Instagram CRM Manager

A new extension

Size

37.2 KB

Version

1.1.1

Created

Feb 3, 2026

Updated

14 days ago

1// ==UserScript==
2// @name		Instagram CRM Manager
3// @description		A new extension
4// @version		1.1.1
5// @match		https://*.instagram.com/*
6// @icon		https://static.cdninstagram.com/rsrc.php/y4/r/QaBlI0OZiks.ico
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    // Utility function to debounce
12    function debounce(func, wait) {
13        let timeout;
14        return function executedFunction(...args) {
15            const later = () => {
16                clearTimeout(timeout);
17                func(...args);
18            };
19            clearTimeout(timeout);
20            timeout = setTimeout(later, wait);
21        };
22    }
23
24    // CRM Data Manager
25    class CRMDataManager {
26        constructor() {
27            this.contacts = [];
28            this.initialized = false;
29        }
30
31        async init() {
32            if (this.initialized) return;
33            const data = await GM.getValue('instagram_crm_contacts', '[]');
34            this.contacts = JSON.parse(data);
35            this.initialized = true;
36            console.log('CRM Data Manager initialized with', this.contacts.length, 'contacts');
37        }
38
39        async saveContacts() {
40            await GM.setValue('instagram_crm_contacts', JSON.stringify(this.contacts));
41            console.log('Contacts saved:', this.contacts.length);
42        }
43
44        async addContact(contactData) {
45            const existingIndex = this.contacts.findIndex(c => c.username === contactData.username);
46            
47            if (existingIndex >= 0) {
48                // Update existing contact
49                this.contacts[existingIndex] = {
50                    ...this.contacts[existingIndex],
51                    ...contactData,
52                    lastUpdated: new Date().toISOString()
53                };
54                console.log('Updated existing contact:', contactData.username);
55            } else {
56                // Add new contact
57                const newContact = {
58                    id: Date.now().toString(),
59                    ...contactData,
60                    notes: contactData.notes || '',
61                    tags: contactData.tags || [],
62                    dateAdded: new Date().toISOString(),
63                    lastUpdated: new Date().toISOString(),
64                    interactions: contactData.interactions || [],
65                    lastContactDate: contactData.lastContactDate || null,
66                    reminderInterval: contactData.reminderInterval || 3
67                };
68                this.contacts.unshift(newContact);
69                console.log('Added new contact:', contactData.username);
70            }
71            
72            await this.saveContacts();
73            return true;
74        }
75
76        async updateContact(id, updates) {
77            const index = this.contacts.findIndex(c => c.id === id);
78            if (index >= 0) {
79                this.contacts[index] = {
80                    ...this.contacts[index],
81                    ...updates,
82                    lastUpdated: new Date().toISOString()
83                };
84                await this.saveContacts();
85                return true;
86            }
87            return false;
88        }
89
90        async deleteContact(id) {
91            const index = this.contacts.findIndex(c => c.id === id);
92            if (index >= 0) {
93                this.contacts.splice(index, 1);
94                await this.saveContacts();
95                return true;
96            }
97            return false;
98        }
99
100        getContacts() {
101            return this.contacts;
102        }
103
104        searchContacts(query) {
105            const lowerQuery = query.toLowerCase();
106            return this.contacts.filter(contact => 
107                contact.username.toLowerCase().includes(lowerQuery) ||
108                contact.fullName?.toLowerCase().includes(lowerQuery) ||
109                contact.notes?.toLowerCase().includes(lowerQuery) ||
110                contact.tags?.some(tag => tag.toLowerCase().includes(lowerQuery))
111            );
112        }
113
114        getOverdueContacts() {
115            const now = new Date();
116            return this.contacts.filter(contact => {
117                if (!contact.lastContactDate) return false;
118                const lastContact = new Date(contact.lastContactDate);
119                const daysSinceContact = Math.floor((now - lastContact) / (1000 * 60 * 60 * 24));
120                const reminderInterval = contact.reminderInterval || 3;
121                return daysSinceContact >= reminderInterval;
122            });
123        }
124
125        async markAsContacted(id) {
126            const index = this.contacts.findIndex(c => c.id === id);
127            if (index >= 0) {
128                this.contacts[index].lastContactDate = new Date().toISOString();
129                this.contacts[index].lastUpdated = new Date().toISOString();
130                await this.saveContacts();
131                return true;
132            }
133            return false;
134        }
135    }
136
137    // CRM UI Manager
138    class CRMUIManager {
139        constructor(dataManager) {
140            this.dataManager = dataManager;
141            this.panel = null;
142            this.isOpen = false;
143            this.currentView = 'list'; // 'list' or 'detail'
144            this.selectedContact = null;
145        }
146
147        createPanel() {
148            if (this.panel) return;
149
150            const panel = document.createElement('div');
151            panel.id = 'instagram-crm-panel';
152            panel.style.cssText = `
153                position: fixed;
154                top: 0;
155                right: -400px;
156                width: 400px;
157                height: 100vh;
158                background: #ffffff;
159                box-shadow: -2px 0 10px rgba(0,0,0,0.2);
160                z-index: 999999;
161                transition: right 0.3s ease;
162                display: flex;
163                flex-direction: column;
164                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
165            `;
166
167            panel.innerHTML = `
168                <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; display: flex; justify-content: space-between; align-items: center;">
169                    <h2 style="margin: 0; font-size: 20px; font-weight: 600;">Instagram CRM</h2>
170                    <button id="crm-close-btn" style="background: rgba(255,255,255,0.2); border: none; color: white; width: 30px; height: 30px; border-radius: 50%; cursor: pointer; font-size: 18px; display: flex; align-items: center; justify-content: center;">×</button>
171                </div>
172                
173                <div style="padding: 15px; border-bottom: 1px solid #dbdbdb;">
174                    <input type="text" id="crm-search" placeholder="Search contacts..." style="width: 100%; padding: 10px; border: 1px solid #dbdbdb; border-radius: 8px; font-size: 14px; box-sizing: border-box;">
175                </div>
176
177                <div id="crm-content" style="flex: 1; overflow-y: auto; padding: 15px;">
178                    <!-- Content will be dynamically loaded here -->
179                </div>
180
181                <div style="padding: 15px; border-top: 1px solid #dbdbdb; background: #fafafa;">
182                    <div style="font-size: 12px; color: #8e8e8e; text-align: center;">
183                        <span id="crm-contact-count">0</span> contacts saved
184                    </div>
185                </div>
186            `;
187
188            document.body.appendChild(panel);
189            this.panel = panel;
190
191            // Event listeners
192            panel.querySelector('#crm-close-btn').addEventListener('click', () => this.togglePanel());
193            panel.querySelector('#crm-search').addEventListener('input', debounce((e) => {
194                this.handleSearch(e.target.value);
195            }, 300));
196
197            console.log('CRM Panel created');
198        }
199
200        createToggleButton() {
201            const button = document.createElement('button');
202            button.id = 'crm-toggle-btn';
203            button.innerHTML = '👥';
204            button.title = 'Open CRM Manager';
205            button.style.cssText = `
206                position: fixed;
207                bottom: 30px;
208                right: 30px;
209                width: 60px;
210                height: 60px;
211                border-radius: 50%;
212                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
213                color: white;
214                border: none;
215                font-size: 28px;
216                cursor: pointer;
217                box-shadow: 0 4px 12px rgba(0,0,0,0.3);
218                z-index: 999998;
219                transition: transform 0.2s ease;
220                display: flex;
221                align-items: center;
222                justify-content: center;
223                position: relative;
224            `;
225
226            button.addEventListener('mouseenter', () => {
227                button.style.transform = 'scale(1.1)';
228            });
229
230            button.addEventListener('mouseleave', () => {
231                button.style.transform = 'scale(1)';
232            });
233
234            button.addEventListener('click', () => this.togglePanel());
235
236            document.body.appendChild(button);
237            this.updateBadge();
238            console.log('CRM Toggle button created');
239        }
240
241        togglePanel() {
242            this.isOpen = !this.isOpen;
243            if (this.isOpen) {
244                this.panel.style.right = '0';
245                this.renderContactList();
246            } else {
247                this.panel.style.right = '-400px';
248            }
249        }
250
251        updateBadge() {
252            const overdueCount = this.dataManager.getOverdueContacts().length;
253            const button = document.getElementById('crm-toggle-btn');
254            if (!button) return;
255
256            let badge = document.getElementById('crm-badge');
257            if (overdueCount > 0) {
258                if (!badge) {
259                    badge = document.createElement('div');
260                    badge.id = 'crm-badge';
261                    badge.style.cssText = `
262                        position: absolute;
263                        top: -5px;
264                        right: -5px;
265                        background: #ed4956;
266                        color: white;
267                        border-radius: 50%;
268                        width: 24px;
269                        height: 24px;
270                        display: flex;
271                        align-items: center;
272                        justify-content: center;
273                        font-size: 12px;
274                        font-weight: bold;
275                        border: 2px solid white;
276                    `;
277                    button.appendChild(badge);
278                }
279                badge.textContent = overdueCount > 99 ? '99+' : overdueCount;
280            } else if (badge) {
281                badge.remove();
282            }
283        }
284
285        renderContactList() {
286            const content = this.panel.querySelector('#crm-content');
287            const contacts = this.dataManager.getContacts();
288            
289            this.panel.querySelector('#crm-contact-count').textContent = contacts.length;
290
291            if (contacts.length === 0) {
292                content.innerHTML = `
293                    <div style="text-align: center; padding: 40px 20px; color: #8e8e8e;">
294                        <div style="font-size: 48px; margin-bottom: 15px;">📋</div>
295                        <p style="margin: 0; font-size: 16px; font-weight: 500;">No contacts yet</p>
296                        <p style="margin: 10px 0 0 0; font-size: 14px;">Visit Instagram profiles to add contacts</p>
297                    </div>
298                `;
299                return;
300            }
301
302            const overdueContacts = this.dataManager.getOverdueContacts();
303            const overdueIds = new Set(overdueContacts.map(c => c.id));
304
305            let html = '<div style="display: flex; flex-direction: column; gap: 10px;">';
306            
307            contacts.forEach(contact => {
308                const initials = contact.fullName ? contact.fullName.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2) : contact.username.slice(0, 2).toUpperCase();
309                const avatarColor = this.getColorFromString(contact.username);
310                const isOverdue = overdueIds.has(contact.id);
311                
312                html += `
313                    <div class="crm-contact-card" data-id="${contact.id}" style="background: white; border: 1px solid ${isOverdue ? '#ed4956' : '#dbdbdb'}; border-radius: 12px; padding: 15px; cursor: pointer; transition: all 0.2s ease; ${isOverdue ? 'box-shadow: 0 0 0 2px rgba(237, 73, 86, 0.1);' : ''}">
314                        <div style="display: flex; align-items: center; gap: 12px;">
315                            <div style="width: 50px; height: 50px; border-radius: 50%; background: ${avatarColor}; display: flex; align-items: center; justify-content: center; color: white; font-weight: 600; font-size: 16px; flex-shrink: 0; position: relative;">
316                                ${contact.profilePic ? `<img src="${contact.profilePic}" style="width: 100%; height: 100%; border-radius: 50%; object-fit: cover;">` : initials}
317                                ${isOverdue ? '<div style="position: absolute; top: -3px; right: -3px; width: 16px; height: 16px; background: #ed4956; border: 2px solid white; border-radius: 50%;"></div>' : ''}
318                            </div>
319                            <div style="flex: 1; min-width: 0;">
320                                <div style="font-weight: 600; font-size: 14px; color: #262626; margin-bottom: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
321                                    ${contact.fullName || contact.username}
322                                    ${isOverdue ? '<span style="color: #ed4956; margin-left: 5px;">⏰</span>' : ''}
323                                </div>
324                                <div style="font-size: 13px; color: #8e8e8e; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
325                                    @${contact.username}
326                                </div>
327                                ${contact.tags && contact.tags.length > 0 ? `
328                                    <div style="margin-top: 5px; display: flex; gap: 5px; flex-wrap: wrap;">
329                                        ${contact.tags.slice(0, 2).map(tag => `<span style="background: #e0e7ff; color: #4c51bf; padding: 2px 8px; border-radius: 12px; font-size: 11px;">${tag}</span>`).join('')}
330                                    </div>
331                                ` : ''}
332                            </div>
333                        </div>
334                    </div>
335                `;
336            });
337            
338            html += '</div>';
339            content.innerHTML = html;
340
341            // Add click listeners to contact cards
342            content.querySelectorAll('.crm-contact-card').forEach(card => {
343                card.addEventListener('mouseenter', () => {
344                    card.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)';
345                    card.style.transform = 'translateY(-2px)';
346                });
347                card.addEventListener('mouseleave', () => {
348                    const contactId = card.getAttribute('data-id');
349                    const isOverdue = overdueIds.has(contactId);
350                    card.style.boxShadow = isOverdue ? '0 0 0 2px rgba(237, 73, 86, 0.1)' : 'none';
351                    card.style.transform = 'translateY(0)';
352                });
353                card.addEventListener('click', () => {
354                    const contactId = card.getAttribute('data-id');
355                    this.showContactDetail(contactId);
356                });
357            });
358        }
359
360        showContactDetail(contactId) {
361            const contact = this.dataManager.getContacts().find(c => c.id === contactId);
362            if (!contact) return;
363
364            this.selectedContact = contact;
365            const content = this.panel.querySelector('#crm-content');
366            
367            const initials = contact.fullName ? contact.fullName.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2) : contact.username.slice(0, 2).toUpperCase();
368            const avatarColor = this.getColorFromString(contact.username);
369
370            const lastContactDate = contact.lastContactDate ? new Date(contact.lastContactDate) : null;
371            const daysSinceContact = lastContactDate ? Math.floor((new Date() - lastContactDate) / (1000 * 60 * 60 * 24)) : null;
372            const reminderInterval = contact.reminderInterval || 3;
373            const isOverdue = daysSinceContact !== null && daysSinceContact >= reminderInterval;
374
375            content.innerHTML = `
376                <div>
377                    <button id="crm-back-btn" style="background: none; border: none; color: #667eea; cursor: pointer; font-size: 14px; padding: 0 0 15px 0; display: flex; align-items: center; gap: 5px;">
378                        ← Back to list
379                    </button>
380
381                    <div style="text-align: center; margin-bottom: 20px;">
382                        <div style="width: 80px; height: 80px; border-radius: 50%; background: ${avatarColor}; display: flex; align-items: center; justify-content: center; color: white; font-weight: 600; font-size: 24px; margin: 0 auto 15px;">
383                            ${contact.profilePic ? `<img src="${contact.profilePic}" style="width: 100%; height: 100%; border-radius: 50%; object-fit: cover;">` : initials}
384                        </div>
385                        <h3 style="margin: 0 0 5px 0; font-size: 18px; color: #262626;">${contact.fullName || contact.username}</h3>
386                        <a href="https://www.instagram.com/${contact.username}" target="_blank" style="color: #667eea; text-decoration: none; font-size: 14px;">@${contact.username}</a>
387                    </div>
388
389                    ${isOverdue ? `
390                        <div style="background: #fff3cd; border: 1px solid #ffc107; padding: 12px; border-radius: 8px; margin-bottom: 15px; text-align: center;">
391                            <div style="font-size: 14px; color: #856404; font-weight: 600;">⏰ Follow-up Reminder</div>
392                            <div style="font-size: 13px; color: #856404; margin-top: 5px;">Last contact: ${daysSinceContact} days ago</div>
393                        </div>
394                    ` : ''}
395
396                    <div style="margin-bottom: 15px;">
397                        <label style="display: block; font-size: 12px; color: #8e8e8e; margin-bottom: 8px; font-weight: 600;">CONTACT TRACKING</label>
398                        <div style="background: #f8f9fa; padding: 12px; border-radius: 8px; margin-bottom: 10px;">
399                            <div style="font-size: 13px; color: #262626; margin-bottom: 8px;">
400                                ${lastContactDate ? `Last contacted: ${lastContactDate.toLocaleDateString()} (${daysSinceContact} days ago)` : 'No contact recorded yet'}
401                            </div>
402                            <div style="display: flex; gap: 8px; align-items: center; margin-bottom: 8px;">
403                                <label style="font-size: 13px; color: #262626;">Remind me every:</label>
404                                <select id="crm-reminder-interval" style="padding: 6px; border: 1px solid #dbdbdb; border-radius: 6px; font-size: 13px;">
405                                    <option value="1" ${reminderInterval === 1 ? 'selected' : ''}>1 day</option>
406                                    <option value="2" ${reminderInterval === 2 ? 'selected' : ''}>2 days</option>
407                                    <option value="3" ${reminderInterval === 3 ? 'selected' : ''}>3 days</option>
408                                    <option value="5" ${reminderInterval === 5 ? 'selected' : ''}>5 days</option>
409                                    <option value="7" ${reminderInterval === 7 ? 'selected' : ''}>7 days</option>
410                                    <option value="14" ${reminderInterval === 14 ? 'selected' : ''}>14 days</option>
411                                    <option value="30" ${reminderInterval === 30 ? 'selected' : ''}>30 days</option>
412                                </select>
413                            </div>
414                        </div>
415                        <button id="crm-mark-contacted" style="width: 100%; padding: 10px; background: #43e97b; color: white; border: none; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer;">✓ Mark as Contacted Today</button>
416                    </div>
417
418                    ${contact.bio ? `
419                        <div style="background: #f8f9fa; padding: 12px; border-radius: 8px; margin-bottom: 15px;">
420                            <div style="font-size: 12px; color: #8e8e8e; margin-bottom: 5px; font-weight: 600;">BIO</div>
421                            <div style="font-size: 14px; color: #262626;">${contact.bio}</div>
422                        </div>
423                    ` : ''}
424
425                    ${contact.followers || contact.following ? `
426                        <div style="display: flex; gap: 10px; margin-bottom: 15px;">
427                            ${contact.followers ? `
428                                <div style="flex: 1; background: #f8f9fa; padding: 12px; border-radius: 8px; text-align: center;">
429                                    <div style="font-size: 16px; font-weight: 600; color: #262626;">${contact.followers}</div>
430                                    <div style="font-size: 12px; color: #8e8e8e;">Followers</div>
431                                </div>
432                            ` : ''}
433                            ${contact.following ? `
434                                <div style="flex: 1; background: #f8f9fa; padding: 12px; border-radius: 8px; text-align: center;">
435                                    <div style="font-size: 16px; font-weight: 600; color: #262626;">${contact.following}</div>
436                                    <div style="font-size: 12px; color: #8e8e8e;">Following</div>
437                                </div>
438                            ` : ''}
439                        </div>
440                    ` : ''}
441
442                    <div style="margin-bottom: 15px;">
443                        <label style="display: block; font-size: 12px; color: #8e8e8e; margin-bottom: 5px; font-weight: 600;">TAGS</label>
444                        <div id="crm-tags-container" style="display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 8px;">
445                            ${contact.tags && contact.tags.length > 0 ? contact.tags.map(tag => `
446                                <span class="crm-tag" data-tag="${tag}" style="background: #e0e7ff; color: #4c51bf; padding: 5px 10px; border-radius: 15px; font-size: 12px; display: flex; align-items: center; gap: 5px;">
447                                    ${tag}
448                                    <span class="crm-tag-remove" style="cursor: pointer; font-weight: bold;">×</span>
449                                </span>
450                            `).join('') : '<span style="font-size: 13px; color: #8e8e8e;">No tags</span>'}
451                        </div>
452                        <input type="text" id="crm-add-tag" placeholder="Add tag and press Enter" style="width: 100%; padding: 8px; border: 1px solid #dbdbdb; border-radius: 6px; font-size: 13px; box-sizing: border-box;">
453                    </div>
454
455                    <div style="margin-bottom: 15px;">
456                        <label style="display: block; font-size: 12px; color: #8e8e8e; margin-bottom: 5px; font-weight: 600;">NOTES</label>
457                        <textarea id="crm-notes" style="width: 100%; min-height: 100px; padding: 10px; border: 1px solid #dbdbdb; border-radius: 8px; font-size: 14px; font-family: inherit; resize: vertical; box-sizing: border-box;">${contact.notes || ''}</textarea>
458                        <button id="crm-save-notes" style="margin-top: 8px; width: 100%; padding: 10px; background: #667eea; color: white; border: none; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer;">Save Notes</button>
459                    </div>
460
461                    <div style="font-size: 11px; color: #8e8e8e; margin-bottom: 15px;">
462                        Added: ${new Date(contact.dateAdded).toLocaleDateString()}<br>
463                        Last updated: ${new Date(contact.lastUpdated).toLocaleDateString()}
464                    </div>
465
466                    <button id="crm-delete-contact" style="width: 100%; padding: 10px; background: #ed4956; color: white; border: none; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer;">Delete Contact</button>
467                </div>
468            `;
469
470            // Event listeners
471            content.querySelector('#crm-back-btn').addEventListener('click', () => this.renderContactList());
472            
473            content.querySelector('#crm-mark-contacted').addEventListener('click', async () => {
474                await this.dataManager.markAsContacted(contact.id);
475                this.updateBadge();
476                this.showContactDetail(contact.id);
477            });
478
479            content.querySelector('#crm-reminder-interval').addEventListener('change', async (e) => {
480                const interval = parseInt(e.target.value);
481                await this.dataManager.updateContact(contact.id, { reminderInterval: interval });
482                this.selectedContact.reminderInterval = interval;
483                this.updateBadge();
484            });
485
486            content.querySelector('#crm-save-notes').addEventListener('click', async () => {
487                const notes = content.querySelector('#crm-notes').value;
488                await this.dataManager.updateContact(contact.id, { notes });
489                this.selectedContact.notes = notes;
490                alert('Notes saved!');
491            });
492
493            content.querySelector('#crm-delete-contact').addEventListener('click', async () => {
494                if (confirm(`Delete ${contact.username} from your CRM?`)) {
495                    await this.dataManager.deleteContact(contact.id);
496                    this.updateBadge();
497                    this.renderContactList();
498                }
499            });
500
501            // Tag management
502            const tagInput = content.querySelector('#crm-add-tag');
503            tagInput.addEventListener('keypress', async (e) => {
504                if (e.key === 'Enter' && tagInput.value.trim()) {
505                    const newTag = tagInput.value.trim();
506                    const updatedTags = [...(contact.tags || []), newTag];
507                    await this.dataManager.updateContact(contact.id, { tags: updatedTags });
508                    this.selectedContact.tags = updatedTags;
509                    this.showContactDetail(contact.id);
510                }
511            });
512
513            // Tag removal
514            content.querySelectorAll('.crm-tag-remove').forEach(removeBtn => {
515                removeBtn.addEventListener('click', async (e) => {
516                    const tagElement = e.target.closest('.crm-tag');
517                    const tagToRemove = tagElement.getAttribute('data-tag');
518                    const updatedTags = contact.tags.filter(t => t !== tagToRemove);
519                    await this.dataManager.updateContact(contact.id, { tags: updatedTags });
520                    this.selectedContact.tags = updatedTags;
521                    this.showContactDetail(contact.id);
522                });
523            });
524        }
525
526        handleSearch(query) {
527            if (!query.trim()) {
528                this.renderContactList();
529                return;
530            }
531
532            const results = this.dataManager.searchContacts(query);
533            const content = this.panel.querySelector('#crm-content');
534
535            if (results.length === 0) {
536                content.innerHTML = `
537                    <div style="text-align: center; padding: 40px 20px; color: #8e8e8e;">
538                        <div style="font-size: 48px; margin-bottom: 15px;">🔍</div>
539                        <p style="margin: 0; font-size: 16px; font-weight: 500;">No results found</p>
540                        <p style="margin: 10px 0 0 0; font-size: 14px;">Try a different search term</p>
541                    </div>
542                `;
543                return;
544            }
545
546            let html = '<div style="display: flex; flex-direction: column; gap: 10px;">';
547            
548            results.forEach(contact => {
549                const initials = contact.fullName ? contact.fullName.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2) : contact.username.slice(0, 2).toUpperCase();
550                const avatarColor = this.getColorFromString(contact.username);
551                
552                html += `
553                    <div class="crm-contact-card" data-id="${contact.id}" style="background: white; border: 1px solid #dbdbdb; border-radius: 12px; padding: 15px; cursor: pointer; transition: all 0.2s ease;">
554                        <div style="display: flex; align-items: center; gap: 12px;">
555                            <div style="width: 50px; height: 50px; border-radius: 50%; background: ${avatarColor}; display: flex; align-items: center; justify-content: center; color: white; font-weight: 600; font-size: 16px; flex-shrink: 0;">
556                                ${contact.profilePic ? `<img src="${contact.profilePic}" style="width: 100%; height: 100%; border-radius: 50%; object-fit: cover;">` : initials}
557                            </div>
558                            <div style="flex: 1; min-width: 0;">
559                                <div style="font-weight: 600; font-size: 14px; color: #262626; margin-bottom: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
560                                    ${contact.fullName || contact.username}
561                                </div>
562                                <div style="font-size: 13px; color: #8e8e8e; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
563                                    @${contact.username}
564                                </div>
565                            </div>
566                        </div>
567                    </div>
568                `;
569            });
570            
571            html += '</div>';
572            content.innerHTML = html;
573
574            // Add click listeners
575            content.querySelectorAll('.crm-contact-card').forEach(card => {
576                card.addEventListener('mouseenter', () => {
577                    card.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)';
578                    card.style.transform = 'translateY(-2px)';
579                });
580                card.addEventListener('mouseleave', () => {
581                    card.style.boxShadow = 'none';
582                    card.style.transform = 'translateY(0)';
583                });
584                card.addEventListener('click', () => {
585                    const contactId = card.getAttribute('data-id');
586                    this.showContactDetail(contactId);
587                });
588            });
589        }
590
591        getColorFromString(str) {
592            const colors = [
593                '#667eea', '#764ba2', '#f093fb', '#4facfe',
594                '#43e97b', '#fa709a', '#fee140', '#30cfd0',
595                '#a8edea', '#fed6e3', '#c471f5', '#fa71cd'
596            ];
597            let hash = 0;
598            for (let i = 0; i < str.length; i++) {
599                hash = str.charCodeAt(i) + ((hash << 5) - hash);
600            }
601            return colors[Math.abs(hash) % colors.length];
602        }
603    }
604
605    // Profile Scraper
606    class ProfileScraper {
607        async scrapeCurrentProfile() {
608            console.log('Scraping profile data...');
609            
610            // Get username from URL
611            const urlMatch = window.location.pathname.match(/^\/([^\/]+)\/?$/);
612            if (!urlMatch) {
613                console.log('Not on a profile page');
614                return null;
615            }
616
617            const username = urlMatch[1];
618            
619            // Wait for profile data to load
620            await new Promise(resolve => setTimeout(resolve, 2000));
621
622            const profileData = {
623                username: username,
624                fullName: '',
625                bio: '',
626                followers: '',
627                following: '',
628                profilePic: '',
629                profileUrl: window.location.href
630            };
631
632            // Try to get full name
633            const nameElement = document.querySelector('header section h2, header section span[dir="auto"]');
634            if (nameElement) {
635                profileData.fullName = nameElement.textContent.trim();
636            }
637
638            // Try to get bio
639            const bioElement = document.querySelector('header section span[style*="line-height"], header section div[style*="line-height"] span');
640            if (bioElement) {
641                profileData.bio = bioElement.textContent.trim();
642            }
643
644            // Try to get follower/following counts
645            const statsElements = document.querySelectorAll('header section ul li span[title], header section ul li span span');
646            if (statsElements.length >= 2) {
647                profileData.followers = statsElements[0].textContent.trim();
648                profileData.following = statsElements[1].textContent.trim();
649            }
650
651            // Try to get profile picture
652            const profilePicElement = document.querySelector('header img[alt*="profile picture"], header img[alt*="' + username + '"]');
653            if (profilePicElement) {
654                profileData.profilePic = profilePicElement.src;
655            }
656
657            console.log('Scraped profile data:', profileData);
658            return profileData;
659        }
660
661        addSaveButton(dataManager, uiManager) {
662            // Remove existing button if any
663            const existingBtn = document.querySelector('#crm-save-profile-btn');
664            if (existingBtn) existingBtn.remove();
665
666            // Check if we're on a profile page
667            const urlMatch = window.location.pathname.match(/^\/([^\/]+)\/?$/);
668            if (!urlMatch) return;
669
670            // Wait for header to load
671            setTimeout(() => {
672                const header = document.querySelector('header section');
673                if (!header) return;
674
675                const saveBtn = document.createElement('button');
676                saveBtn.id = 'crm-save-profile-btn';
677                saveBtn.innerHTML = '💾 Save to CRM';
678                saveBtn.style.cssText = `
679                    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
680                    color: white;
681                    border: none;
682                    padding: 8px 16px;
683                    border-radius: 8px;
684                    font-size: 14px;
685                    font-weight: 600;
686                    cursor: pointer;
687                    margin-left: 8px;
688                    transition: transform 0.2s ease;
689                `;
690
691                saveBtn.addEventListener('mouseenter', () => {
692                    saveBtn.style.transform = 'scale(1.05)';
693                });
694
695                saveBtn.addEventListener('mouseleave', () => {
696                    saveBtn.style.transform = 'scale(1)';
697                });
698
699                saveBtn.addEventListener('click', async () => {
700                    saveBtn.disabled = true;
701                    saveBtn.innerHTML = '⏳ Saving...';
702                    
703                    const profileData = await this.scrapeCurrentProfile();
704                    if (profileData) {
705                        await dataManager.addContact(profileData);
706                        saveBtn.innerHTML = '✅ Saved!';
707                        setTimeout(() => {
708                            saveBtn.innerHTML = '💾 Save to CRM';
709                            saveBtn.disabled = false;
710                        }, 2000);
711                    } else {
712                        saveBtn.innerHTML = '❌ Failed';
713                        setTimeout(() => {
714                            saveBtn.innerHTML = '💾 Save to CRM';
715                            saveBtn.disabled = false;
716                        }, 2000);
717                    }
718                });
719
720                // Find a good place to insert the button
721                const buttonContainer = header.querySelector('div[style*="flex-direction: row"]');
722                if (buttonContainer) {
723                    buttonContainer.appendChild(saveBtn);
724                } else {
725                    header.appendChild(saveBtn);
726                }
727
728                console.log('Save button added to profile');
729            }, 2000);
730        }
731    }
732
733    // Main initialization
734    async function init() {
735        console.log('Instagram CRM Manager initializing...');
736
737        const dataManager = new CRMDataManager();
738        await dataManager.init();
739
740        const uiManager = new CRMUIManager(dataManager);
741        uiManager.createPanel();
742        uiManager.createToggleButton();
743
744        const scraper = new ProfileScraper();
745
746        // Add save button on profile pages
747        if (window.location.pathname.match(/^\/[^\/]+\/?$/)) {
748            scraper.addSaveButton(dataManager, uiManager);
749        }
750
751        // Watch for navigation changes
752        let lastUrl = window.location.href;
753        const observer = new MutationObserver(debounce(() => {
754            if (window.location.href !== lastUrl) {
755                lastUrl = window.location.href;
756                console.log('URL changed:', lastUrl);
757                
758                if (window.location.pathname.match(/^\/[^\/]+\/?$/)) {
759                    scraper.addSaveButton(dataManager, uiManager);
760                }
761            }
762        }, 500));
763
764        observer.observe(document.body, {
765            childList: true,
766            subtree: true
767        });
768
769        console.log('Instagram CRM Manager initialized successfully!');
770    }
771
772    // Start when page is ready
773    if (document.readyState === 'loading') {
774        document.addEventListener('DOMContentLoaded', init);
775    } else {
776        init();
777    }
778})();
Instagram CRM Manager | Robomonkey