Ozon BASE Messenger Bulk Sender

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

Size

90.0 KB

Version

1.1.111

Created

Jan 27, 2026

Updated

about 1 month ago

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