Ozon BASE Messenger Bulk Sender

Массовая рассылка сообщений покупателям в Ozon Messenger

Size

71.6 KB

Version

1.2.0

Created

Jan 28, 2026

Updated

3 months ago

1// ==UserScript==
2// @name		Ozon BASE Messenger Bulk Sender
3// @description		Массовая рассылка сообщений покупателям в Ozon Messenger
4// @version		1.2.0
5// @match		*://seller.ozon.ru/*
6// @grant		GM.getValue
7// @grant		GM.setValue
8// @grant		GM.openInTab
9// ==/UserScript==
10(function() {
11    'use strict';
12
13    console.log('Ozon Messenger Bulk Sender загружен - ВЕРСИЯ 1.2.0');
14
15    // Глобальные переменные для управления процессом
16    let isRunning = false;
17    let isPaused = false;
18    let shouldStop = false;
19
20    // ============= ФУНКЦИИ ДЛЯ РАБОТЫ С СОСТОЯНИЕМ РАССЫЛКИ =============
21    
22    // Очистка состояния рассылки
23    async function clearSendingState() {
24        await GM.setValue('sendingState', null);
25        console.log('Состояние рассылки очищено');
26    }
27    
28    // Загрузка базы данных контактов
29    async function loadContactsDatabase() {
30        try {
31            const dbString = await GM.getValue('contactsDatabase', null);
32            if (dbString) {
33                const db = JSON.parse(dbString);
34                console.log('База данных загружена:', Object.keys(db.contacts || {}).length, 'контактов');
35                return db;
36            }
37        } catch (error) {
38            console.error('Ошибка загрузки базы данных:', error);
39        }
40        
41        // Возвращаем пустую базу, если её нет
42        return {
43            contacts: {},
44            lastUpdate: null,
45            totalContacts: 0
46        };
47    }
48    
49    // Сохранение базы данных контактов
50    async function saveContactsDatabase(db) {
51        try {
52            db.lastUpdate = new Date().toISOString();
53            db.totalContacts = Object.keys(db.contacts).length;
54            await GM.setValue('contactsDatabase', JSON.stringify(db));
55            console.log('База данных сохранена:', db.totalContacts, 'контактов');
56            return true;
57        } catch (error) {
58            console.error('Ошибка сохранения базы данных:', error);
59            return false;
60        }
61    }
62    
63    // Добавление контакта в базу
64    function addContactToDatabase(db, contactId, contactData) {
65        db.contacts[contactId] = {
66            id: contactId,
67            name: contactData.name || 'Неизвестно',
68            lastMessageDate: contactData.lastMessageDate || null,
69            lastSentDate: contactData.lastSentDate || null,
70            addedAt: contactData.addedAt || new Date().toISOString(),
71            messageCount: contactData.messageCount || 0
72        };
73    }
74    
75    // Получение статистики базы данных
76    function getDatabaseStats(db) {
77        const total = Object.keys(db.contacts).length;
78        const withDates = Object.values(db.contacts).filter(c => c.lastMessageDate).length;
79        const sent = Object.values(db.contacts).filter(c => c.lastSentDate).length;
80        
81        return {
82            total,
83            withDates,
84            sent,
85            notSent: total - sent,
86            lastUpdate: db.lastUpdate
87        };
88    }
89    
90    // Удаление дубликатов из базы данных
91    async function removeDuplicatesFromDatabase() {
92        const db = await loadContactsDatabase();
93        const initialCount = Object.keys(db.contacts).length;
94        
95        // Дубликаты уже не могут существовать, т.к. мы используем ID как ключ
96        // Но проверим на всякий случай
97        console.log('Проверка дубликатов в базе данных...');
98        console.log('Всего контактов:', initialCount);
99        
100        // Дубликатов быть не может, т.к. используется объект с уникальными ключами
101        alert(`Проверка завершена!\nВсего контактов: ${initialCount}\nДубликатов не обнаружено (используются уникальные ID)`);
102        
103        return {
104            initial: initialCount,
105            final: initialCount,
106            removed: 0
107        };
108    }
109    
110    // Функция для создания модального окна просмотра базы
111    function createViewDatabaseModal() {
112        // Проверяем, не создано ли уже окно
113        if (document.getElementById('viewDbModal')) {
114            return;
115        }
116        
117        const modalHTML = `
118            <div id="viewDbModal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 10001; justify-content: center; align-items: center;">
119                <div style="background: white; padding: 30px; border-radius: 12px; width: 800px; max-width: 95%; max-height: 90vh; overflow-y: auto; box-shadow: 0 4px 20px rgba(0,0,0,0.3);">
120                    <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
121                        <h2 style="margin: 0; color: #333; font-size: 24px;">👁️ Просмотр базы контактов</h2>
122                        <button id="closeViewDbButton" style="padding: 8px 16px; background: #999; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600;">Закрыть</button>
123                    </div>
124                    
125                    <div id="viewDbStatsBlock" style="margin-bottom: 20px; padding: 15px; background: #f0f8ff; border-radius: 6px; border-left: 4px solid #0066cc;">
126                        <div style="font-weight: 600; color: #0066cc; margin-bottom: 10px;">Статистика базы:</div>
127                        <div id="viewDbStatsContent" style="color: #333; font-size: 14px;"></div>
128                    </div>
129                    
130                    <div style="margin-bottom: 15px; display: flex; gap: 10px; align-items: center;">
131                        <input type="text" id="searchContactInput" placeholder="Поиск по имени..." style="flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px;">
132                        <button id="removeDuplicatesButton" style="padding: 10px 18px; background: #ff9800; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 600;">🗑️ Удалить дубликаты</button>
133                    </div>
134                    
135                    <div id="contactsListContainer" style="max-height: 500px; overflow-y: auto; border: 1px solid #ddd; border-radius: 6px;">
136                        <div id="contactsList" style="padding: 10px;"></div>
137                    </div>
138                </div>
139            </div>
140        `;
141        
142        document.body.insertAdjacentHTML('beforeend', modalHTML);
143        console.log('Модальное окно просмотра базы создано');
144        
145        // Обработчики событий
146        document.getElementById('closeViewDbButton').addEventListener('click', closeViewDatabaseModal);
147        document.getElementById('removeDuplicatesButton').addEventListener('click', async () => {
148            await removeDuplicatesFromDatabase();
149            await loadAndDisplayContacts();
150        });
151        
152        // Поиск по контактам
153        document.getElementById('searchContactInput').addEventListener('input', (e) => {
154            filterContactsList(e.target.value);
155        });
156    }
157    
158    // Функция для открытия модального окна просмотра базы
159    async function openViewDatabaseModal() {
160        createViewDatabaseModal();
161        const modal = document.getElementById('viewDbModal');
162        if (modal) {
163            modal.style.display = 'flex';
164            await loadAndDisplayContacts();
165            console.log('Модальное окно просмотра базы открыто');
166        }
167    }
168    
169    // Функция для закрытия модального окна просмотра базы
170    function closeViewDatabaseModal() {
171        const modal = document.getElementById('viewDbModal');
172        if (modal) {
173            modal.style.display = 'none';
174            console.log('Модальное окно просмотра базы закрыто');
175        }
176    }
177    
178    // Функция для загрузки и отображения контактов
179    async function loadAndDisplayContacts(searchQuery = '') {
180        const db = await loadContactsDatabase();
181        const stats = getDatabaseStats(db);
182        const contacts = Object.values(db.contacts);
183        
184        // Обновляем статистику
185        const statsContent = document.getElementById('viewDbStatsContent');
186        if (statsContent) {
187            statsContent.innerHTML = `
188                <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px;">
189                    <div><strong>Всего контактов:</strong> ${stats.total}</div>
190                    <div><strong>С датами:</strong> ${stats.withDates}</div>
191                    <div><strong>Отправлено:</strong> ${stats.sent}</div>
192                    <div><strong>Не отправлено:</strong> ${stats.notSent}</div>
193                </div>
194            `;
195        }
196        
197        // Фильтруем контакты по поисковому запросу
198        const filteredContacts = searchQuery 
199            ? contacts.filter(c => c.name.toLowerCase().includes(searchQuery.toLowerCase()))
200            : contacts;
201        
202        // Сортируем по дате последнего сообщения (новые сверху)
203        filteredContacts.sort((a, b) => {
204            if (!a.lastMessageDate) return 1;
205            if (!b.lastMessageDate) return -1;
206            return new Date(b.lastMessageDate) - new Date(a.lastMessageDate);
207        });
208        
209        // Отображаем список контактов
210        const contactsList = document.getElementById('contactsList');
211        if (contactsList) {
212            if (filteredContacts.length === 0) {
213                contactsList.innerHTML = '<div style="text-align: center; padding: 40px; color: #999;">Контакты не найдены</div>';
214            } else {
215                contactsList.innerHTML = filteredContacts.map(contact => {
216                    const lastMsgDate = contact.lastMessageDate 
217                        ? new Date(contact.lastMessageDate).toLocaleDateString('ru-RU')
218                        : 'Нет данных';
219                    const lastSentDate = contact.lastSentDate 
220                        ? new Date(contact.lastSentDate).toLocaleDateString('ru-RU')
221                        : 'Не отправлялось';
222                    const sentStatus = contact.lastSentDate ? '✅' : '⏳';
223                    
224                    return `
225                        <div style="padding: 12px; margin-bottom: 8px; background: ${contact.lastSentDate ? '#f0f8ff' : '#fff8f0'}; border: 1px solid #ddd; border-radius: 6px; font-size: 13px;">
226                            <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 8px;">
227                                <div style="font-weight: 600; color: #333; font-size: 14px;">${sentStatus} ${contact.name}</div>
228                                <div style="font-size: 11px; color: #999;">${contact.id.substring(0, 8)}...</div>
229                            </div>
230                            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; color: #666;">
231                                <div><strong>Последнее сообщение:</strong> ${lastMsgDate}</div>
232                                <div><strong>Отправлено:</strong> ${lastSentDate}</div>
233                            </div>
234                            ${contact.messageCount > 0 ? `<div style="margin-top: 5px; color: #666;"><strong>Отправлено сообщений:</strong> ${contact.messageCount}</div>` : ''}
235                        </div>
236                    `;
237                }).join('');
238            }
239        }
240    }
241    
242    // Функция для фильтрации списка контактов
243    function filterContactsList(searchQuery) {
244        loadAndDisplayContacts(searchQuery);
245    }
246
247    // ============= ФУНКЦИИ ДЛЯ СБОРА КОНТАКТОВ =============
248    
249    // Функция для извлечения ID из URL чата
250    function extractChatId(chatElement) {
251        // Ищем атрибут deeplink в элементе чата
252        const deeplink = chatElement.getAttribute('deeplink');
253        if (deeplink) {
254            const match = deeplink.match(/id=([a-f0-9-]+)/);
255            if (match) {
256                console.log('Извлечен ID из deeplink:', match[1]);
257                return match[1];
258            }
259        }
260        
261        // Запасной вариант - ищем ссылку внутри элемента
262        const link = chatElement.querySelector('a[href*="id="]');
263        if (link) {
264            const href = link.getAttribute('href');
265            const match = href.match(/id=([a-f0-9-]+)/);
266            if (match) {
267                console.log('Извлечен ID из ссылки:', match[1]);
268                return match[1];
269            }
270        }
271        
272        console.log('Не удалось извлечь ID чата');
273        return null;
274    }
275    
276    // Функция для получения данных чата из элемента списка
277    function getChatDataFromElement(chatElement) {
278        const chatName = chatElement.querySelector('.index_chatTitle_TiXTq')?.textContent?.trim() || 'Неизвестно';
279        const dateElement = chatElement.querySelector('.index_chatDate_z4mNc, .index_chatDate_WJ-\\+mb');
280        const dateText = dateElement?.textContent?.trim();
281        const lastMessageDate = dateText ? parseDate(dateText) : null;
282        
283        return {
284            name: chatName,
285            lastMessageDate: lastMessageDate ? lastMessageDate.toISOString() : null,
286            lastSentDate: null,
287            messageCount: 0
288        };
289    }
290    
291    // Функция для скролла списка чатов
292    async function scrollChatList() {
293        // Сначала скроллим всю страницу вниз
294        window.scrollTo(0, document.body.scrollHeight);
295        console.log('Проскроллили страницу вниз');
296        
297        // Небольшая пауза
298        await sleep(500);
299        
300        // Проверяем, применен ли фильтр Ozon (ищем кнопку сброса фильтра)
301        const clearFilterBtn = document.querySelector('.c8s110-a2, .c8s110-c0, .c8s110-a4, .c8s110-a5');
302        let filterWasActive = false;
303        
304        if (clearFilterBtn) {
305            console.log('Обнаружен активный фильтр Ozon, временно сбрасываем');
306            filterWasActive = true;
307            clearFilterBtn.click();
308            await sleep(1000); // Ждем сброса фильтра
309        }
310        
311        // Получаем все чаты
312        const allChats = document.querySelectorAll('.index_chat_4fr82');
313        
314        if (allChats.length === 0) {
315            console.error('Чаты не найдены для скролла');
316            return false;
317        }
318        
319        // Находим последний ВИДИМЫЙ чат (не скрытый фильтром)
320        const visibleChats = Array.from(allChats).filter(chat => {
321            const style = window.getComputedStyle(chat);
322            return style.display !== 'none' && chat.offsetParent !== null;
323        });
324        
325        if (visibleChats.length === 0) {
326            console.error('Нет видимых чатов для скролла');
327            return false;
328        }
329        
330        // Берем последний видимый чат и скроллим к нему
331        const lastVisibleChat = visibleChats[visibleChats.length - 1];
332        console.log('Скроллим к последнему видимому чату, всего видимых чатов:', visibleChats.length, 'из', allChats.length);
333        
334        lastVisibleChat.scrollIntoView({ behavior: 'smooth', block: 'end' });
335        
336        // Ждем подгрузки новых чатов
337        await sleep(2500);
338        
339        // Если фильтр был активен, применяем его обратно
340        if (filterWasActive) {
341            console.log('Применяем фильтр Ozon обратно');
342            // Находим кнопку "Применить" фильтра и кликаем
343            const applyFilterBtn = document.querySelector('button[type="submit"]');
344            if (applyFilterBtn) {
345                applyFilterBtn.click();
346                await sleep(1000);
347            }
348        }
349        
350        console.log('Скролл выполнен');
351        return true;
352    }
353    
354    // Основная функция сбора базы контактов
355    async function collectContactsDatabase(updateExisting = false, testMode = false) {
356        console.log('=== НАЧАЛО СБОРА БАЗЫ КОНТАКТОВ ===');
357        console.log('Режим:', updateExisting ? 'Обновление существующей базы' : 'Полный сбор');
358        console.log('Тестовый режим:', testMode ? 'ДА (только 1 контакт)' : 'НЕТ');
359        
360        // Загружаем существующую базу
361        const db = await loadContactsDatabase();
362        const initialCount = Object.keys(db.contacts).length;
363        console.log('Начальное количество контактов в базе:', initialCount);
364        
365        // Трекер обработанных ID в этой сессии
366        const processedInSession = new Set();
367        
368        let newContacts = 0;
369        let updatedContacts = 0;
370        let scrollAttempts = 0;
371        let previousChatCount = 0;
372        let noNewChatsCount = 0;
373        const maxNoNewChatsAttempts = 3; // Если 3 раза подряд не появились новые чаты - останавливаемся
374        
375        while (!shouldStop) {
376            // Проверка на паузу
377            while (isPaused && !shouldStop) {
378                updateStatus('Пауза сбора базы', `Новых: ${newContacts}, Обновлено: ${updatedContacts}`);
379                await sleep(500);
380            }
381            
382            if (shouldStop) {
383                console.log('Сбор базы остановлен пользователем');
384                break;
385            }
386            
387            // Получаем текущие чаты
388            const currentChats = getAllChats();
389            console.log(`Попытка ${scrollAttempts + 1}: найдено ${currentChats.length} чатов в DOM`);
390            
391            // Обрабатываем каждый чат
392            for (const chatElement of currentChats) {
393                const chatId = extractChatId(chatElement);
394                
395                if (!chatId) {
396                    console.log('Не удалось извлечь ID чата, пропускаем');
397                    continue;
398                }
399                
400                // Пропускаем, если уже обработали в этой сессии
401                if (processedInSession.has(chatId)) {
402                    continue;
403                }
404                
405                // Проверяем, есть ли уже этот контакт в базе
406                const existsInDb = db.contacts[chatId] !== undefined;
407                
408                // В режиме обновления - пропускаем контакты, которых нет в базе
409                if (updateExisting && !existsInDb) {
410                    console.log(`Режим обновления: контакт ${chatId} не в базе, пропускаем`);
411                    continue;
412                }
413                
414                // В режиме полного сбора - пропускаем контакты, которые уже есть
415                if (!updateExisting && existsInDb) {
416                    console.log(`Режим полного сбора: контакт ${chatId} уже в базе, пропускаем`);
417                    continue;
418                }
419                
420                // Получаем данные чата
421                const chatData = getChatDataFromElement(chatElement);
422                
423                if (existsInDb) {
424                    // Обновляем существующий контакт
425                    db.contacts[chatId].name = chatData.name;
426                    db.contacts[chatId].lastMessageDate = chatData.lastMessageDate;
427                    updatedContacts++;
428                    console.log(`Обновлен контакт: ${chatData.name} (${chatId})`);
429                } else {
430                    // Добавляем новый контакт
431                    addContactToDatabase(db, chatId, chatData);
432                    newContacts++;
433                    console.log(`Добавлен контакт: ${chatData.name} (${chatId})`);
434                }
435                
436                // Отмечаем как обработанный в этой сессии
437                processedInSession.add(chatId);
438                
439                // Проверка тестового режима - останавливаемся после первого собранного контакта
440                if (testMode && (newContacts >= 1 || updatedContacts >= 1)) {
441                    console.log('Тестовый режим: собран 1 контакт, останавливаем сбор');
442                    shouldStop = true;
443                    break;
444                }
445            }
446            
447            // Обновляем статус
448            updateStatus(
449                'Сбор базы контактов...',
450                `Новых: ${newContacts} | Обновлено: ${updatedContacts} | Попыток скролла: ${scrollAttempts}`
451            );
452            
453            // Если тестовый режим и уже собрали контакт - выходим
454            if (testMode && (newContacts >= 1 || updatedContacts >= 1)) {
455                break;
456            }
457            
458            // Проверяем, появились ли новые чаты после скролла
459            if (currentChats.length === previousChatCount) {
460                noNewChatsCount++;
461                console.log(`Новые чаты не появились (${noNewChatsCount}/${maxNoNewChatsAttempts})`);
462                
463                if (noNewChatsCount >= maxNoNewChatsAttempts) {
464                    console.log('Достигнут конец списка чатов (новые чаты не подгружаются)');
465                    break;
466                }
467                
468                // Скроллим список чатов для подгрузки новых
469                console.log('Нет новых чатов, скроллим для подгрузки...');
470                await scrollChatList();
471                continue;
472            } else {
473                noNewChatsCount = 0; // Сбрасываем счетчик, если появились новые чаты
474            }
475            
476            previousChatCount = currentChats.length;
477            
478            // Скроллим для подгрузки следующей порции
479            const scrollSuccess = await scrollChatList();
480            if (!scrollSuccess) {
481                console.error('Ошибка скролла, останавливаем сбор');
482                break;
483            }
484            
485            scrollAttempts++;
486            
487            // Сохраняем базу каждые 50 контактов
488            if ((newContacts + updatedContacts) % 50 === 0 && (newContacts + updatedContacts) > 0) {
489                await saveContactsDatabase(db);
490                console.log('Промежуточное сохранение базы данных');
491            }
492        }
493        
494        // Финальное сохранение базы
495        await saveContactsDatabase(db);
496        
497        const finalCount = Object.keys(db.contacts).length;
498        console.log('=== СБОР БАЗЫ ЗАВЕРШЕН ===');
499        console.log('Всего контактов в базе:', finalCount);
500        console.log('Было:', initialCount, '| Стало:', finalCount, '| Добавлено:', newContacts, '| Обновлено:', updatedContacts);
501        
502        const statusMessage = testMode 
503            ? `Тестовый сбор завершен! Собрано: ${newContacts + updatedContacts} контакт`
504            : `Всего в базе: ${finalCount} контактов | Добавлено: ${newContacts} | Обновлено: ${updatedContacts}`;
505        
506        updateStatus('Сбор базы завершен!', statusMessage);
507        
508        // Сбрасываем флаги
509        shouldStop = false;
510        
511        return {
512            total: finalCount,
513            added: newContacts,
514            updated: updatedContacts
515        };
516    }
517
518    // Функция для создания модального окна
519    function createModal() {
520        const modalHTML = `
521            <div id="bulkSenderModal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 10000; justify-content: center; align-items: center;">
522                <div style="background: white; padding: 30px; border-radius: 12px; width: 600px; max-width: 90%; max-height: 90vh; overflow-y: auto; box-shadow: 0 4px 20px rgba(0,0,0,0.3);">
523                    <h2 style="margin: 0 0 20px 0; color: #333; font-size: 24px;">Массовая рассылка</h2>
524                    
525                    <!-- Выбор режима рассылки -->
526                    <div style="margin-bottom: 25px; padding: 20px; background: #f9f9f9; border-radius: 8px; border: 1px solid #e0e0e0;">
527                        <h3 style="margin: 0 0 15px 0; color: #555; font-size: 18px;">🎯 Режим рассылки</h3>
528                        <div style="display: flex; flex-direction: column; gap: 12px;">
529                            <label style="display: flex; align-items: center; cursor: pointer; padding: 12px; background: white; border-radius: 6px; border: 2px solid #ddd; transition: all 0.2s;">
530                                <input type="radio" name="sendingMode" value="database" checked style="width: 18px; height: 18px; margin-right: 12px; cursor: pointer;">
531                                <div>
532                                    <div style="color: #333; font-weight: 600; font-size: 14px;">📊 По базе данных</div>
533                                    <div style="color: #777; font-size: 12px; margin-top: 4px;">Отправка по собранной базе контактов с фильтром по дате</div>
534                                </div>
535                            </label>
536                            <label style="display: flex; align-items: center; cursor: pointer; padding: 12px; background: white; border-radius: 6px; border: 2px solid #ddd; transition: all 0.2s;">
537                                <input type="radio" name="sendingMode" value="visible" style="width: 18px; height: 18px; margin-right: 12px; cursor: pointer;">
538                                <div>
539                                    <div style="color: #333; font-weight: 600; font-size: 14px;">👁️ По видимым чатам</div>
540                                    <div style="color: #777; font-size: 12px; margin-top: 4px;">Отправка только по чатам, видимым в списке (с учетом фильтров)</div>
541                                </div>
542                            </label>
543                        </div>
544                    </div>
545                    
546                    <!-- Блок управления базой данных -->
547                    <div style="margin-bottom: 25px; padding: 20px; background: #f0f8ff; border-radius: 8px; border: 1px solid #d0e8ff;">
548                        <h3 style="margin: 0 0 15px 0; color: #0066cc; font-size: 18px;">📊 Управление базой контактов</h3>
549                        
550                        <div id="dbStatsBlock" style="margin-bottom: 15px; padding: 12px; background: white; border-radius: 6px; font-size: 13px; color: #333;">
551                            Загрузка статистики...
552                        </div>
553                        
554                        <div style="display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 15px;">
555                            <button id="collectDbButton" style="flex: 1; min-width: 140px; padding: 10px 16px; background: #4caf50; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 600;">📥 Собрать базу</button>
556                            <button id="updateDbButton" style="flex: 1; min-width: 140px; padding: 10px 16px; background: #2196f3; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 600;">🔄 Обновить базу</button>
557                            <button id="viewDbButton" style="flex: 1; min-width: 140px; padding: 10px 16px; background: #ff9800; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 600;">👁️ Просмотр базы</button>
558                            <button id="emergencyStopButton" style="flex: 1; min-width: 140px; padding: 10px 16px; background: #d32f2f; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 600;">🚨 Экстренная остановка</button>
559                            <button id="pauseDbButton" style="display: none; flex: 1; min-width: 140px; padding: 10px 16px; background: #ff9800; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 600;">⏸️ Пауза</button>
560                            <button id="stopDbButton" style="display: none; flex: 1; min-width: 140px; padding: 10px 16px; background: #f44336; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 600;">⏹️ Стоп</button>
561                        </div>
562                        
563                        <div style="display: flex; align-items: center; gap: 10px;">
564                            <input type="checkbox" id="testModeCollect" style="width: 16px; height: 16px; cursor: pointer;">
565                            <label for="testModeCollect" style="color: #555; font-size: 13px; cursor: pointer;">Тестовый режим (собрать только 1 контакт)</label>
566                        </div>
567                    </div>
568                    
569                    <div style="margin-bottom: 20px;">
570                        <label style="display: block; margin-bottom: 8px; color: #555; font-weight: 600;">Текст сообщения:</label>
571                        <textarea id="messageText" style="width: 100%; height: 120px; padding: 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; font-family: inherit; resize: vertical;" placeholder="Введите текст сообщения..."></textarea>
572                    </div>
573                    
574                    <div style="margin-bottom: 25px;">
575                        <label style="display: block; margin-bottom: 8px; color: #555; font-weight: 600;">Дата последнего сообщения (до какой даты отправляли):</label>
576                        <input type="date" id="filterDate" style="width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px;">
577                        <small style="color: #777; display: block; margin-top: 5px;">Будут обработаны чаты с датой последнего сообщения до указанной включительно</small>
578                    </div>
579                    
580                    <div style="margin-bottom: 20px;">
581                        <label style="display: flex; align-items: center; cursor: pointer;">
582                            <input type="checkbox" id="testMode" checked style="width: 18px; height: 18px; margin-right: 10px; cursor: pointer;">
583                            <span style="color: #555; font-weight: 600;">Тестовый режим (отправить только 1 сообщение)</span>
584                        </label>
585                        <small style="color: #ff6600; display: block; margin-top: 5px; margin-left: 28px;">⚠️ Рекомендуется для первого запуска</small>
586                    </div>
587                    
588                    <div id="statusBlock" style="display: none; margin-bottom: 20px; padding: 15px; background: #f0f8ff; border-radius: 6px; border-left: 4px solid #0066cc;">
589                        <div style="font-weight: 600; color: #0066cc; margin-bottom: 5px;">Статус:</div>
590                        <div id="statusText" style="color: #333;">Готов к запуску</div>
591                        <div id="progressText" style="color: #666; margin-top: 5px; font-size: 13px;"></div>
592                    </div>
593                    
594                    <div style="display: flex; gap: 10px; justify-content: flex-end;">
595                        <button id="startButton" style="padding: 12px 24px; background: #0066cc; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600;">Запустить</button>
596                        <button id="pauseButton" style="display: none; padding: 12px 24px; background: #ff9800; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600;">Пауза</button>
597                        <button id="stopButton" style="display: none; padding: 12px 24px; background: #f44336; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600;">Стоп</button>
598                        <button id="closeButton" style="padding: 12px 24px; background: #999; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600;">Закрыть</button>
599                    </div>
600                </div>
601            </div>
602        `;
603        
604        document.body.insertAdjacentHTML('beforeend', modalHTML);
605        console.log('Модальное окно создано');
606        
607        // Обработчики событий
608        document.getElementById('closeButton').addEventListener('click', closeModal);
609        document.getElementById('startButton').addEventListener('click', () => {
610            console.log('!!! КЛИК ПО КНОПКЕ ЗАПУСТИТЬ !!!');
611            startBulkSending();
612        });
613        document.getElementById('pauseButton').addEventListener('click', togglePause);
614        document.getElementById('stopButton').addEventListener('click', stopBulkSending);
615        
616        // Обработчики для кнопок управления базой
617        document.getElementById('collectDbButton').addEventListener('click', async () => {
618            const testModeCollect = document.getElementById('testModeCollect').checked;
619            const confirmMessage = testModeCollect 
620                ? 'Начать тестовый сбор базы контактов (только 1 контакт)?'
621                : 'Начать полный сбор базы контактов? Это может занять некоторое время.';
622            
623            if (confirm(confirmMessage)) {
624                // Показываем кнопки паузы и остановки
625                document.getElementById('collectDbButton').style.display = 'none';
626                document.getElementById('updateDbButton').style.display = 'none';
627                document.getElementById('pauseDbButton').style.display = 'inline-block';
628                document.getElementById('stopDbButton').style.display = 'inline-block';
629                
630                isRunning = true;
631                shouldStop = false;
632                isPaused = false;
633                await collectContactsDatabase(false, testModeCollect);
634                isRunning = false;
635                
636                // Скрываем кнопки паузы и остановки
637                document.getElementById('collectDbButton').style.display = 'inline-block';
638                document.getElementById('updateDbButton').style.display = 'inline-block';
639                document.getElementById('pauseDbButton').style.display = 'none';
640                document.getElementById('stopDbButton').style.display = 'none';
641                
642                await updateDatabaseStats();
643            }
644        });
645        
646        document.getElementById('updateDbButton').addEventListener('click', async () => {
647            const testModeCollect = document.getElementById('testModeCollect').checked;
648            const confirmMessage = testModeCollect 
649                ? 'Обновить базу контактов в тестовом режиме (только 1 контакт)?'
650                : 'Обновить существующую базу контактов?';
651            
652            if (confirm(confirmMessage)) {
653                // Показываем кнопки паузы и остановки
654                document.getElementById('collectDbButton').style.display = 'none';
655                document.getElementById('updateDbButton').style.display = 'none';
656                document.getElementById('pauseDbButton').style.display = 'inline-block';
657                document.getElementById('stopDbButton').style.display = 'inline-block';
658                
659                isRunning = true;
660                shouldStop = false;
661                isPaused = false;
662                await collectContactsDatabase(true, testModeCollect);
663                isRunning = false;
664                
665                // Скрываем кнопки паузы и остановки
666                document.getElementById('collectDbButton').style.display = 'inline-block';
667                document.getElementById('updateDbButton').style.display = 'inline-block';
668                document.getElementById('pauseDbButton').style.display = 'none';
669                document.getElementById('stopDbButton').style.display = 'none';
670                
671                await updateDatabaseStats();
672            }
673        });
674        
675        // Обработчик паузы для сбора базы
676        document.getElementById('pauseDbButton').addEventListener('click', () => {
677            isPaused = !isPaused;
678            const pauseDbButton = document.getElementById('pauseDbButton');
679            
680            if (isPaused) {
681                pauseDbButton.textContent = '▶️ Продолжить';
682                pauseDbButton.style.background = '#4caf50';
683                console.log('Сбор базы приостановлен');
684            } else {
685                pauseDbButton.textContent = '⏸️ Пауза';
686                pauseDbButton.style.background = '#ff9800';
687                console.log('Сбор базы возобновлен');
688            }
689        });
690        
691        // Обработчик остановки для сбора базы
692        document.getElementById('stopDbButton').addEventListener('click', () => {
693            if (confirm('Вы уверены, что хотите остановить сбор базы?')) {
694                shouldStop = true;
695                isPaused = false;
696                console.log('Запрошена остановка сбора базы');
697            }
698        });
699        
700        // Обработчик для кнопки просмотра базы
701        document.getElementById('viewDbButton').addEventListener('click', openViewDatabaseModal);
702        
703        // Обработчик для кнопки экстренной остановки
704        document.getElementById('emergencyStopButton').addEventListener('click', async () => {
705            if (confirm('Экстренная остановка очистит все активные процессы рассылки. Продолжить?')) {
706                await clearSendingState();
707                shouldStop = true;
708                isPaused = false;
709                isRunning = false;
710                alert('Процесс остановлен! Все активные рассылки прекращены.');
711                console.log('Экстренная остановка выполнена');
712                
713                // Обновляем интерфейс
714                updateStatus('Остановлено', 'Процесс прерван экстренной остановкой');
715            }
716        });
717        
718        // Загружаем статистику при создании модального окна
719        updateDatabaseStats();
720    }
721    
722    // Функция для обновления статистики базы данных в интерфейсе
723    async function updateDatabaseStats() {
724        const db = await loadContactsDatabase();
725        const stats = getDatabaseStats(db);
726        const statsBlock = document.getElementById('dbStatsBlock');
727        
728        if (statsBlock) {
729            const lastUpdateText = stats.lastUpdate 
730                ? new Date(stats.lastUpdate).toLocaleString('ru-RU')
731                : 'Никогда';
732            
733            statsBlock.innerHTML = `
734                <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px;">
735                    <div><strong>Всего контактов:</strong> ${stats.total}</div>
736                    <div><strong>С датами:</strong> ${stats.withDates}</div>
737                    <div><strong>Отправлено:</strong> ${stats.sent}</div>
738                    <div><strong>Не отправлено:</strong> ${stats.notSent}</div>
739                </div>
740                <div style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #e0e0e0; font-size: 12px; color: #999;">
741                    Последнее обновление: ${lastUpdateText}
742                </div>
743            `;
744        }
745    }
746
747    // Функция для открытия модального окна
748    function openModal() {
749        const modal = document.getElementById('bulkSenderModal');
750        if (modal) {
751            modal.style.display = 'flex';
752            console.log('Модальное окно открыто');
753        }
754    }
755
756    // Функция для закрытия модального окна
757    function closeModal() {
758        const modal = document.getElementById('bulkSenderModal');
759        if (modal && !isRunning) {
760            modal.style.display = 'none';
761            console.log('Модальное окно закрыто');
762        } else if (isRunning) {
763            alert('Дождитесь завершения рассылки или нажмите "Стоп"');
764        }
765    }
766
767    // Функция для обновления статуса
768    function updateStatus(status, progress = '') {
769        const statusBlock = document.getElementById('statusBlock');
770        const statusText = document.getElementById('statusText');
771        const progressText = document.getElementById('progressText');
772        
773        if (statusBlock && statusText) {
774            statusBlock.style.display = 'block';
775            statusText.textContent = status;
776            progressText.textContent = progress;
777            console.log('Статус:', status, progress);
778        }
779    }
780
781    // Функция для парсинга даты из текста
782    function parseDate(dateText) {
783        console.log('Парсинг даты:', dateText);
784        
785        if (!dateText) {
786            console.log('Пустой текст даты');
787            return null;
788        }
789        
790        const trimmed = dateText.trim();
791        
792        // Если формат "10:29" (время) - берем текущую дату
793        if (trimmed.includes(':')) {
794            const today = new Date();
795            console.log('Распознано время, используем текущую дату:', today);
796            return today;
797        }
798        
799        // Если формат "23.01" (день.месяц) или "31.12.2025" (день.месяц.год)
800        if (trimmed.includes('.')) {
801            const parts = trimmed.split('.');
802            if (parts.length === 2) {
803                // Формат "23.01"
804                const day = parseInt(parts[0]);
805                const month = parseInt(parts[1]) - 1; // Месяцы с 0
806                const currentYear = new Date().getFullYear();
807                
808                if (!isNaN(day) && !isNaN(month)) {
809                    const date = new Date(currentYear, month, day);
810                    console.log('Распознанная дата (день.месяц):', date);
811                    return date;
812                }
813            } else if (parts.length === 3) {
814                // Формат "31.12.2025"
815                const day = parseInt(parts[0]);
816                const month = parseInt(parts[1]) - 1; // Месяцы с 0
817                const year = parseInt(parts[2]);
818                
819                if (!isNaN(day) && !isNaN(month) && !isNaN(year)) {
820                    const date = new Date(year, month, day);
821                    console.log('Распознанная дата (день.месяц.год):', date);
822                    return date;
823                }
824            }
825        }
826        
827        // Если дата в формате "24 января" или "21 июня 2025"
828        const months = {
829            'января': 0, 'февраля': 1, 'марта': 2, 'апреля': 3,
830            'мая': 4, 'июня': 5, 'июля': 6, 'августа': 7,
831            'сентября': 8, 'октября': 9, 'ноября': 10, 'декабря': 11
832        };
833        
834        const parts = trimmed.split(' ');
835        
836        // Формат "21 июня 2025"
837        if (parts.length === 3) {
838            const day = parseInt(parts[0]);
839            const month = months[parts[1].toLowerCase()];
840            const year = parseInt(parts[2]);
841            
842            if (!isNaN(day) && !isNaN(month) && !isNaN(year)) {
843                const date = new Date(year, month, day);
844                console.log('Распознанная дата (день месяц год):', date);
845                return date;
846            }
847        }
848        
849        // Формат "24 января"
850        if (parts.length === 2) {
851            const day = parseInt(parts[0]);
852            const month = months[parts[1].toLowerCase()];
853            
854            if (!isNaN(day) && !isNaN(month)) {
855                const currentYear = new Date().getFullYear();
856                const date = new Date(currentYear, month, day);
857                console.log('Распознанная дата (день месяц):', date);
858                return date;
859            }
860        }
861        
862        console.log('Не удалось распознать дату');
863        return null;
864    }
865
866    // Функция для получения всех чатов
867    function getAllChats() {
868        // Сначала проверяем, есть ли результаты поиска (класс index_chat_EHlBq)
869        const searchChats = document.querySelectorAll('.index_chat_EHlBq');
870        
871        if (searchChats.length > 0) {
872            // Если есть результаты поиска, используем их
873            console.log('Найдены результаты поиска:', searchChats.length);
874            return Array.from(searchChats);
875        }
876        
877        // Если поиска нет, используем обычные чаты
878        const chats = document.querySelectorAll('.index_chat_4fr82');
879        // Фильтруем только видимые чаты (учитываем фильтр поиска)
880        const visibleChats = Array.from(chats).filter(chat => {
881            const style = window.getComputedStyle(chat);
882            // Проверяем, что чат не скрыт (убрали проверку высоты, т.к. чаты виртуализированы)
883            return style.display !== 'none' && 
884                   style.visibility !== 'hidden' && 
885                   style.opacity !== '0' &&
886                   chat.offsetParent !== null; // Проверяем offsetParent для фильтрации
887        });
888        console.log('Найдено чатов:', visibleChats.length, '(всего в DOM:', chats.length, ')');
889        return visibleChats;
890    }
891
892    // Функция для клика по чату
893    async function clickChat(chat) {
894        // Пробуем оба селектора для имени чата (обычный и поисковый)
895        const chatName = chat.querySelector('.index_chatTitle_TiXTq')?.textContent?.trim() || 
896                        chat.querySelector('.index_chatTitle_x9txX')?.textContent || 
897                        'Неизвестно';
898        console.log('Клик по чату:', chatName);
899        chat.click();
900        await sleep(1500); // Уменьшаем задержку загрузки чата
901    }
902    
903    // Функция для получения даты последнего сообщения в открытом чате
904    function getLastMessageDate() {
905        const dateElements = document.querySelectorAll('.om_1_n4');
906        if (dateElements.length > 0) {
907            // Берем последнюю дату (последнее сообщение)
908            const lastDateElement = dateElements[dateElements.length - 1];
909            const dateText = lastDateElement.textContent.trim();
910            console.log('Дата последнего сообщения:', dateText);
911            return parseDate(dateText);
912        }
913        console.log('Элемент даты не найден');
914        return null;
915    }
916
917    // Функция для отправки сообщения
918    async function sendMessage(messageText) {
919        console.log('Отправка сообщения:', messageText);
920        
921        // Находим текстовое поле
922        const textarea = document.querySelector('.om_17_a4');
923        if (!textarea) {
924            console.error('Текстовое поле не найдено');
925            return false;
926        }
927        
928        // Вставляем текст
929        textarea.value = messageText;
930        textarea.dispatchEvent(new Event('input', { bubbles: true }));
931        console.log('Текст вставлен в поле');
932        
933        // Задержка перед отправкой
934        await sleep(300);
935        
936        // Находим кнопку отправки (вторая кнопка с классом om_17_a8)
937        const sendButtons = document.querySelectorAll('.om_17_a8');
938        if (sendButtons.length < 2) {
939            console.error('Кнопка отправки не найдена');
940            return false;
941        }
942        
943        // Кликаем на вторую кнопку (первая - это прикрепление файла)
944        const sendButton = sendButtons[1];
945        sendButton.click();
946        console.log('Сообщение отправлено (клик выполнен)');
947        
948        // Задержка после отправки
949        await sleep(300);
950        
951        return true;
952    }
953
954    // Функция задержки
955    function sleep(ms) {
956        return new Promise(resolve => setTimeout(resolve, ms));
957    }
958    
959    // Функция переключения паузы
960    function togglePause() {
961        isPaused = !isPaused;
962        const pauseButton = document.getElementById('pauseButton');
963        
964        if (isPaused) {
965            pauseButton.textContent = '▶️ Продолжить';
966            pauseButton.style.background = '#4caf50';
967            console.log('Рассылка приостановлена');
968        } else {
969            pauseButton.textContent = '⏸️ Пауза';
970            pauseButton.style.background = '#ff9800';
971            console.log('Рассылка возобновлена');
972        }
973    }
974    
975    // Функция остановки рассылки
976    function stopBulkSending() {
977        if (confirm('Вы уверены, что хотите остановить рассылку?')) {
978            shouldStop = true;
979            isPaused = false;
980            isRunning = false;
981            console.log('Запрошена остановка рассылки');
982            
983            // Очищаем состояние рассылки
984            clearSendingState();
985            
986            // Возвращаем интерфейс в исходное состояние
987            const startButton = document.getElementById('startButton');
988            const testModeCheckbox = document.getElementById('testMode');
989            
990            if (startButton) {
991                startButton.style.display = 'inline-block';
992                startButton.style.background = '#0066cc';
993                startButton.textContent = 'Запустить';
994                startButton.disabled = false;
995            }
996            
997            document.getElementById('pauseButton').style.display = 'none';
998            document.getElementById('stopButton').style.display = 'none';
999            document.getElementById('messageText').disabled = false;
1000            document.getElementById('filterDate').disabled = false;
1001            if (testModeCheckbox) testModeCheckbox.disabled = false;
1002            document.querySelectorAll('input[name="sendingMode"]').forEach(radio => radio.disabled = false);
1003            
1004            updateStatus('Остановлено', 'Рассылка остановлена пользователем');
1005        }
1006    }
1007
1008    // Функция рассылки по видимым чатам
1009    async function startBulkSendingByVisibleChats(messageText, filterDateInput, isTestMode) {
1010        console.log('=== РАССЫЛКА ПО ВИДИМЫМ ЧАТАМ ===');
1011        
1012        // Меняем интерфейс сразу
1013        isRunning = true;
1014        isPaused = false;
1015        shouldStop = false;
1016        
1017        const testModeCheckbox = document.getElementById('testMode');
1018        const startButton = document.getElementById('startButton');
1019        
1020        startButton.style.background = '#4caf50';
1021        startButton.textContent = '⏳ Запуск...';
1022        startButton.disabled = true;
1023        
1024        document.getElementById('pauseButton').style.display = 'inline-block';
1025        document.getElementById('stopButton').style.display = 'inline-block';
1026        document.getElementById('messageText').disabled = true;
1027        document.getElementById('filterDate').disabled = true;
1028        if (testModeCheckbox) testModeCheckbox.disabled = true;
1029        document.querySelectorAll('input[name="sendingMode"]').forEach(radio => radio.disabled = true);
1030        
1031        updateStatus('Получение списка чатов...', 'Подготовка к рассылке...');
1032        
1033        // Получаем все видимые чаты
1034        const chats = getAllChats();
1035        
1036        if (chats.length === 0) {
1037            alert('Не найдено видимых чатов. Убедитесь, что вы находитесь на странице мессенджера.');
1038            isRunning = false;
1039            startButton.style.display = 'inline-block';
1040            startButton.style.background = '#0066cc';
1041            startButton.textContent = 'Запустить';
1042            startButton.disabled = false;
1043            document.getElementById('pauseButton').style.display = 'none';
1044            document.getElementById('stopButton').style.display = 'none';
1045            document.getElementById('messageText').disabled = false;
1046            document.getElementById('filterDate').disabled = false;
1047            if (testModeCheckbox) testModeCheckbox.disabled = false;
1048            document.querySelectorAll('input[name="sendingMode"]').forEach(radio => radio.disabled = false);
1049            return;
1050        }
1051        
1052        const filterDate = new Date(filterDateInput);
1053        filterDate.setHours(23, 59, 59, 999);
1054        
1055        console.log('Найдено видимых чатов:', chats.length);
1056        console.log('Фильтр по дате:', filterDate);
1057        console.log('Тестовый режим:', isTestMode ? 'ДА (только 1 сообщение)' : 'НЕТ');
1058        
1059        updateStatus('Начало рассылки...', `Всего чатов: ${chats.length}`);
1060        startButton.style.display = 'none';
1061        
1062        // Начинаем рассылку
1063        let processed = 0;
1064        let sent = 0;
1065        let skipped = 0;
1066        
1067        for (const chat of chats) {
1068            // Проверка на паузу
1069            while (isPaused && !shouldStop) {
1070                updateStatus('Пауза', `Обработано: ${processed}/${chats.length} | Отправлено: ${sent} | Пропущено: ${skipped}`);
1071                await sleep(500);
1072            }
1073            
1074            if (shouldStop) {
1075                console.log('Рассылка остановлена пользователем');
1076                break;
1077            }
1078            
1079            // Проверка тестового режима
1080            if (isTestMode && sent >= 1) {
1081                console.log('Тестовый режим: достигнут лимит в 1 сообщение');
1082                break;
1083            }
1084            
1085            const chatName = chat.querySelector('.index_chatTitle_TiXTq')?.textContent?.trim() || 
1086                            chat.querySelector('.index_chatTitle_x9txX')?.textContent?.trim() || 
1087                            'Неизвестно';
1088            
1089            console.log(`[${processed + 1}/${chats.length}] Обрабатываем чат: ${chatName}`);
1090            updateStatus(
1091                `Обработка: ${chatName}`,
1092                `Обработано: ${processed}/${chats.length} | Отправлено: ${sent} | Пропущено: ${skipped}`
1093            );
1094            
1095            // Кликаем по чату
1096            await clickChat(chat);
1097            
1098            // Получаем дату последнего сообщения
1099            const lastMessageDate = getLastMessageDate();
1100            console.log('Дата последнего сообщения:', lastMessageDate);
1101            
1102            if (!lastMessageDate) {
1103                console.log('Не удалось получить дату, пропускаем');
1104                skipped++;
1105                processed++;
1106                continue;
1107            }
1108            
1109            // Проверяем дату
1110            const shouldSend = lastMessageDate <= filterDate;
1111            console.log('Дата подходит?', shouldSend);
1112            
1113            if (!shouldSend) {
1114                console.log('⏭️ Дата не подходит, пропускаем');
1115                skipped++;
1116                processed++;
1117                continue;
1118            }
1119            
1120            // Отправляем сообщение
1121            const success = await sendMessage(messageText);
1122            
1123            if (success) {
1124                console.log('✅ Сообщение отправлено');
1125                sent++;
1126            } else {
1127                console.error('❌ Ошибка отправки');
1128                skipped++;
1129            }
1130            
1131            processed++;
1132            
1133            // Небольшая пауза между чатами
1134            await sleep(500);
1135        }
1136        
1137        // Завершение
1138        isRunning = false;
1139        isPaused = false;
1140        shouldStop = false;
1141        
1142        startButton.style.display = 'inline-block';
1143        startButton.style.background = '#0066cc';
1144        startButton.textContent = 'Запустить';
1145        startButton.disabled = false;
1146        document.getElementById('pauseButton').style.display = 'none';
1147        document.getElementById('stopButton').style.display = 'none';
1148        document.getElementById('messageText').disabled = false;
1149        document.getElementById('filterDate').disabled = false;
1150        if (testModeCheckbox) testModeCheckbox.disabled = false;
1151        document.querySelectorAll('input[name="sendingMode"]').forEach(radio => radio.disabled = false);
1152        
1153        updateStatus(
1154            'Рассылка завершена!',
1155            `Обработано: ${processed} | Отправлено: ${sent} | Пропущено: ${skipped}`
1156        );
1157        
1158        console.log('=== РАССЫЛКА ЗАВЕРШЕНА ===');
1159        console.log('Обработано:', processed);
1160        console.log('Отправлено:', sent);
1161        console.log('Пропущено:', skipped);
1162    }
1163    
1164    // Функция рассылки по базе данных
1165    async function startBulkSendingByDatabase(messageText, filterDateInput, isTestMode) {
1166        console.log('=== РАССЫЛКА ПО БАЗЕ ДАННЫХ ===');
1167        
1168        // Меняем интерфейс сразу
1169        isRunning = true;
1170        isPaused = false;
1171        shouldStop = false;
1172        
1173        const testModeCheckbox = document.getElementById('testMode');
1174        const startButton = document.getElementById('startButton');
1175        
1176        startButton.style.background = '#4caf50';
1177        startButton.textContent = '⏳ Запуск...';
1178        startButton.disabled = true;
1179        
1180        document.getElementById('pauseButton').style.display = 'inline-block';
1181        document.getElementById('stopButton').style.display = 'inline-block';
1182        document.getElementById('messageText').disabled = true;
1183        document.getElementById('filterDate').disabled = true;
1184        if (testModeCheckbox) testModeCheckbox.disabled = true;
1185        document.querySelectorAll('input[name="sendingMode"]').forEach(radio => radio.disabled = true);
1186        
1187        updateStatus('Загрузка базы данных...', 'Подготовка к рассылке...');
1188        
1189        // Загружаем базу данных
1190        const db = await loadContactsDatabase();
1191        const filterDate = new Date(filterDateInput);
1192        filterDate.setHours(23, 59, 59, 999);
1193        
1194        // Фильтруем контакты по дате
1195        const contactsToSend = Object.values(db.contacts).filter(contact => {
1196            if (!contact.lastMessageDate) return false;
1197            const contactDate = new Date(contact.lastMessageDate);
1198            return contactDate <= filterDate;
1199        });
1200        
1201        if (contactsToSend.length === 0) {
1202            alert('Не найдено контактов для рассылки с указанной датой.');
1203            isRunning = false;
1204            startButton.style.display = 'inline-block';
1205            startButton.style.background = '#0066cc';
1206            startButton.textContent = 'Запустить';
1207            startButton.disabled = false;
1208            document.getElementById('pauseButton').style.display = 'none';
1209            document.getElementById('stopButton').style.display = 'none';
1210            document.getElementById('messageText').disabled = false;
1211            document.getElementById('filterDate').disabled = false;
1212            if (testModeCheckbox) testModeCheckbox.disabled = false;
1213            document.querySelectorAll('input[name="sendingMode"]').forEach(radio => radio.disabled = false);
1214            return;
1215        }
1216        
1217        console.log('Найдено контактов для рассылки:', contactsToSend.length);
1218        console.log('Фильтр по дате:', filterDate);
1219        console.log('Тестовый режим:', isTestMode ? 'ДА (только 1 сообщение)' : 'НЕТ');
1220        
1221        updateStatus('Начало рассылки...', `Всего контактов: ${contactsToSend.length}`);
1222        startButton.style.display = 'none';
1223        
1224        // Начинаем рассылку
1225        let processed = 0;
1226        let sent = 0;
1227        let skipped = 0;
1228        
1229        for (const contact of contactsToSend) {
1230            // Проверка на паузу
1231            while (isPaused && !shouldStop) {
1232                updateStatus('Пауза', `Обработано: ${processed}/${contactsToSend.length} | Отправлено: ${sent} | Пропущено: ${skipped}`);
1233                await sleep(500);
1234            }
1235            
1236            if (shouldStop) {
1237                console.log('Рассылка остановлена пользователем');
1238                break;
1239            }
1240            
1241            // Проверка тестового режима
1242            if (isTestMode && sent >= 1) {
1243                console.log('Тестовый режим: достигнут лимит в 1 сообщение');
1244                break;
1245            }
1246            
1247            console.log(`[${processed + 1}/${contactsToSend.length}] Обрабатываем контакт: ${contact.name}`);
1248            updateStatus(
1249                `Обработка: ${contact.name}`,
1250                `Обработано: ${processed}/${contactsToSend.length} | Отправлено: ${sent} | Пропущено: ${skipped}`
1251            );
1252            
1253            // Находим чат в списке по ID
1254            const chatElement = findChatInListById(contact.id);
1255            
1256            if (!chatElement) {
1257                console.log('Чат не найден в списке, пропускаем');
1258                skipped++;
1259                processed++;
1260                continue;
1261            }
1262            
1263            // Кликаем по чату
1264            await clickChat(chatElement);
1265            
1266            // Отправляем сообщение
1267            const success = await sendMessage(messageText);
1268            
1269            if (success) {
1270                console.log('✅ Сообщение отправлено');
1271                sent++;
1272                
1273                // Обновляем дату отправки в базе
1274                contact.lastSentDate = new Date().toISOString();
1275                contact.messageCount = (contact.messageCount || 0) + 1;
1276            } else {
1277                console.error('❌ Ошибка отправки');
1278                skipped++;
1279            }
1280            
1281            processed++;
1282            
1283            // Небольшая пауза между чатами
1284            await sleep(500);
1285        }
1286        
1287        // Сохраняем обновленную базу
1288        await saveContactsDatabase(db);
1289        
1290        // Завершение
1291        isRunning = false;
1292        isPaused = false;
1293        shouldStop = false;
1294        
1295        startButton.style.display = 'inline-block';
1296        startButton.style.background = '#0066cc';
1297        startButton.textContent = 'Запустить';
1298        startButton.disabled = false;
1299        document.getElementById('pauseButton').style.display = 'none';
1300        document.getElementById('stopButton').style.display = 'none';
1301        document.getElementById('messageText').disabled = false;
1302        document.getElementById('filterDate').disabled = false;
1303        if (testModeCheckbox) testModeCheckbox.disabled = false;
1304        document.querySelectorAll('input[name="sendingMode"]').forEach(radio => radio.disabled = false);
1305        
1306        updateStatus(
1307            'Рассылка завершена!',
1308            `Обработано: ${processed} | Отправлено: ${sent} | Пропущено: ${skipped}`
1309        );
1310        
1311        console.log('=== РАССЫЛКА ЗАВЕРШЕНА ===');
1312        console.log('Обработано:', processed);
1313        console.log('Отправлено:', sent);
1314        console.log('Пропущено:', skipped);
1315    }
1316    
1317    // Функция для поиска чата в списке по ID
1318    function findChatInListById(chatId) {
1319        console.log('Поиск чата в списке по ID:', chatId);
1320        
1321        const allChats = document.querySelectorAll('.index_chat_4fr82');
1322        
1323        for (const chat of allChats) {
1324            const currentChatId = extractChatId(chat);
1325            if (currentChatId === chatId) {
1326                console.log('Чат найден в списке');
1327                return chat;
1328            }
1329        }
1330        
1331        console.log('Чат не найден в списке');
1332        return null;
1333    }
1334
1335    // Функция для создания плавающей кнопки
1336    function createFloatingButton() {
1337        // Проверяем, не создана ли уже кнопка
1338        if (document.getElementById('bulkSenderFloatingBtn')) {
1339            return;
1340        }
1341        
1342        const button = document.createElement('button');
1343        button.id = 'bulkSenderFloatingBtn';
1344        button.textContent = '📧 Рассылка';
1345        button.style.cssText = `
1346            position: fixed;
1347            bottom: 20px;
1348            right: 20px;
1349            padding: 15px 25px;
1350            background: #0066cc;
1351            color: white;
1352            border: none;
1353            border-radius: 50px;
1354            cursor: pointer;
1355            font-size: 16px;
1356            font-weight: 600;
1357            box-shadow: 0 4px 12px rgba(0, 102, 204, 0.4);
1358            z-index: 9999;
1359            transition: all 0.3s;
1360        `;
1361        
1362        button.addEventListener('mouseenter', () => {
1363            button.style.background = '#0052a3';
1364            button.style.transform = 'scale(1.05)';
1365            button.style.boxShadow = '0 6px 16px rgba(0, 102, 204, 0.6)';
1366        });
1367        
1368        button.addEventListener('mouseleave', () => {
1369            button.style.background = '#0066cc';
1370            button.style.transform = 'scale(1)';
1371            button.style.boxShadow = '0 4px 12px rgba(0, 102, 204, 0.4)';
1372        });
1373        
1374        button.addEventListener('click', openModal);
1375        
1376        document.body.appendChild(button);
1377        console.log('Плавающая кнопка создана');
1378    }
1379
1380    // Инициализация
1381    async function init() {
1382        console.log('Инициализация расширения');
1383        
1384        // Проверяем, что мы на странице мессенджера с покупателями
1385        if (!window.location.href.includes('group=customers_v2')) {
1386            console.log('Не на странице чатов с покупателями, расширение не активно');
1387            return;
1388        }
1389        
1390        console.log('Страница подходит, создаем интерфейс');
1391        
1392        // Создаем плавающую кнопку и модальное окно
1393        createFloatingButton();
1394        createModal();
1395        
1396        // Ждем загрузки списка чатов и создаем модальное окно просмотра базы
1397        setTimeout(() => {
1398            createViewDatabaseModal();
1399        }, 2000);
1400        
1401        console.log('Интерфейс создан');
1402    }
1403
1404    // Запуск при загрузке страницы
1405    if (document.readyState === 'loading') {
1406        document.addEventListener('DOMContentLoaded', init);
1407    } else {
1408        init();
1409    }
1410})();