Ozon BASE Messenger Bulk Sender

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

Size

169.1 KB

Version

1.1.199

Created

Feb 8, 2026

Updated

29 days ago

1// ==UserScript==
2// @name		Ozon BASE Messenger Bulk Sender
3// @description		Массовая рассылка сообщений покупателям в Ozon Messenger
4// @version		1.1.199
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 isPaused = false;
17    let shouldStop = false;
18
19    // ============= ИНИЦИАЛИЗАЦИЯ INDEXEDDB =============
20
21    let db = null;
22    const DB_NAME = 'OzonMessengerDB';
23    const DB_VERSION = 2;
24    const STORE_NAME = 'storage';
25
26    // Инициализация IndexedDB
27    async function initIndexedDB() {
28        return new Promise((resolve, reject) => {
29            try {
30                console.log('Попытка открытия IndexedDB:', DB_NAME);
31                
32                // Проверяем доступность IndexedDB
33                if (!window.indexedDB) {
34                    const error = new Error('IndexedDB не поддерживается в этом браузере');
35                    console.error(error.message);
36                    reject(error);
37                    return;
38                }
39                
40                const request = indexedDB.open(DB_NAME, DB_VERSION);
41                
42                request.onerror = () => {
43                    const errorMsg = request.error ? 
44                        `${request.error.name}: ${request.error.message}` : 
45                        'Неизвестная ошибка IndexedDB';
46                    console.error('Ошибка открытия IndexedDB:', errorMsg);
47                    console.error('Полный объект ошибки:', request.error);
48                    reject(new Error(errorMsg));
49                };
50                
51                request.onsuccess = () => {
52                    db = request.result;
53                    console.log('✅ IndexedDB успешно инициализирована');
54                    
55                    // Обработчик ошибок базы данных
56                    db.onerror = (event) => {
57                        console.error('Ошибка базы данных:', event);
58                    };
59                    
60                    // Обработчик закрытия базы данных
61                    db.onclose = () => {
62                        console.warn('База данных была закрыта');
63                        db = null;
64                    };
65                    
66                    resolve(db);
67                };
68                
69                request.onupgradeneeded = (event) => {
70                    console.log('Обновление структуры IndexedDB...');
71                    const database = event.target.result;
72                    
73                    // Создаем хранилище, если его нет
74                    if (!database.objectStoreNames.contains(STORE_NAME)) {
75                        database.createObjectStore(STORE_NAME, { keyPath: 'key' });
76                        console.log('✅ Создано хранилище IndexedDB:', STORE_NAME);
77                    }
78                };
79                
80                request.onblocked = () => {
81                    console.warn('⚠️ IndexedDB заблокирована. Закройте другие вкладки с этим сайтом.');
82                    reject(new Error('IndexedDB заблокирована другой вкладкой'));
83                };
84            } catch (error) {
85                console.error('Критическая ошибка при инициализации IndexedDB:', error);
86                console.error('error.name:', error.name);
87                console.error('error.message:', error.message);
88                console.error('error.stack:', error.stack);
89                reject(error);
90            }
91        });
92    }
93
94    // Сохранение значения в IndexedDB
95    async function setDBValue(key, value) {
96        if (!db) {
97            console.log('База данных не инициализирована, инициализируем...');
98            await initIndexedDB();
99        }
100        
101        return new Promise((resolve, reject) => {
102            try {
103                console.log('Сохранение в IndexedDB, ключ:', key, 'размер данных:', JSON.stringify(value).length, 'байт');
104                const transaction = db.transaction([STORE_NAME], 'readwrite');
105                
106                transaction.onerror = (event) => {
107                    console.error('Ошибка транзакции при сохранении:', event);
108                    console.error('transaction.error:', transaction.error);
109                    reject(new Error(`Transaction error: ${transaction.error?.name} - ${transaction.error?.message}`));
110                };
111                
112                transaction.oncomplete = () => {
113                    console.log('Транзакция сохранения завершена успешно');
114                };
115                
116                const objectStore = transaction.objectStore(STORE_NAME);
117                const request = objectStore.put({ key: key, value: value });
118                
119                request.onsuccess = () => {
120                    console.log('✅ Значение сохранено в IndexedDB:', key);
121                    resolve();
122                };
123                
124                request.onerror = (event) => {
125                    console.error('Ошибка сохранения в IndexedDB:', event);
126                    console.error('request.error:', request.error);
127                    console.error('request.error.name:', request.error?.name);
128                    console.error('request.error.message:', request.error?.message);
129                    reject(new Error(`Put error: ${request.error?.name} - ${request.error?.message}`));
130                };
131            } catch (error) {
132                console.error('Ошибка при создании транзакции IndexedDB:', error);
133                console.error('error.name:', error.name);
134                console.error('error.message:', error.message);
135                console.error('error.stack:', error.stack);
136                reject(error);
137            }
138        });
139    }
140
141    // Получение значения из IndexedDB
142    async function getDBValue(key, defaultValue = null) {
143        if (!db) {
144            console.log('База данных не инициализирована, инициализируем...');
145            await initIndexedDB();
146        }
147        
148        return new Promise((resolve, reject) => {
149            try {
150                console.log('Чтение из IndexedDB, ключ:', key);
151                const transaction = db.transaction([STORE_NAME], 'readonly');
152                
153                transaction.onerror = (event) => {
154                    console.error('Ошибка транзакции при чтении:', event);
155                    console.error('transaction.error:', transaction.error);
156                    reject(new Error(`Transaction error: ${transaction.error?.name} - ${transaction.error?.message}`));
157                };
158                
159                const objectStore = transaction.objectStore(STORE_NAME);
160                const request = objectStore.get(key);
161                
162                request.onsuccess = () => {
163                    if (request.result) {
164                        console.log('✅ Значение получено из IndexedDB:', key);
165                        resolve(request.result.value);
166                    } else {
167                        console.log('⚠️ Значение не найдено в IndexedDB, возвращаем default:', key);
168                        resolve(defaultValue);
169                    }
170                };
171                
172                request.onerror = (event) => {
173                    console.error('Ошибка чтения из IndexedDB:', event);
174                    console.error('request.error:', request.error);
175                    console.error('request.error.name:', request.error?.name);
176                    console.error('request.error.message:', request.error?.message);
177                    reject(new Error(`Get error: ${request.error?.name} - ${request.error?.message}`));
178                };
179            } catch (error) {
180                console.error('Ошибка при создании транзакции IndexedDB:', error);
181                console.error('error.name:', error.name);
182                console.error('error.message:', error.message);
183                console.error('error.stack:', error.stack);
184                reject(error);
185            }
186        });
187    }
188
189    // Удаление значения из IndexedDB
190    async function deleteDBValue(key) {
191        if (!db) {
192            console.log('База данных не инициализирована, инициализируем...');
193            await initIndexedDB();
194        }
195        
196        return new Promise((resolve, reject) => {
197            try {
198                console.log('Удаление из IndexedDB, ключ:', key);
199                const transaction = db.transaction([STORE_NAME], 'readwrite');
200                
201                transaction.onerror = (event) => {
202                    console.error('Ошибка транзакции при удалении:', event);
203                    console.error('transaction.error:', transaction.error);
204                    reject(new Error(`Transaction error: ${transaction.error?.name} - ${transaction.error?.message}`));
205                };
206                
207                const objectStore = transaction.objectStore(STORE_NAME);
208                const request = objectStore.delete(key);
209                
210                request.onsuccess = () => {
211                    console.log('✅ Значение удалено из IndexedDB:', key);
212                    resolve();
213                };
214                
215                request.onerror = (event) => {
216                    console.error('Ошибка удаления из IndexedDB:', event);
217                    console.error('request.error:', request.error);
218                    console.error('request.error.name:', request.error?.name);
219                    console.error('request.error.message:', request.error?.message);
220                    reject(new Error(`Delete error: ${request.error?.name} - ${request.error?.message}`));
221                };
222            } catch (error) {
223                console.error('Ошибка при создании транзакции IndexedDB:', error);
224                console.error('error.name:', error.name);
225                console.error('error.message:', error.message);
226                console.error('error.stack:', error.stack);
227                reject(error);
228            }
229        });
230    }
231
232    // ============= ФУНКЦИИ ДЛЯ ИМПОРТА/ЭКСПОРТА БАЗЫ =============
233
234    // Экспорт базы данных в JSON файл
235    async function exportDatabase() {
236        try {
237            const db = await loadContactsDatabase();
238            const currentAccount = getCurrentAccount();
239            
240            // Формируем данные для экспорта
241            const exportData = {
242                account: currentAccount,
243                exportDate: new Date().toISOString(),
244                version: '1.0',
245                database: db
246            };
247            
248            // Конвертируем в JSON
249            const jsonString = JSON.stringify(exportData, null, 2);
250            const blob = new Blob([jsonString], { type: 'application/json' });
251            
252            // Создаем ссылку для скачивания
253            const url = URL.createObjectURL(blob);
254            const a = document.createElement('a');
255            a.href = url;
256            
257            // Формируем имя файла с датой и аккаунтом
258            const dateStr = new Date().toISOString().split('T')[0];
259            const accountSlug = currentAccount.replace(/[^a-zA-Z0-9]/g, '_').substring(0, 30);
260            a.download = `ozon_contacts_${accountSlug}_${dateStr}.json`;
261            
262            document.body.appendChild(a);
263            a.click();
264            document.body.removeChild(a);
265            URL.revokeObjectURL(url);
266            
267            console.log('✅ База данных экспортирована');
268            alert(`✅ База данных экспортирована!\n\nАккаунт: ${currentAccount}\nКонтактов: ${Object.keys(db.contacts).length}\nФайл: ${a.download}`);
269            
270            return true;
271        } catch (error) {
272            console.error('❌ Ошибка экспорта базы данных:', error);
273            alert('❌ Ошибка экспорта базы данных: ' + error.message);
274            return false;
275        }
276    }
277
278    // Импорт базы данных из JSON файла
279    async function importDatabase() {
280        return new Promise((resolve) => {
281            // Создаем input для выбора файла
282            const input = document.createElement('input');
283            input.type = 'file';
284            input.accept = '.json';
285            
286            input.onchange = async (e) => {
287                try {
288                    const file = e.target.files[0];
289                    if (!file) {
290                        resolve(false);
291                        return;
292                    }
293                    
294                    // Читаем файл
295                    const reader = new FileReader();
296                    reader.onload = async (event) => {
297                        try {
298                            const importData = JSON.parse(event.target.result);
299                            
300                            // Валидация структуры
301                            if (!importData.database || !importData.database.contacts) {
302                                throw new Error('Неверный формат файла базы данных');
303                            }
304                            
305                            const currentAccount = getCurrentAccount();
306                            const importedAccount = importData.account || 'Неизвестный аккаунт';
307                            const contactsCount = Object.keys(importData.database.contacts).length;
308                            
309                            // Загружаем текущую базу
310                            const currentDb = await loadContactsDatabase();
311                            const existingContactsCount = Object.keys(currentDb.contacts).length;
312                            
313                            // Подсчитываем новые контакты
314                            let newContactsCount = 0;
315                            for (const chatId in importData.database.contacts) {
316                                if (!currentDb.contacts[chatId]) {
317                                    newContactsCount++;
318                                }
319                            }
320                            
321                            // Подтверждение импорта
322                            const confirmMessage = 'Импортировать базу данных?\n\n' +
323                                `Импортируемый аккаунт: ${importedAccount}\n` +
324                                `Текущий аккаунт: ${currentAccount}\n` +
325                                `Контактов в файле: ${contactsCount}\n` +
326                                `Контактов в текущей базе: ${existingContactsCount}\n` +
327                                `Новых контактов для добавления: ${newContactsCount}\n` +
328                                `Дата экспорта: ${importData.exportDate ? new Date(importData.exportDate).toLocaleString('ru-RU') : 'Неизвестно'}\n\n` +
329                                '✅ Существующие контакты будут сохранены!\n' +
330                                '➕ Будут добавлены только новые контакты.';
331                            
332                            if (!confirm(confirmMessage)) {
333                                resolve(false);
334                                return;
335                            }
336                            
337                            // Загружаем всю структуру с аккаунтами
338                            const allDatabases = await getDBValue('contactsDatabaseByAccount', {});
339                            
340                            // Получаем текущую базу для аккаунта
341                            if (!allDatabases[currentAccount]) {
342                                allDatabases[currentAccount] = {
343                                    contacts: {},
344                                    lastUpdate: null
345                                };
346                            }
347                            
348                            // ИЗМЕНЕНО: Объединяем контакты вместо замены
349                            // Добавляем только новые контакты из импортируемой базы
350                            let actuallyAdded = 0;
351                            for (const chatId in importData.database.contacts) {
352                                if (!allDatabases[currentAccount].contacts[chatId]) {
353                                    allDatabases[currentAccount].contacts[chatId] = importData.database.contacts[chatId];
354                                    actuallyAdded++;
355                                }
356                            }
357                            
358                            // Обновляем дату последнего обновления
359                            allDatabases[currentAccount].lastUpdate = new Date().toISOString();
360                            
361                            // Сохраняем
362                            await setDBValue('contactsDatabaseByAccount', allDatabases);
363                            
364                            console.log('✅ База данных импортирована (добавлено новых контактов)');
365                            alert(`✅ База данных успешно импортирована!\n\nДобавлено новых контактов: ${actuallyAdded}\nВсего контактов в базе: ${Object.keys(allDatabases[currentAccount].contacts).length}\nАккаунт: ${currentAccount}`);
366                            
367                            // Обновляем статистику в интерфейсе
368                            await updateDatabaseStats();
369                            
370                            resolve(true);
371                        } catch (error) {
372                            console.error('❌ Ошибка парсинга файла:', error);
373                            alert('❌ Ошибка чтения файла: ' + error.message);
374                            resolve(false);
375                        }
376                    };
377                    
378                    reader.onerror = () => {
379                        alert('❌ Ошибка чтения файла');
380                        resolve(false);
381                    };
382                    
383                    reader.readAsText(file);
384                } catch (error) {
385                    console.error('❌ Ошибка импорта:', error);
386                    alert('❌ Ошибка импорта: ' + error.message);
387                    resolve(false);
388                }
389            };
390            
391            // Открываем диалог выбора файла
392            input.click();
393        });
394    }
395
396    // ============= ФУНКЦИИ ДЛЯ РАБОТЫ С БАЗОЙ КОНТАКТОВ =============
397    
398    // Функция для определения текущего аккаунта
399    function getCurrentAccount() {
400        try {
401            // Ищем элемент с названием аккаунта
402            const accountElement = document.querySelector('.index_companyItem_wgEhc.index_hasSelect_GanwO');
403            if (accountElement) {
404                const accountName = accountElement.textContent.trim();
405                console.log('Определен текущий аккаунт:', accountName);
406                return accountName;
407            }
408            
409            console.warn('Элемент с названием аккаунта не найден, используем "Неизвестный аккаунт"');
410            return 'Неизвестный аккаунт';
411        } catch (error) {
412            console.error('Ошибка при определении аккаунта:', error);
413            return 'Неизвестный аккаунт';
414        }
415    }
416    
417    // Загрузка базы контактов
418    async function loadContactsDatabase() {
419        try {
420            // Загружаем всю структуру с аккаунтами
421            const allDatabases = await getDBValue('contactsDatabaseByAccount', {});
422            
423            // Определяем текущий аккаунт
424            const currentAccount = getCurrentAccount();
425            
426            // Если для текущего аккаунта нет базы - создаем пустую
427            if (!allDatabases[currentAccount]) {
428                allDatabases[currentAccount] = {
429                    contacts: {},
430                    lastUpdate: null
431                };
432            }
433            
434            const database = allDatabases[currentAccount];
435            console.log('База контактов загружена для аккаунта:', currentAccount, '|', Object.keys(database.contacts).length, 'контактов');
436            return database;
437        } catch (error) {
438            console.error('Ошибка загрузки базы контактов:', error);
439            return {
440                contacts: {},
441                lastUpdate: null
442            };
443        }
444    }
445    
446    // Сохранение базы контактов
447    async function saveContactsDatabase(database) {
448        try {
449            // Загружаем всю структуру
450            const allDatabases = await getDBValue('contactsDatabaseByAccount', {});
451            
452            // Определяем текущий аккаунт
453            const currentAccount = getCurrentAccount();
454            
455            // Обновляем дату последнего обновления
456            database.lastUpdate = new Date().toISOString();
457            
458            // Сохраняем базу для текущего аккаунта
459            allDatabases[currentAccount] = database;
460            
461            // Сохраняем всю структуру
462            await setDBValue('contactsDatabaseByAccount', allDatabases);
463            
464            console.log('✅ База контактов сохранена для аккаунта:', currentAccount, '|', Object.keys(database.contacts).length, 'контактов');
465            return true;
466        } catch (error) {
467            console.error('❌ Ошибка сохранения базы контактов:', error);
468            return false;
469        }
470    }
471    
472    // Добавление контакта в базу
473    function addContactToDatabase(database, chatId, contactData) {
474        database.contacts[chatId] = {
475            id: chatId,
476            name: contactData.name,
477            lastMessageDate: contactData.lastMessageDate,
478            lastSentDate: contactData.lastSentDate,
479            messageCount: contactData.messageCount || 0
480        };
481    }
482    
483    // Получение статистики базы данных
484    function getDatabaseStats(database) {
485        const contacts = Object.values(database.contacts);
486        const total = contacts.length;
487        const withDates = contacts.filter(c => c.lastMessageDate).length;
488        const sent = contacts.filter(c => c.lastSentDate).length;
489        const notSent = total - sent;
490        
491        return {
492            total,
493            withDates,
494            sent,
495            notSent,
496            lastUpdate: database.lastUpdate
497        };
498    }
499    
500    // Очистка базы контактов
501    async function clearContactsDatabase() {
502        const currentAccount = getCurrentAccount();
503        
504        if (confirm(`Вы уверены, что хотите очистить базу контактов для аккаунта "${currentAccount}"?\n\nЭто действие нельзя отменить.`)) {
505            try {
506                // Загружаем всю структуру
507                const allDatabases = await getDBValue('contactsDatabaseByAccount', {});
508                
509                // Очищаем базу для текущего аккаунта
510                allDatabases[currentAccount] = {
511                    contacts: {},
512                    lastUpdate: null
513                };
514                
515                // Сохраняем обновленную структуру
516                await setDBValue('contactsDatabaseByAccount', allDatabases);
517                
518                console.log('✅ База контактов очищена для аккаунта:', currentAccount);
519                return true;
520            } catch (error) {
521                console.error('❌ Ошибка очистки базы контактов:', error);
522                return false;
523            }
524        }
525        return false;
526    }
527    
528    // Удаление дубликатов из базы
529    async function removeDuplicatesFromDatabase() {
530        const database = await loadContactsDatabase();
531        const contacts = Object.values(database.contacts);
532        
533        // Группируем по имени
534        const grouped = {};
535        contacts.forEach(contact => {
536            if (!grouped[contact.name]) {
537                grouped[contact.name] = [];
538            }
539            grouped[contact.name].push(contact);
540        });
541        
542        // Находим дубликаты
543        let duplicatesCount = 0;
544        Object.keys(grouped).forEach(name => {
545            const group = grouped[name];
546            if (group.length > 1) {
547                // Оставляем только первый контакт
548                for (let i = 1; i < group.length; i++) {
549                    delete database.contacts[group[i].id];
550                    duplicatesCount++;
551                }
552            }
553        });
554        
555        if (duplicatesCount > 0) {
556            await saveContactsDatabase(database);
557            alert(`Удалено дубликатов: ${duplicatesCount}`);
558        } else {
559            alert('Дубликаты не найдены');
560        }
561    }
562    
563    // ============= ФУНКЦИИ ДЛЯ РАБОТЫ С СОСТОЯНИЕМ РАССЫЛКИ =============
564    
565    // Загрузка состояния рассылки
566    async function loadSendingState() {
567        try {
568            const state = await getDBValue('sendingState', null);
569            if (state) {
570                console.log('Состояние рассылки загружено:', state);
571            }
572            return state;
573        } catch (error) {
574            console.error('Ошибка загрузки состояния рассылки:', error);
575            return null;
576        }
577    }
578    
579    // Сохранение состояния рассылки
580    async function saveSendingState(state) {
581        try {
582            await setDBValue('sendingState', state);
583            console.log('✅ Состояние рассылки сохранено');
584            return true;
585        } catch (error) {
586            console.error('❌ Ошибка сохранения состояния рассылки:', error);
587            return false;
588        }
589    }
590    
591    // Очистка состояния рассылки
592    async function clearSendingState() {
593        try {
594            await deleteDBValue('sendingState');
595            console.log('✅ Состояние рассылки очищено');
596            return true;
597        } catch (error) {
598            console.error('❌ Ошибка очистки состояния рассылки:', error);
599            return false;
600        }
601    }
602
603    // ============= ФУНКЦИИ ДЛЯ РАБОТЫ С ЧЕРНЫМ СПИСКОМ =============
604    
605    // Загрузка черного списка
606    async function loadBlacklist() {
607        try {
608            // Загружаем всю структуру с аккаунтами
609            const allBlacklists = await getDBValue('blacklistByAccount', {});
610            
611            // Определяем текущий аккаунт
612            const currentAccount = getCurrentAccount();
613            
614            // Если для текущего аккаунта нет черного списка - создаем пустой
615            if (!allBlacklists[currentAccount]) {
616                allBlacklists[currentAccount] = [];
617            }
618            
619            const blacklist = allBlacklists[currentAccount];
620            console.log('Черный список загружен для аккаунта:', currentAccount, '|', blacklist.length, 'ID');
621            return blacklist;
622        } catch (error) {
623            console.error('Ошибка загрузки черного списка:', error);
624            return [];
625        }
626    }
627    
628    // Сохранение черного списка
629    async function saveBlacklist(blacklist) {
630        try {
631            // Загружаем всю структуру
632            const allBlacklists = await getDBValue('blacklistByAccount', {});
633            
634            // Определяем текущий аккаунт
635            const currentAccount = getCurrentAccount();
636            
637            // Сохраняем черный список для текущего аккаунта
638            allBlacklists[currentAccount] = blacklist;
639            
640            // Сохраняем всю структуру
641            await setDBValue('blacklistByAccount', allBlacklists);
642            
643            console.log('✅ Черный список сохранен для аккаунта:', currentAccount, '|', blacklist.length, 'ID');
644            return true;
645        } catch (error) {
646            console.error('❌ Ошибка сохранения черного списка:', error);
647            return false;
648        }
649    }
650    
651    // Добавление ID в черный список
652    async function addToBlacklist(chatIds) {
653        const blacklist = await loadBlacklist();
654        const db = await loadContactsDatabase();
655        
656        let addedCount = 0;
657        let removedFromDbCount = 0;
658        
659        for (const chatId of chatIds) {
660            // Проверяем, нет ли уже в черном списке
661            if (!blacklist.includes(chatId)) {
662                blacklist.push(chatId);
663                addedCount++;
664                console.log('Добавлен в ЧС:', chatId);
665            }
666            
667            // Удаляем из базы контактов, если есть
668            if (db.contacts[chatId]) {
669                delete db.contacts[chatId];
670                removedFromDbCount++;
671                console.log('Удален из базы:', chatId);
672            }
673        }
674        
675        await saveBlacklist(blacklist);
676        await saveContactsDatabase(db);
677        
678        return { addedCount, removedFromDbCount };
679    }
680    
681    // Удаление ID из черного списка
682    async function removeFromBlacklist(chatIds) {
683        const blacklist = await loadBlacklist();
684        const db = await loadContactsDatabase();
685        
686        let removedCount = 0;
687        let addedToDbCount = 0;
688        
689        for (const chatId of chatIds) {
690            const index = blacklist.indexOf(chatId);
691            if (index > -1) {
692                blacklist.splice(index, 1);
693                removedCount++;
694                console.log('Удален из ЧС:', chatId);
695                
696                // Возвращаем в базу контактов
697                if (!db.contacts[chatId]) {
698                    addContactToDatabase(db, chatId, {
699                        name: 'Восстановлен из ЧС',
700                        lastMessageDate: null,
701                        lastSentDate: null,
702                        messageCount: 0
703                    });
704                    addedToDbCount++;
705                    console.log('Возвращен в базу:', chatId);
706                }
707            }
708        }
709        
710        await saveBlacklist(blacklist);
711        await saveContactsDatabase(db);
712        
713        return { removedCount, addedToDbCount };
714    }
715    
716    // Проверка, находится ли ID в черном списке
717    async function isInBlacklist(chatId) {
718        const blacklist = await loadBlacklist();
719        return blacklist.includes(chatId);
720    }
721    
722    // Очистка черного списка
723    async function clearBlacklist() {
724        const blacklist = await loadBlacklist();
725        const count = blacklist.length;
726        
727        if (count === 0) {
728            alert('Черный список уже пуст');
729            return { count: 0 };
730        }
731        
732        if (confirm(`Вы уверены, что хотите очистить весь черный список? (${count} ID)\n\nВнимание: ID будут возвращены в базу контактов.`)) {
733            // Возвращаем все ID в базу
734            const result = await removeFromBlacklist(blacklist);
735            console.log('Черный список очищен');
736            return result;
737        }
738        
739        return { count: 0 };
740    }
741    
742    // Массовое добавление видимых чатов в черный список
743    async function collectChatsToBlacklist() {
744        console.log('=== НАЧАЛО МАССОВОГО ДОБАВЛЕНИЯ В ЧЕРНЫЙ СПИСОК ===');
745        
746        const chatIdsToAdd = [];
747        let scrollAttempts = 0;
748        let noNewChatsCount = 0;
749        const maxNoNewChatsAttempts = 3;
750        
751        // Трекер обработанных ID в этой сессии
752        const processedInSession = new Set();
753        
754        while (!shouldStop) {
755            // Проверка на паузу
756            while (isPaused && !shouldStop) {
757                updateStatus('Пауза сбора ЧС', `Собрано: ${chatIdsToAdd.length} ID`);
758                await sleep(500);
759            }
760            
761            if (shouldStop) {
762                console.log('Сбор ЧС остановлен пользователем');
763                break;
764            }
765            
766            // Получаем текущие видимые чаты
767            const currentChats = getAllChats();
768            console.log(`Попытка ${scrollAttempts + 1}: найдено ${currentChats.length} чатов в DOM`);
769            
770            // Фильтруем только те чаты, которые еще не обработали
771            const newChats = currentChats.filter(chat => {
772                const chatId = extractChatId(chat);
773                return chatId && !processedInSession.has(chatId);
774            });
775            
776            console.log('Новых необработанных чатов:', newChats.length);
777            
778            // Если нет новых чатов - увеличиваем счетчик
779            if (newChats.length === 0) {
780                noNewChatsCount++;
781                console.log(`Новые чаты не появились (${noNewChatsCount}/${maxNoNewChatsAttempts})`);
782                
783                if (noNewChatsCount >= maxNoNewChatsAttempts) {
784                    console.log('Достигнут конец списка чатов');
785                    break;
786                }
787                
788                // Скроллим для подгрузки новых
789                console.log('Скроллим для подгрузки новых чатов...');
790                await scrollChatList();
791                await sleep(5000);
792                continue;
793            } else {
794                noNewChatsCount = 0;
795            }
796            
797            // Обрабатываем новые чаты
798            for (const chat of newChats) {
799                // Проверка на паузу
800                while (isPaused && !shouldStop) {
801                    updateStatus('Пауза сбора базы', `Новых: ${newContactsSet.size}, Обновлено: ${updatedContactsSet.size}, Пропущено (ЧС): ${skippedBlacklist}`);
802                    await sleep(500);
803                }
804                
805                if (shouldStop) break;
806                
807                const chatId = extractChatId(chat);
808                const chatName = chat.querySelector('.index_chatTitle_TiXTq')?.textContent || 
809                               chat.querySelector('.index_chatTitle_x9txX')?.textContent || 
810                               'Неизвестно';
811                
812                // Добавляем ID в кольцевой буфер
813                const isNew = addToRecentIds(chatId);
814                if (!isNew) {
815                    console.log('Чат уже обработан (в буфере):', chatName);
816                    continue;
817                }
818                
819                // Проверяем черный список
820                if (blacklist.has(chatId)) {
821                    console.log(`⛔ Чат в черном списке, пропускаем: ${chatName} (${chatId})`);
822                    skippedBlacklist++;
823                    continue;
824                }
825                
826                // Получаем данные чата из элемента списка
827                const chatData = getChatDataFromElement(chat);
828                
829                // Проверяем, есть ли уже в базе
830                const isExisting = db.contacts[chatId];
831                
832                if (isExisting) {
833                    // Обновляем существующий контакт
834                    db.contacts[chatId].lastMessageDate = chatData.lastMessageDate || db.contacts[chatId].lastMessageDate;
835                    updatedContactsSet.add(chatId);
836                    console.log(`🔄 Обновлен контакт: ${chatName} (${chatId})`);
837                } else {
838                    // Добавляем новый контакт
839                    addContactToDatabase(db, chatId, chatData);
840                    newContactsSet.add(chatId);
841                    console.log(`➕ Добавлен новый контакт: ${chatName} (${chatId})`);
842                }
843                
844                updateStatus(
845                    `Сбор базы: ${chatName}`,
846                    `Обработано: ${newContactsSet.size + updatedContactsSet.size}, Новых: ${newContactsSet.size}, Обновлено: ${updatedContactsSet.size}, Пропущено (ЧС): ${skippedBlacklist}`
847                );
848                
849                // Периодическое сохранение
850                contactsSinceLastSave++;
851                if (contactsSinceLastSave >= saveEveryNContacts) {
852                    console.log(`Промежуточное сохранение базы (${newContactsSet.size + updatedContactsSet.size} контактов)...`);
853                    await saveContactsDatabase(db);
854                    contactsSinceLastSave = 0;
855                }
856            }
857            
858            // Скроллим для подгрузки следующей порции
859            if (!shouldStop && newChats.length > 0) {
860                await scrollChatList();
861                await sleep(3000);
862            }
863        }
864        
865        // Финальное сохранение базы
866        console.log('Финальное сохранение базы данных...');
867        await saveContactsDatabase(db);
868        
869        // ОПТИМИЗАЦИЯ: Финальная очистка памяти
870        console.log('🧹 Выполняем агрессивную очистку памяти...');
871        
872        // Очищаем кольцевой буфер до минимума
873        while (recentIdsQueue.length > 100) {
874            const oldestId = recentIdsQueue.shift();
875            recentlyProcessedIds.delete(oldestId);
876        }
877        console.log('Кольцевой буфер очищен до 100 ID');
878        
879        // АГРЕССИВНАЯ ОЧИСТКА DOM: Удаляем ВСЕ чаты кроме последних 50
880        const allChatsInDom = document.querySelectorAll('.index_chat_4fr82, .index_chat_EHlBq');
881        const totalChatsInDom = allChatsInDom.length;
882        console.log(`📊 Всего чатов в DOM перед очисткой: ${totalChatsInDom}`);
883        
884        if (totalChatsInDom > 50) {
885            // Удаляем все чаты кроме последних 50
886            const chatsToRemove = totalChatsInDom - 50;
887            let removedCount = 0;
888            
889            for (let i = 0; i < chatsToRemove; i++) {
890                if (allChatsInDom[i]) {
891                    allChatsInDom[i].remove();
892                    removedCount++;
893                }
894            }
895            
896            console.log(`🗑️ АГРЕССИВНАЯ ОЧИСТКА: Удалено ${removedCount} чатов из DOM`);
897            console.log('✅ Оставлено последних 50 чатов для продолжения работы');
898            
899            // ОБНОВЛЯЕМ СТАТУС ПОСЛЕ ОЧИСТКИ
900            updateStatus(
901                `Сбор базы: очистка памяти (удалено ${removedCount} чатов из DOM)`,
902                `Обработано: ${newContactsSet.size + updatedContactsSet.size}, Новых: ${newContactsSet.size}, Обновлено: ${updatedContactsSet.size}, Пропущено (ЧС): ${skippedBlacklist}`
903            );
904            
905            // Проверяем результат
906            const remainingChats = document.querySelectorAll('.index_chat_4fr82, .index_chat_EHlBq').length;
907            
908            if (remainingChats > 200) {
909                console.warn(`⚠️ ПРОБЛЕМА: После очистки осталось ${remainingChats} чатов!`);
910                console.warn('Рекомендация: Остановите сбор, перезагрузите страницу и продолжите.');
911                
912                // Показываем предупреждение пользователю
913                const shouldContinue = confirm(
914                    '⚠️ ВНИМАНИЕ: Проблема с памятью не решена!\n\n' +
915                    `После очистки осталось ${remainingChats} чатов в DOM.\n` +
916                    'Виртуализация Ozon не работает корректно.\n\n' +
917                    'Рекомендуется:\n' +
918                    '1. Остановить сбор (нажмите "Отмена")\n' +
919                    '2. Перезагрузить страницу\n' +
920                    '3. Продолжить сбор\n\n' +
921                    'Продолжить сбор сейчас? (не рекомендуется)'
922                );
923                
924                if (!shouldContinue) {
925                    shouldStop = true;
926                    // Прерываем цикл через флаг shouldStop
927                }
928            }
929        } else {
930            console.log(`✅ В DOM ${totalChatsInDom} чатов - очистка не требуется`);
931        }
932        
933        iterationsSinceCleanup = 0;
934    }
935
936    // Функция для создания модального окна черного списка
937    function createBlacklistModal() {
938        // Проверяем, не создано ли уже окно
939        if (document.getElementById('blacklistModal')) {
940            return;
941        }
942        
943        const modalHTML = `
944            <div id="blacklistModal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 10002; justify-content: center; align-items: center;">
945                <div style="background: white; padding: 30px; border-radius: 12px; width: 700px; max-width: 95%; max-height: 90vh; overflow-y: auto; box-shadow: 0 4px 20px rgba(0,0,0,0.3);">
946                    <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
947                        <h2 style="margin: 0; color: #333; font-size: 24px;">🚫 Черный список</h2>
948                        <button id="closeBlacklistButton" style="padding: 8px 16px; background: #999; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600;">Закрыть</button>
949                    </div>
950                    
951                    <div id="blacklistStatsBlock" style="margin-bottom: 20px; padding: 15px; background: #fff3f3; border-radius: 6px; border-left: 4px solid #f44336;">
952                        <div style="font-weight: 600; color: #f44336; margin-bottom: 10px;">Статистика черного списка:</div>
953                        <div id="blacklistStatsContent" style="color: #333; font-size: 14px;">Загрузка...</div>
954                    </div>
955                    
956                    <div style="display: flex; gap: 10px; margin-bottom: 20px; flex-wrap: wrap;">
957                        <button id="massAddBlacklistButton" style="padding: 10px 18px; background: #ff5722; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 600;">📥 Массовое добавление</button>
958                        <button id="clearAllBlacklistButton" style="padding: 10px 18px; background: #f44336; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 600;">🗑️ Очистить все</button>
959                        <button id="pauseBlacklistButton" style="display: none; padding: 10px 18px; background: #ff9800; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 600;">⏸️ Пауза</button>
960                        <button id="stopBlacklistButton" style="display: none; padding: 10px 18px; background: #f44336; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 600;">⏹️ Стоп</button>
961                    </div>
962                    
963                    <div style="margin-bottom: 15px;">
964                        <input type="text" id="searchBlacklistInput" placeholder="Поиск по ID..." style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px;">
965                    </div>
966                    
967                    <div id="blacklistContainer" style="max-height: 400px; overflow-y: auto; border: 1px solid #ddd; border-radius: 6px;">
968                        <div id="blacklistContent" style="padding: 10px;"></div>
969                    </div>
970                </div>
971            </div>
972        `;
973        
974        document.body.insertAdjacentHTML('beforeend', modalHTML);
975        console.log('Модальное окно черного списка создано');
976        
977        // Обработчики событий
978        document.getElementById('closeBlacklistButton').addEventListener('click', closeBlacklistModal);
979        
980        document.getElementById('addManualBlacklistButton').addEventListener('click', async () => {
981            const input = document.getElementById('manualBlacklistId');
982            const chatId = input.value.trim();
983            
984            if (!chatId) {
985                alert('Введите ID чата');
986                return;
987            }
988            
989            const result = await addToBlacklist([chatId]);
990            alert(`✅ ID добавлен в черный список!\n\nДобавлено в ЧС: ${result.addedCount}\nУдалено из базы: ${result.removedFromDbCount}`);
991            
992            input.value = '';
993            await loadAndDisplayBlacklist();
994            await updateDatabaseStats();
995        });
996        
997        document.getElementById('massAddBlacklistButton').addEventListener('click', async () => {
998            if (confirm('Начать массовое добавление видимых чатов в черный список?\n\nВсе видимые чаты будут добавлены в ЧС и удалены из базы контактов.')) {
999                // Показываем кнопки паузы и остановки
1000                document.getElementById('massAddBlacklistButton').style.display = 'none';
1001                document.getElementById('pauseBlacklistButton').style.display = 'inline-block';
1002                document.getElementById('stopBlacklistButton').style.display = 'inline-block';
1003                
1004                shouldStop = false;
1005                isPaused = false;
1006                
1007                await collectChatsToBlacklist();
1008                
1009                // Скрываем кнопки паузы и остановки
1010                document.getElementById('massAddBlacklistButton').style.display = 'inline-block';
1011                document.getElementById('pauseBlacklistButton').style.display = 'none';
1012                document.getElementById('stopBlacklistButton').style.display = 'none';
1013                
1014                await loadAndDisplayBlacklist();
1015                await updateDatabaseStats();
1016            }
1017        });
1018        
1019        document.getElementById('clearAllBlacklistButton').addEventListener('click', async () => {
1020            const result = await clearBlacklist();
1021            if (result.count > 0 || result.removedCount > 0) {
1022                alert(`✅ Черный список очищен!\n\nУдалено из ЧС: ${result.removedCount}\nВозвращено в базу: ${result.addedToDbCount}`);
1023                await loadAndDisplayBlacklist();
1024                await updateDatabaseStats();
1025            }
1026        });
1027        
1028        // Обработчик паузы
1029        document.getElementById('pauseBlacklistButton').addEventListener('click', () => {
1030            isPaused = !isPaused;
1031            const pauseButton = document.getElementById('pauseBlacklistButton');
1032            
1033            if (isPaused) {
1034                pauseButton.textContent = '▶️ Продолжить';
1035                pauseButton.style.background = '#4caf50';
1036                console.log('Сбор ЧС приостановлен');
1037            } else {
1038                pauseButton.textContent = '⏸️ Пауза';
1039                pauseButton.style.background = '#ff9800';
1040                console.log('Сбор ЧС возобновлен');
1041            }
1042        });
1043        
1044        // Обработчик остановки
1045        document.getElementById('stopBlacklistButton').addEventListener('click', () => {
1046            if (confirm('Вы уверены, что хотите остановить сбор?')) {
1047                shouldStop = true;
1048                isPaused = false;
1049                console.log('Запрошена остановка сбора ЧС');
1050            }
1051        });
1052        
1053        // Поиск по черному списку
1054        document.getElementById('searchBlacklistInput').addEventListener('input', (e) => {
1055            filterBlacklist(e.target.value);
1056        });
1057    }
1058    
1059    // Функция для открытия модального окна черного списка
1060    async function openBlacklistModal() {
1061        createBlacklistModal();
1062        const modal = document.getElementById('blacklistModal');
1063        if (modal) {
1064            modal.style.display = 'flex';
1065            await loadAndDisplayBlacklist();
1066            console.log('Модальное окно черного списка открыто');
1067        }
1068    }
1069    
1070    // Функция для закрытия модального окна черного списка
1071    function closeBlacklistModal() {
1072        const modal = document.getElementById('blacklistModal');
1073        if (modal) {
1074            modal.style.display = 'none';
1075            console.log('Модальное окно черного списка закрыто');
1076        }
1077    }
1078    
1079    // Функция для загрузки и отображения черного списка
1080    async function loadAndDisplayBlacklist(searchQuery = '') {
1081        const blacklist = await loadBlacklist();
1082        
1083        // Обновляем статистику
1084        const statsContent = document.getElementById('blacklistStatsContent');
1085        if (statsContent) {
1086            statsContent.innerHTML = `
1087                <div><strong>Всего ID в черном списке:</strong> ${blacklist.length}</div>
1088            `;
1089        }
1090        
1091        // Фильтруем по поисковому запросу
1092        const filteredBlacklist = searchQuery 
1093            ? blacklist.filter(id => id.toLowerCase().includes(searchQuery.toLowerCase()))
1094            : blacklist;
1095        
1096        // Отображаем список
1097        const blacklistContent = document.getElementById('blacklistContent');
1098        if (blacklistContent) {
1099            if (filteredBlacklist.length === 0) {
1100                blacklistContent.innerHTML = '<div style="text-align: center; padding: 40px; color: #999;">Черный список пуст</div>';
1101            } else {
1102                blacklistContent.innerHTML = filteredBlacklist.map(chatId => {
1103                    return `
1104                        <div style="padding: 12px; margin-bottom: 8px; background: #fff8f8; border: 1px solid #ffcdd2; border-radius: 6px; font-size: 13px; display: flex; justify-content: space-between; align-items: center;">
1105                            <div style="font-family: monospace; color: #333;">${chatId}</div>
1106                            <button class="removeFromBlacklistBtn" data-chat-id="${chatId}" style="padding: 6px 12px; background: #4caf50; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; font-weight: 600;">✓ Вернуть</button>
1107                        </div>
1108                    `;
1109                }).join('');
1110                
1111                // Добавляем обработчики для кнопок удаления
1112                document.querySelectorAll('.removeFromBlacklistBtn').forEach(btn => {
1113                    btn.addEventListener('click', async (e) => {
1114                        const chatId = e.target.getAttribute('data-chat-id');
1115                        if (confirm(`Удалить ID из черного списка?\n\n${chatId}\n\nID будет возвращен в базу контактов.`)) {
1116                            const result = await removeFromBlacklist([chatId]);
1117                            alert(`✅ ID удален из черного списка!\n\nУдалено из ЧС: ${result.removedCount}\nВозвращено в базу: ${result.addedToDbCount}`);
1118                            await loadAndDisplayBlacklist();
1119                            await updateDatabaseStats();
1120                        }
1121                    });
1122                });
1123            }
1124        }
1125    }
1126    
1127    // Функция для фильтрации черного списка
1128    function filterBlacklist(searchQuery) {
1129        loadAndDisplayBlacklist(searchQuery);
1130    }
1131
1132    // Функция для создания модального окна просмотра базы
1133    function createViewDatabaseModal() {
1134        // Проверяем, не создано ли уже окно
1135        if (document.getElementById('viewDbModal')) {
1136            return;
1137        }
1138        
1139        const modalHTML = `
1140            <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;">
1141                <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);">
1142                    <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
1143                        <h2 style="margin: 0; color: #333; font-size: 24px;">👁️ Просмотр базы контактов</h2>
1144                        <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>
1145                    </div>
1146                    
1147                    <div id="viewDbStatsBlock" style="margin-bottom: 20px; padding: 15px; background: #f0f8ff; border-radius: 6px; border-left: 4px solid #0066cc;">
1148                        <div style="font-weight: 600; color: #0066cc; margin-bottom: 10px;">Статистика базы:</div>
1149                        <div id="viewDbStatsContent" style="color: #333; font-size: 14px;"></div>
1150                    </div>
1151                    
1152                    <div style="display: flex; gap: 10px; align-items: center;">
1153                        <input type="text" id="searchContactInput" placeholder="Поиск по имени..." style="flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px;">
1154                        <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>
1155                    </div>
1156                    
1157                    <div id="contactsListContainer" style="max-height: 500px; overflow-y: auto; border: 1px solid #ddd; border-radius: 6px;">
1158                        <div id="contactsList" style="padding: 10px;"></div>
1159                    </div>
1160                </div>
1161            </div>
1162        `;
1163        
1164        document.body.insertAdjacentHTML('beforeend', modalHTML);
1165        console.log('Модальное окно просмотра базы создано');
1166        
1167        // Обработчики событий
1168        document.getElementById('closeViewDbButton').addEventListener('click', closeViewDatabaseModal);
1169        document.getElementById('removeDuplicatesButton').addEventListener('click', async () => {
1170            await removeDuplicatesFromDatabase();
1171            await loadAndDisplayContacts();
1172        });
1173        
1174        // Поиск по контактам
1175        document.getElementById('searchContactInput').addEventListener('input', (e) => {
1176            filterContactsList(e.target.value);
1177        });
1178    }
1179    
1180    // Функция для открытия модального окна просмотра базы
1181    async function openViewDatabaseModal() {
1182        createViewDatabaseModal();
1183        const modal = document.getElementById('viewDbModal');
1184        if (modal) {
1185            modal.style.display = 'flex';
1186            await loadAndDisplayContacts();
1187            console.log('Модальное окно просмотра базы открыто');
1188        }
1189    }
1190    
1191    // Функция для закрытия модального окна просмотра базы
1192    function closeViewDatabaseModal() {
1193        const modal = document.getElementById('viewDbModal');
1194        if (modal) {
1195            modal.style.display = 'none';
1196            console.log('Модальное окно просмотра базы закрыто');
1197        }
1198    }
1199    
1200    // Функция для загрузки и отображения контактов
1201    async function loadAndDisplayContacts(searchQuery = '') {
1202        const db = await loadContactsDatabase();
1203        const stats = getDatabaseStats(db);
1204        const contacts = Object.values(db.contacts);
1205        
1206        // Обновляем статистику
1207        const statsContent = document.getElementById('viewDbStatsContent');
1208        if (statsContent) {
1209            statsContent.innerHTML = `
1210                <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px;">
1211                    <div><strong>Всего контактов:</strong> ${stats.total}</div>
1212                    <div><strong>С датами:</strong> ${stats.withDates}</div>
1213                    <div><strong>Отправлено:</strong> ${stats.sent}</div>
1214                    <div><strong>Не отправлено:</strong> ${stats.notSent}</div>
1215                </div>
1216            `;
1217        }
1218        
1219        // Фильтруем контакты по поисковому запросу
1220        const filteredContacts = searchQuery 
1221            ? contacts.filter(c => c.name.toLowerCase().includes(searchQuery.toLowerCase()))
1222            : contacts;
1223        
1224        // Отображаем список контактов
1225        const contactsList = document.getElementById('contactsList');
1226        if (contactsList) {
1227            if (filteredContacts.length === 0) {
1228                contactsList.innerHTML = '<div style="text-align: center; padding: 40px; color: #999;">Контакты не найдены</div>';
1229            } else {
1230                contactsList.innerHTML = filteredContacts.map(contact => {
1231                    const lastMsgDate = contact.lastMessageDate 
1232                        ? new Date(contact.lastMessageDate).toLocaleDateString('ru-RU')
1233                        : 'Нет данных';
1234                    const lastSentDate = contact.lastSentDate 
1235                        ? new Date(contact.lastSentDate).toLocaleDateString('ru-RU')
1236                        : 'Не отправлялось';
1237                    const sentStatus = contact.lastSentDate ? '✅' : '⏳';
1238                    
1239                    return `
1240                        <div style="padding: 12px; margin-bottom: 8px; background: ${contact.lastSentDate ? '#f0f8ff' : '#fff8f0'}; border: 1px solid #ddd; border-radius: 6px; font-size: 13px;">
1241                            <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 8px;">
1242                                <div style="font-weight: 600; color: #333; font-size: 14px;">${sentStatus} ${contact.name}</div>
1243                                <div style="font-size: 11px; color: #999;">${contact.id.substring(0, 8)}...</div>
1244                            </div>
1245                            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; color: #666;">
1246                                <div><strong>Последнее сообщение:</strong> ${lastMsgDate}</div>
1247                                <div><strong>Отправлено:</strong> ${lastSentDate}</div>
1248                            </div>
1249                            ${contact.messageCount > 0 ? `<div style="margin-top: 5px; color: #666;"><strong>Отправлено сообщений:</strong> ${contact.messageCount}</div>` : ''}
1250                        </div>
1251                    `;
1252                }).join('');
1253            }
1254        }
1255    }
1256    
1257    // Функция для фильтрации списка контактов
1258    function filterContactsList(searchQuery) {
1259        loadAndDisplayContacts(searchQuery);
1260    }
1261
1262    // Функция для получения всех чатов
1263    function getAllChats() {
1264        // Сначала проверяем, есть ли результаты поиска (класс index_chat_EHlBq)
1265        const searchChats = document.querySelectorAll('.index_chat_EHlBq');
1266        
1267        if (searchChats.length > 0) {
1268            // Если есть результаты поиска, используем их
1269            console.log('Найдены результаты поиска:', searchChats.length);
1270            return Array.from(searchChats);
1271        }
1272        
1273        // Если поиска нет, используем обычные чаты
1274        const chats = document.querySelectorAll('.index_chat_4fr82');
1275        // Фильтруем только видимые чаты (учитываем фильтр поиска)
1276        const visibleChats = Array.from(chats).filter(chat => {
1277            const style = window.getComputedStyle(chat);
1278            // Проверяем, что чат не скрыт (убрали проверку высоты, т.к. чаты виртуализированы)
1279            return style.display !== 'none' && 
1280                   style.visibility !== 'hidden' && 
1281                   style.opacity !== '0' &&
1282                   chat.offsetParent !== null; // Проверяем offsetParent для фильтрации
1283        });
1284        console.log('Найдено чатов:', visibleChats.length, '(всего в DOM:', chats.length, ')');
1285        return visibleChats;
1286    }
1287
1288    // Функция для клика по чату
1289    async function clickChat(chat) {
1290        // Пробуем оба селектора для имени чата (обычный и поисковый)
1291        const chatName = chat.querySelector('.index_chatTitle_TiXTq')?.textContent?.trim() || 
1292                        chat.querySelector('.index_chatTitle_x9txX')?.textContent || 
1293                        'Неизвестно';
1294        console.log('Клик по чату:', chatName);
1295        chat.click();
1296        await sleep(1000); // Сокращено с 2000 до 1000
1297    }
1298    
1299    // Функция для открытия чата по ID (через прямую ссылку в новой вкладке)
1300    async function openChatById(chatId) {
1301        console.log('Открытие чата по ID в новой вкладке:', chatId);
1302        
1303        // Формируем прямую ссылку на чат
1304        const chatUrl = `https://seller.ozon.ru/app/messenger/?group=customers_v2&id=${chatId}`;
1305        console.log('URL чата:', chatUrl);
1306        
1307        // Открываем чат в новой вкладке
1308        await GM.openInTab(chatUrl, false); // false = открыть в активной вкладке (на переднем плане)
1309        
1310        // Убираем задержку - вкладка откроется сама
1311        
1312        return true;
1313    }
1314
1315    // Функция для получения даты последнего сообщения в открытом чате
1316    function getLastMessageDate() {
1317        const dateElements = document.querySelectorAll('.om_1_n4');
1318        if (dateElements.length > 0) {
1319            // Берем последнюю дату (последнее сообщение)
1320            const lastDateElement = dateElements[dateElements.length - 1];
1321            const dateText = lastDateElement.textContent.trim();
1322            console.log('Дата последнего сообщения:', dateText);
1323            return parseDate(dateText);
1324        }
1325        console.log('Элемент даты не найден');
1326        return null;
1327    }
1328
1329    // Функция для отправки сообщения
1330    async function sendMessage(messageText) {
1331        console.log('Отправка сообщения:', messageText);
1332        
1333        // Находим текстовое поле
1334        const textarea = document.querySelector('.om_17_a4');
1335        if (!textarea) {
1336            console.error('Текстовое поле не найдено');
1337            return false;
1338        }
1339        
1340        // Вставляем текст
1341        textarea.value = messageText;
1342        textarea.dispatchEvent(new Event('input', { bubbles: true }));
1343        console.log('Текст вставлен в поле');
1344        
1345        // Сокращено ожидание с 1000 до 500
1346        await sleep(500);
1347        
1348        // Находим кнопку отправки (вторая кнопка с классом om_17_a8)
1349        const sendButtons = document.querySelectorAll('.om_17_a8');
1350        if (sendButtons.length < 2) {
1351            console.error('Кнопка отправки не найдена');
1352            return false;
1353        }
1354        
1355        // Кликаем на вторую кнопку (первая - это прикрепление файла)
1356        const sendButton = sendButtons[1];
1357        sendButton.click();
1358        console.log('Сообщение отправлено (клик выполнен)');
1359        
1360        // Сокращено ожидание с 1000 до 500
1361        await sleep(500);
1362        
1363        return true;
1364    }
1365
1366    // Функция задержки
1367    function sleep(ms) {
1368        return new Promise(resolve => setTimeout(resolve, ms));
1369    }
1370    
1371    // Функция для парсинга даты из текста Ozon
1372    function parseDate(dateText) {
1373        if (!dateText) return null;
1374        
1375        const today = new Date();
1376        today.setHours(0, 0, 0, 0);
1377        
1378        // Если только время (например "11:01", "10:39") - значит сегодня
1379        if (/^\d{1,2}:\d{2}$/.test(dateText)) {
1380            console.log('Формат времени обнаружен:', dateText, '- считаем сегодняшней датой');
1381            return today;
1382        }
1383        
1384        // Если "Сегодня" или "сегодня"
1385        if (dateText.toLowerCase().includes('сегодня')) {
1386            return today;
1387        }
1388        
1389        // Если "Вчера" или "вчера"
1390        if (dateText.toLowerCase().includes('вчера')) {
1391            const yesterday = new Date(today);
1392            yesterday.setDate(yesterday.getDate() - 1);
1393            return yesterday;
1394        }
1395        
1396        // Если формат "28.01" или "21.12" (день.месяц без года)
1397        const shortDateMatch = dateText.match(/^(\d{1,2})\.(\d{1,2})$/);
1398        if (shortDateMatch) {
1399            const day = parseInt(shortDateMatch[1]);
1400            const month = parseInt(shortDateMatch[2]) - 1;
1401            const date = new Date(today.getFullYear(), month, day);
1402            // Если дата в будущем, значит это прошлый год
1403            if (date > today) {
1404                date.setFullYear(date.getFullYear() - 1);
1405            }
1406            console.log('Формат день.месяц обнаружен:', dateText, '- распознано как', date.toLocaleDateString('ru-RU'));
1407            return date;
1408        }
1409        
1410        // Если формат "21 янв" или "21 января"
1411        const monthMatch = dateText.match(/(\d{1,2})\s+(янв|фев|мар|апр|мая|июн|июл|авг|сен|окт|ноя|дек)/i);
1412        if (monthMatch) {
1413            const day = parseInt(monthMatch[1]);
1414            const monthStr = monthMatch[2].toLowerCase();
1415            
1416            const months = {
1417                'янв': 0, 'фев': 1, 'мар': 2, 'апр': 3, 'мая': 4, 'май': 4,
1418                'июн': 5, 'июл': 6, 'авг': 7, 'сен': 8, 'окт': 9, 'ноя': 10, 'дек': 11
1419            };
1420            
1421            const month = months[monthStr.substring(0, 3)];
1422            if (month !== undefined) {
1423                const date = new Date(today.getFullYear(), month, day);
1424                // Если дата в будущем, значит это прошлый год
1425                if (date > today) {
1426                    date.setFullYear(date.getFullYear() - 1);
1427                }
1428                return date;
1429            }
1430        }
1431        
1432        // Если формат "21.01.2024" или "21/01/2024" или "18.06.2025"
1433        const dateMatch = dateText.match(/(\d{1,2})[./](\d{1,2})[./](\d{4})/);
1434        if (dateMatch) {
1435            const day = parseInt(dateMatch[1]);
1436            const month = parseInt(dateMatch[2]) - 1;
1437            const year = parseInt(dateMatch[3]);
1438            return new Date(year, month, day);
1439        }
1440        
1441        console.log('Не удалось распознать дату:', dateText);
1442        return null;
1443    }
1444    
1445    // Функция для обновления статуса в интерфейсе
1446    function updateStatus(statusText, progressText = '') {
1447        const statusBlock = document.getElementById('statusBlock');
1448        const statusTextElement = document.getElementById('statusText');
1449        const progressTextElement = document.getElementById('progressText');
1450        
1451        if (statusBlock && statusTextElement) {
1452            statusBlock.style.display = 'block';
1453            statusTextElement.textContent = statusText;
1454            if (progressTextElement && progressText) {
1455                progressTextElement.textContent = progressText;
1456            }
1457        }
1458    }
1459    
1460    // Функция для открытия модального окна
1461    function openModal() {
1462        const modal = document.getElementById('bulkSenderModal');
1463        if (modal) {
1464            modal.style.display = 'flex';
1465            console.log('Модальное окно открыто');
1466            // Обновляем статистику при открытии
1467            updateDatabaseStats();
1468        }
1469    }
1470    
1471    // Функция для закрытия модального окна
1472    function closeModal() {
1473        const modal = document.getElementById('bulkSenderModal');
1474        if (modal) {
1475            modal.style.display = 'none';
1476            console.log('Модальное окно закрыто');
1477        }
1478    }
1479    
1480    // Функция переключения паузы
1481    function togglePause() {
1482        isPaused = !isPaused;
1483        const pauseButton = document.getElementById('pauseButton');
1484        
1485        if (isPaused) {
1486            pauseButton.textContent = '▶️ Продолжить';
1487            pauseButton.style.background = '#4caf50';
1488            console.log('Рассылка приостановлена');
1489        } else {
1490            pauseButton.textContent = '⏸️ Пауза';
1491            pauseButton.style.background = '#ff9800';
1492            console.log('Рассылка возобновлена');
1493        }
1494    }
1495    
1496    // Функция остановки рассылки
1497    function stopBulkSending() {
1498        if (confirm('Вы уверены, что хотите остановить рассылку?')) {
1499            shouldStop = true;
1500            isPaused = false;
1501            console.log('Запрошена остановка рассылки');
1502        }
1503    }
1504
1505    // Основная функция рассылки
1506    async function startBulkSending() {
1507        console.log('=== ФУНКЦИЯ startBulkSending ВЫЗВАНА ===');
1508        
1509        // Сразу меняем цвет кнопки для визуальной обратной связи
1510        const startButton = document.getElementById('startButton');
1511        if (startButton) {
1512            startButton.style.background = '#ff9800';
1513            startButton.textContent = 'Запуск...';
1514            startButton.disabled = true;
1515        }
1516        
1517        const messageText = document.getElementById('messageText').value.trim();
1518        const filterDateInput = document.getElementById('filterDate').value;
1519        const testModeCheckbox = document.getElementById('testMode');
1520        const isTestMode = testModeCheckbox ? testModeCheckbox.checked : false;
1521        
1522        // Получаем выбранный режим рассылки
1523        const sendingMode = document.querySelector('input[name="sendingMode"]:checked')?.value || 'database';
1524        
1525        console.log('Текст сообщения:', messageText);
1526        console.log('Дата фильтра:', filterDateInput);
1527        console.log('Тестовый режим:', isTestMode);
1528        console.log('Режим рассылки:', sendingMode);
1529        
1530        if (!messageText) {
1531            alert('Введите текст сообщения');
1532            console.log('Остановка: нет текста сообщения');
1533            // Возвращаем кнопку в исходное состояние
1534            if (startButton) {
1535                startButton.style.background = '#0066cc';
1536                startButton.textContent = 'Запустить';
1537                startButton.disabled = false;
1538            }
1539            return;
1540        }
1541        
1542        if (!filterDateInput) {
1543            alert('Выберите дату фильтра');
1544            console.log('Остановка: нет даты фильтра');
1545            // Возвращаем кнопку в исходное состояние
1546            if (startButton) {
1547                startButton.style.background = '#0066cc';
1548                startButton.textContent = 'Запустить';
1549                startButton.disabled = false;
1550            }
1551            return;
1552        }
1553        
1554        // Выбираем функцию в зависимости от режима
1555        if (sendingMode === 'visible') {
1556            await startBulkSendingByVisibleChats(messageText, filterDateInput, isTestMode);
1557        } else {
1558            await startBulkSendingByDatabase(messageText, filterDateInput, isTestMode);
1559        }
1560    }
1561    
1562    // Функция рассылки по базе данных
1563    async function startBulkSendingByDatabase(messageText, filterDateInput, isTestMode) {
1564        console.log('=== РАССЫЛКА ПО БАЗЕ ДАННЫХ ===');
1565        
1566        // Загружаем базу данных
1567        const db = await loadContactsDatabase();
1568        const totalContacts = Object.keys(db.contacts).length;
1569        
1570        if (totalContacts === 0) {
1571            alert('База контактов пуста! Сначала соберите базу контактов с помощью кнопки "Собрать базу".');
1572            // Возвращаем кнопку в исходное состояние
1573            const startButton = document.getElementById('startButton');
1574            if (startButton) {
1575                startButton.style.background = '#0066cc';
1576                startButton.textContent = 'Запустить';
1577                startButton.disabled = false;
1578            }
1579            return;
1580        }
1581        
1582        const filterDate = new Date(filterDateInput);
1583        filterDate.setHours(23, 59, 59, 999); // Устанавливаем конец дня для корректного сравнения
1584        
1585        console.log('Всего контактов в базе:', totalContacts);
1586        console.log('Фильтр по дате:', filterDate.toLocaleDateString('ru-RU'));
1587        console.log('Тестовый режим:', isTestMode ? 'ДА (только 1 сообщение)' : 'НЕТ');
1588        
1589        // Фильтруем контакты по дате
1590        const contactsToSend = Object.values(db.contacts).filter(contact => {
1591            if (!contact.lastMessageDate) {
1592                console.log('❌ Контакт без даты:', contact.name);
1593                return false;
1594            }
1595            const contactDate = new Date(contact.lastMessageDate);
1596            const matches = contactDate <= filterDate;
1597            console.log(`${matches ? '✅' : '❌'} ${contact.name}: ${contactDate.toLocaleDateString('ru-RU')} ${matches ? '<=' : '>'} ${filterDate.toLocaleDateString('ru-RU')}`);
1598            return matches;
1599        });
1600        
1601        console.log('Контактов подходящих по дате:', contactsToSend.length);
1602        
1603        if (contactsToSend.length === 0) {
1604            alert(`Нет контактов, подходящих по указанной дате.\n\nВсего в базе: ${totalContacts}\nС датами: ${Object.values(db.contacts).filter(c => c.lastMessageDate).length}\nПодходящих по фильтру (дата <= ${filterDate.toLocaleDateString('ru-RU')}): 0`);
1605            // Возвращаем кнопку в исходное состояние
1606            const startButton = document.getElementById('startButton');
1607            if (startButton) {
1608                startButton.style.background = '#0066cc';
1609                startButton.textContent = 'Запустить';
1610                startButton.disabled = false;
1611            }
1612            return;
1613        }
1614        
1615        // Показываем статистику фильтрации
1616        const filterStats = `Всего в базе: ${totalContacts} | Подходящих по фильтру: ${contactsToSend.length}`;
1617        console.log(filterStats);
1618        
1619        // Показываем пользователю статистику перед запуском
1620        if (!confirm(`Готово к запуску!\n\nВсего контактов в базе: ${totalContacts}\nПодходящих по фильтру (дата <= ${filterDate.toLocaleDateString('ru-RU')}): ${contactsToSend.length}\n\nТестовый режим: ${isTestMode ? 'ДА (1 сообщение)' : 'НЕТ'}\n\nНачать рассылку?`)) {
1621            console.log('Рассылка отменена пользователем');
1622            // Возвращаем кнопку в исходное состояние
1623            const startButton = document.getElementById('startButton');
1624            if (startButton) {
1625                startButton.style.background = '#0066cc';
1626                startButton.textContent = 'Запустить';
1627                startButton.disabled = false;
1628            }
1629            return;
1630        }
1631        
1632        // Сохраняем состояние рассылки
1633        const sendingState = {
1634            isActive: true,
1635            messageText: messageText,
1636            filterDate: filterDate.toISOString(), // Сохраняем дату фильтра
1637            testMode: isTestMode,
1638            contactsList: contactsToSend,
1639            totalContacts: contactsToSend.length,
1640            processed: 0,
1641            sent: 0,
1642            skipped: 0,
1643            currentContactId: contactsToSend[0].id,
1644            firstChatOpened: false // Флаг, что первый чат еще не открыт
1645        };
1646        
1647        await saveSendingState(sendingState);
1648        console.log('Состояние рассылки сохранено, запускаем мониторинг');
1649        
1650        // Запускаем мониторинг прогресса (он откроет первый чат)
1651        console.log('Запускаем мониторинг прогресса рассылки');
1652        monitorSendingProgress();
1653    }
1654    
1655    // Функция рассылки по видимым чатам
1656    async function startBulkSendingByVisibleChats(messageText, filterDateInput, isTestMode) {
1657        console.log('=== РАССЫЛКА ПО ВИДИМЫМ ЧАТАМ ===');
1658        
1659        const filterDate = new Date(filterDateInput);
1660        filterDate.setHours(23, 59, 59, 999);
1661        
1662        console.log('Фильтр по дате:', filterDate);
1663        console.log('Тестовый режим:', isTestMode ? 'ДА (только 1 сообщение)' : 'НЕТ');
1664        
1665        // Загружаем черный список
1666        const blacklist = await loadBlacklist();
1667        console.log('Загружен черный список:', blacklist.length, 'ID');
1668        
1669        // Меняем интерфейс
1670        isPaused = false;
1671        shouldStop = false;
1672        
1673        const testModeCheckbox = document.getElementById('testMode');
1674        document.getElementById('startButton').style.display = 'none';
1675        document.getElementById('pauseButton').style.display = 'inline-block';
1676        document.getElementById('stopButton').style.display = 'inline-block';
1677        document.getElementById('messageText').disabled = true;
1678        document.getElementById('filterDate').disabled = true;
1679        if (testModeCheckbox) testModeCheckbox.disabled = true;
1680        
1681        // Блокируем радио-кнопки режима
1682        document.querySelectorAll('input[name="sendingMode"]').forEach(radio => radio.disabled = true);
1683        
1684        updateStatus(
1685            'Запуск рассылки по видимым чатам...',
1686            'Подготовка к рассылке...'
1687        );
1688        
1689        let processed = 0;
1690        let sent = 0;
1691        let skipped = 0;
1692        let skippedBlacklist = 0;
1693        
1694        // Трекер обработанных ID в этой сессии
1695        const processedChatIds = new Set();
1696        
1697        // Счетчик попыток без новых чатов
1698        let noNewChatsCount = 0;
1699        const maxNoNewChatsAttempts = 3;
1700        
1701        // Счетчик обработанных чатов с момента последнего скролла
1702        let chatsProcessedSinceScroll = 0;
1703        const scrollAfterChats = 10; // Скроллим каждые 10 обработанных чатов
1704        
1705        while (!shouldStop) {
1706            // Проверка на паузу
1707            while (isPaused && !shouldStop) {
1708                updateStatus('Пауза', `Обработано: ${processed}, Отправлено: ${sent}, Пропущено: ${skipped}, ЧС: ${skippedBlacklist}`);
1709                await sleep(500);
1710            }
1711            
1712            if (shouldStop) {
1713                updateStatus('Остановлено пользователем', `Обработано: ${processed}, Отправлено: ${sent}, Пропущено: ${skipped}, ЧС: ${skippedBlacklist}`);
1714                break;
1715            }
1716            
1717            // Проверка тестового режима
1718            if (isTestMode && sent >= 1) {
1719                console.log('Тестовый режим: собран 1 контакт, останавливаем рассылку');
1720                shouldStop = true;
1721                break;
1722            }
1723            
1724            // Проверка режима обновления
1725            if (updateExisting && existingContactIds) {
1726                const remainingToUpdate = Array.from(existingContactIds).filter(id => !recentlyProcessedIds.has(id));
1727                console.log('Осталось обновить контактов:', remainingToUpdate.length, 'из', existingContactIds.size);
1728                
1729                if (remainingToUpdate.length === 0) {
1730                    console.log('Все контакты из базы обновлены, завершаем');
1731                    break;
1732                }
1733            }
1734            
1735            // Получаем текущие видимые чаты
1736            const currentChats = getAllChats();
1737            console.log('Найдено видимых чатов в DOM:', currentChats.length);
1738            
1739            // Фильтруем только те чаты, которые еще не обработали
1740            const newChats = currentChats.filter(chat => {
1741                const chatId = extractChatId(chat);
1742                if (!chatId) return false;
1743                
1744                // Проверяем кольцевой буфер последних ID
1745                if (recentlyProcessedIds.has(chatId)) return false;
1746                
1747                return true;
1748            });
1749            
1750            console.log('Новых необработанных чатов:', newChats.length);
1751            console.log('В кольцевом буфере ID:', recentlyProcessedIds.size);
1752            
1753            // Если нет новых чатов - увеличиваем счетчик
1754            if (newChats.length === 0) {
1755                noNewChatsCount++;
1756                console.log(`Новые чаты не появились (${noNewChatsCount}/${maxNoNewChatsAttempts})`);
1757                
1758                if (noNewChatsCount >= maxNoNewChatsAttempts) {
1759                    console.log('Достигнут конец списка чатов (новые чаты не подгружаются после', maxNoNewChatsAttempts, 'попыток)');
1760                    break;
1761                }
1762                
1763                // Скроллим список чатов для подгрузки новых
1764                console.log('Нет новых чатов, скроллим для подгрузки...');
1765                
1766                // ОБНОВЛЯЕМ СТАТУС ПЕРЕД СКРОЛЛОМ
1767                updateStatus(
1768                    'Сбор видимых чатов: прокрутка списка...',
1769                    `Обработано: ${processed}, Отправлено: ${sent}, Пропущено: ${skipped}, ЧС: ${skippedBlacklist}`
1770                );
1771                
1772                await scrollChatList();
1773                await sleep(3000); // УМЕНЬШЕНО с 5000 до 3000
1774                scrollAttempts++;
1775                continue;
1776            } else {
1777                noNewChatsCount = 0;
1778            }
1779            
1780            // Обрабатываем новые чаты
1781            for (const chat of newChats) {
1782                // Проверка на паузу
1783                while (isPaused && !shouldStop) {
1784                    updateStatus('Пауза', `Обработано: ${processed}, Отправлено: ${sent}, Пропущено: ${skipped}, ЧС: ${skippedBlacklist}`);
1785                    await sleep(500);
1786                }
1787                
1788                // Проверка на остановку
1789                if (shouldStop) {
1790                    break;
1791                }
1792                
1793                // Проверка тестового режима
1794                if (isTestMode && sent >= 1) {
1795                    console.log('Тестовый режим: собран 1 контакт, останавливаем рассылку');
1796                    shouldStop = true;
1797                    break;
1798                }
1799                
1800                const chatId = extractChatId(chat);
1801                const chatName = chat.querySelector('.index_chatTitle_TiXTq')?.textContent || 
1802                               chat.querySelector('.index_chatTitle_x9txX')?.textContent || 
1803                               'Неизвестно';
1804                
1805                // Добавляем ID в кольцевой буфер
1806                const isNew = addToRecentIds(chatId);
1807                if (!isNew) {
1808                    console.log('Чат уже обработан (в буфере):', chatName);
1809                    continue;
1810                }
1811                
1812                // Проверяем черный список
1813                if (blacklist.includes(chatId)) {
1814                    console.log(`⛔ Чат в черном списке, пропускаем: ${chatName} (${chatId})`);
1815                    skippedBlacklist++;
1816                    continue;
1817                }
1818                
1819                updateStatus(
1820                    `Обработка чата: ${chatName}`,
1821                    `Обработано: ${processed}, Отправлено: ${sent}, Пропущено: ${skipped}, ЧС: ${skippedBlacklist}`
1822                );
1823                
1824                console.log(`[${processed + 1}] Обрабатываем чат: ${chatName} (${chatId})`);
1825                
1826                // Кликаем по чату
1827                await clickChat(chat);
1828                
1829                // Получаем дату последнего сообщения
1830                const lastMessageDate = getLastMessageDate();
1831                
1832                if (!lastMessageDate) {
1833                    console.log('Не удалось получить дату, пропускаем чат');
1834                    skipped++;
1835                    processed++;
1836                    chatsProcessedSinceScroll++;
1837                    continue;
1838                }
1839                
1840                // Проверяем дату перед отправкой - сравниваем реальную дату из чата с фильтром
1841                let shouldSend = false;
1842                if (lastMessageDate && filterDate) {
1843                    console.log('Сравнение дат:');
1844                    console.log('  Дата из чата:', lastMessageDate.toLocaleDateString('ru-RU'));
1845                    console.log('  Дата фильтра:', filterDate.toLocaleDateString('ru-RU'));
1846                    console.log('  Дата из чата <= Дата фильтра?', lastMessageDate <= filterDate);
1847                    
1848                    // Отправляем только если дата последнего сообщения <= дате фильтра
1849                    shouldSend = lastMessageDate <= filterDate;
1850                } else {
1851                    console.log('Не удалось получить дату или фильтр, пропускаем отправку');
1852                    shouldSend = false;
1853                }
1854                
1855                // Отправляем сообщение только если дата подходит
1856                let success = false;
1857                if (shouldSend) {
1858                    console.log('✅ Дата подходит, отправляем сообщение');
1859                    success = await sendMessage(messageText);
1860                } else {
1861                    console.log('⏭️ Пропускаем отправку (дата не подходит)');
1862                    success = false;
1863                }
1864                
1865                if (success) {
1866                    sent++;
1867                    console.log(`✅ Сообщение отправлено в чат: ${chatName}`);
1868                    
1869                    // Обновляем базу данных
1870                    const db = await loadContactsDatabase();
1871                    if (db.contacts[chatId]) {
1872                        db.contacts[chatId].lastSentDate = new Date().toISOString();
1873                        db.contacts[chatId].messageCount = (db.contacts[chatId].messageCount || 0) + 1;
1874                        await saveContactsDatabase(db);
1875                    }
1876                } else {
1877                    console.error('❌ Ошибка отправки или дата не подходит');
1878                    skipped++;
1879                }
1880                
1881                processed++;
1882                chatsProcessedSinceScroll++;
1883                
1884                // Скроллим список чатов каждые N обработанных чатов для подгрузки новых
1885                if (chatsProcessedSinceScroll >= scrollAfterChats) {
1886                    console.log(`Обработано ${chatsProcessedSinceScroll} чатов, скроллим для подгрузки новых...`);
1887                    await scrollChatList();
1888                    chatsProcessedSinceScroll = 0;
1889                }
1890                
1891                // Пауза между чатами - сокращено с 1000 до 500
1892                await sleep(500);
1893            }
1894            
1895            // Скроллим для подгрузки следующей порции
1896            if (!shouldStop && newChats.length > 0) {
1897                console.log('Обработана порция чатов, скроллим для подгрузки следующей...');
1898                await scrollChatList();
1899                // ВАЖНО: Ждем дольше, чтобы новые чаты успели отрендериться в DOM
1900                console.log('Ждем 5 секунд для рендеринга новых чатов...');
1901                await sleep(5000);
1902            }
1903        }
1904        
1905        // Завершение
1906        isPaused = false;
1907        shouldStop = false;
1908        
1909        document.getElementById('startButton').style.display = 'inline-block';
1910        document.getElementById('pauseButton').style.display = 'none';
1911        document.getElementById('stopButton').style.display = 'none';
1912        document.getElementById('messageText').disabled = false;
1913        document.getElementById('filterDate').disabled = false;
1914        if (testModeCheckbox) testModeCheckbox.disabled = false;
1915        document.querySelectorAll('input[name="sendingMode"]').forEach(radio => radio.disabled = false);
1916        
1917        updateStatus(
1918            'Рассылка завершена!',
1919            `Всего обработано: ${processed} | Отправлено: ${sent} | Пропущено: ${skipped} | Черный список: ${skippedBlacklist}`
1920        );
1921        
1922        console.log('=== РАССЫЛКА ЗАВЕРШЕНА ===');
1923        console.log('Обработано:', processed);
1924        console.log('Отправлено:', sent);
1925        console.log('Пропущено:', skipped);
1926        console.log('Пропущено (ЧС):', skippedBlacklist);
1927    }
1928
1929    // Функция для создания плавающей кнопки
1930    function createFloatingButton() {
1931        // Проверяем, не создана ли уже кнопка
1932        if (document.getElementById('bulkSenderFloatingBtn')) {
1933            return;
1934        }
1935        
1936        const button = document.createElement('button');
1937        button.id = 'bulkSenderFloatingBtn';
1938        button.textContent = '📧 Рассылка';
1939        button.style.cssText = `
1940            position: fixed;
1941            bottom: 20px;
1942            right: 20px;
1943            padding: 15px 25px;
1944            background: #0066cc;
1945            color: white;
1946            border: none;
1947            border-radius: 50px;
1948            cursor: pointer;
1949            font-size: 16px;
1950            font-weight: 600;
1951            box-shadow: 0 4px 12px rgba(0, 102, 204, 0.4);
1952            z-index: 9999;
1953            transition: all 0.3s;
1954        `;
1955        
1956        button.addEventListener('mouseenter', () => {
1957            button.style.background = '#0052a3';
1958            button.style.transform = 'scale(1.05)';
1959            button.style.boxShadow = '0 6px 16px rgba(0, 102, 204, 0.6)';
1960        });
1961        
1962        button.addEventListener('mouseleave', () => {
1963            button.style.background = '#0066cc';
1964            button.style.transform = 'scale(1)';
1965            button.style.boxShadow = '0 4px 12px rgba(0, 102, 204, 0.4)';
1966        });
1967        
1968        button.addEventListener('click', openModal);
1969        
1970        document.body.appendChild(button);
1971        console.log('Плавающая кнопка создана');
1972    }
1973    
1974    // Функция мониторинга прогресса рассылки в главной вкладке
1975    async function monitorSendingProgress() {
1976        console.log('🚀 Запуск мониторинга прогресса рассылки');
1977        
1978        let lastProcessed = -1;
1979        let lastOpenedIndex = -1; // Индекс последнего открытого чата
1980        const maxParallelChats = 3; // Максимум одновременно открытых чатов
1981        
1982        const monitorInterval = setInterval(async () => {
1983            const currentState = await loadSendingState();
1984            
1985            if (!currentState || !currentState.isActive) {
1986                console.log('Рассылка завершена или остановлена, останавливаем мониторинг');
1987                clearInterval(monitorInterval);
1988                
1989                // Обновляем интерфейс
1990                const finalStats = currentState ? 
1991                    `Обработано: ${currentState.processed}/${currentState.totalContacts} | Отправлено: ${currentState.sent} | Пропущено: ${currentState.skipped}` :
1992                    'Проверьте результаты';
1993                updateStatus('Рассылка завершена!', finalStats);
1994                await updateDatabaseStats();
1995                
1996                // Разблокируем интерфейс
1997                isPaused = false;
1998                document.getElementById('startButton').style.display = 'inline-block';
1999                document.getElementById('startButton').disabled = false;
2000                document.getElementById('startButton').style.background = '#0066cc';
2001                document.getElementById('startButton').textContent = 'Запустить';
2002                document.getElementById('pauseButton').style.display = 'none';
2003                document.getElementById('stopButton').style.display = 'none';
2004                document.getElementById('messageText').disabled = false;
2005                document.getElementById('filterDate').disabled = false;
2006                const testModeCheckbox = document.getElementById('testMode');
2007                if (testModeCheckbox) testModeCheckbox.disabled = false;
2008                document.querySelectorAll('input[name="sendingMode"]').forEach(radio => radio.disabled = false);
2009                
2010                return;
2011            }
2012            
2013            console.log('🔍 Проверка состояния:', {
2014                processed: currentState.processed,
2015                totalContacts: currentState.totalContacts,
2016                lastOpenedIndex: lastOpenedIndex,
2017                maxParallel: maxParallelChats
2018            });
2019            
2020            // Обновляем статус только если изменился прогресс
2021            if (currentState.processed !== lastProcessed) {
2022                lastProcessed = currentState.processed;
2023                
2024                const currentContact = currentState.contactsList[currentState.processed - 1];
2025                const contactName = currentContact ? currentContact.name : 'Неизвестно';
2026                
2027                updateStatus(
2028                    `Обработка: ${contactName}`,
2029                    `Обработано: ${currentState.processed}/${currentState.totalContacts} | Отправлено: ${currentState.sent} | Пропущено: ${currentState.skipped}`
2030                );
2031                
2032                console.log('📊 Обновлен прогресс:', currentState.processed, '/', currentState.totalContacts);
2033            }
2034            
2035            // Открываем чаты параллельно
2036            // Логика: открываем до maxParallelChats чатов вперед от текущего processed
2037            const remainingContacts = currentState.totalContacts - currentState.processed;
2038            const nextIndexToOpen = lastOpenedIndex + 1;
2039            
2040            // ИСПРАВЛЕНО: Правильный расчет - открываем чаты так, чтобы всегда было maxParallelChats впереди
2041            // Сколько чатов должно быть открыто впереди текущего processed
2042            const targetOpenAhead = Math.min(maxParallelChats, remainingContacts);
2043            // Сколько чатов уже открыто впереди (от processed до lastOpenedIndex включительно)
2044            const currentlyOpenAhead = Math.max(0, lastOpenedIndex - currentState.processed + 1);
2045            // Сколько еще нужно открыть
2046            const needToOpen = targetOpenAhead - currentlyOpenAhead;
2047            // Сколько осталось до конца списка
2048            const chatsUntilEnd = currentState.totalContacts - nextIndexToOpen;
2049            
2050            const chatsToOpen = Math.max(0, Math.min(needToOpen, chatsUntilEnd));
2051            
2052            console.log('Расчет открытия чатов:', {
2053                processed: currentState.processed,
2054                lastOpened: lastOpenedIndex,
2055                nextToOpen: nextIndexToOpen,
2056                targetOpenAhead: targetOpenAhead,
2057                currentlyOpenAhead: currentlyOpenAhead,
2058                needToOpen: needToOpen,
2059                chatsUntilEnd: chatsUntilEnd,
2060                willOpen: chatsToOpen
2061            });
2062            
2063            if (chatsToOpen > 0) {
2064                for (let i = 0; i < chatsToOpen; i++) {
2065                    const contactIndex = nextIndexToOpen + i;
2066                    const contact = currentState.contactsList[contactIndex];
2067                    
2068                    if (contact) {
2069                        console.log('📂 Открываем чат [', contactIndex, ']:', contact.name, '(', contact.id, ')');
2070                        lastOpenedIndex = contactIndex;
2071                        await openChatById(contact.id);
2072                        await sleep(200); // Небольшая задержка между открытием вкладок
2073                    }
2074                }
2075            }
2076        }, 1000); // Проверяем каждую секунду
2077    }
2078
2079    // Функция для создания фильтра по дате в списке чатов
2080    function createDateFilter() {
2081        // Проверяем, не создан ли уже фильтр
2082        if (document.getElementById('dateFilterContainer')) {
2083            return;
2084        }
2085        
2086        // Ищем контейнер с поиском
2087        const searchContainer = document.querySelector('.om_1_f0')?.parentElement;
2088        if (!searchContainer) {
2089            console.log('Контейнер для фильтра не найден');
2090            return;
2091        }
2092        
2093        // Создаем контейнер для фильтра
2094        const filterContainer = document.createElement('div');
2095        filterContainer.id = 'dateFilterContainer';
2096        filterContainer.style.cssText = `
2097            padding: 15px;
2098            background: #f0f8ff;
2099            border-bottom: 1px solid #ddd;
2100            display: flex;
2101            gap: 10px;
2102            align-items: center;
2103            flex-wrap: wrap;
2104        `;
2105        
2106        filterContainer.innerHTML = `
2107            <label style="font-size: 13px; font-weight: 600; color: #555;">Фильтр по дате последнего сообщения:</label>
2108            <input type="date" id="dateFilterFrom" style="width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px;">
2109            <span style="color: #555;"></span>
2110            <input type="date" id="dateFilterTo" style="width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px;">
2111            <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>
2112            <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>
2113        `;
2114        
2115        // Вставляем фильтр перед списком чатов
2116        const chatList = document.querySelector('.om_1_f0');
2117        if (chatList && chatList.parentElement) {
2118            chatList.parentElement.insertBefore(filterContainer, chatList);
2119            console.log('Фильтр по дате создан');
2120            
2121            // Обработчики событий
2122            document.getElementById('applyDateFilter').addEventListener('click', applyDateFilter);
2123            document.getElementById('clearDateFilter').addEventListener('click', clearDateFilter);
2124        }
2125    }
2126
2127    // Функция применения фильтра по дате
2128    async function applyDateFilter() {
2129        const dateFrom = document.getElementById('dateFilterFrom').value;
2130        const dateTo = document.getElementById('dateFilterTo').value;
2131        
2132        if (!dateFrom || !dateTo) {
2133            alert('Выберите обе даты для фильтрации');
2134            return;
2135        }
2136        
2137        const filterFrom = new Date(dateFrom);
2138        const filterTo = new Date(dateTo);
2139        
2140        console.log('Применение фильтра по дате:', filterFrom, '-', filterTo);
2141        
2142        // Показываем индикатор загрузки
2143        const applyButton = document.getElementById('applyDateFilter');
2144        const originalText = applyButton.textContent;
2145        applyButton.textContent = 'Фильтрация...';
2146        applyButton.disabled = true;
2147        
2148        // Получаем все чаты
2149        const allChats = document.querySelectorAll('.index_chat_4fr82');
2150        let visibleCount = 0;
2151        let hiddenCount = 0;
2152        
2153        allChats.forEach(chat => {
2154            // Ищем дату в самом элементе чата (не открывая его)
2155            const dateElement = chat.querySelector('.index_chatDate_z4mNc');
2156            const dateText = dateElement?.textContent?.trim();
2157            
2158            const chatDate = parseDate(dateText);
2159            
2160            if (chatDate) {
2161                // Проверяем, попадает ли дата в диапазон
2162                if (chatDate >= filterFrom && chatDate <= filterTo) {
2163                    // Дата подходит - оставляем чат видимым
2164                    chat.style.display = 'grid';
2165                    visibleCount++;
2166                    console.log('Чат подходит:', chat.querySelector('.index_chatTitle_TiXTq')?.textContent, chatDate);
2167                } else {
2168                    // Дата не подходит - скрываем чат
2169                    chat.style.display = 'none';
2170                    hiddenCount++;
2171                }
2172            } else {
2173                // Не удалось распознать дату - оставляем видимым
2174                chat.style.display = 'grid';
2175                visibleCount++;
2176            }
2177        });
2178        
2179        // Восстанавливаем кнопку
2180        applyButton.textContent = originalText;
2181        applyButton.disabled = false;
2182        
2183        // Показываем кнопку сброса
2184        document.getElementById('clearDateFilter').style.display = 'inline-block';
2185        
2186        console.log(`Фильтр применен. Показано: ${visibleCount}, Скрыто: ${hiddenCount}`);
2187        alert(`Фильтр применен!\nПоказано чатов: ${visibleCount}\nСкрыто чатов: ${hiddenCount}`);
2188    }
2189
2190    // Функция сброса фильтра
2191    function clearDateFilter() {
2192        // Показываем все чаты
2193        const allChats = document.querySelectorAll('.index_chat_4fr82');
2194        allChats.forEach(chat => {
2195            chat.style.display = 'grid';
2196        });
2197        
2198        // Очищаем поля
2199        document.getElementById('dateFilterFrom').value = '';
2200        document.getElementById('dateFilterTo').value = '';
2201        
2202        // Скрываем кнопку сброса
2203        document.getElementById('clearDateFilter').style.display = 'none';
2204        
2205        console.log('Фильтр сброшен');
2206    }
2207
2208    // Функция для обновления статистики базы данных в интерфейсе
2209    async function updateDatabaseStats() {
2210        const db = await loadContactsDatabase();
2211        const stats = getDatabaseStats(db);
2212        
2213        console.log('Обновление статистики базы данных:', stats);
2214        
2215        // Ждем, пока элемент появится в DOM (максимум 5 секунд)
2216        let attempts = 0;
2217        const maxAttempts = 50;
2218        
2219        while (attempts < maxAttempts) {
2220            const statsBlock = document.getElementById('dbStatsBlock');
2221            
2222            if (statsBlock) {
2223                const lastUpdateText = stats.lastUpdate 
2224                    ? new Date(stats.lastUpdate).toLocaleString('ru-RU')
2225                    : 'Никогда';
2226                
2227                statsBlock.innerHTML = `
2228                    <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px;">
2229                        <div><strong>Всего контактов:</strong> ${stats.total}</div>
2230                        <div><strong>С датами:</strong> ${stats.withDates}</div>
2231                        <div><strong>Отправлено:</strong> ${stats.sent}</div>
2232                        <div><strong>Не отправлено:</strong> ${stats.notSent}</div>
2233                    </div>
2234                    <div style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #e0e0e0; font-size: 12px; color: #999;">
2235                        Последнее обновление: ${lastUpdateText}
2236                    </div>
2237                `;
2238                console.log('Статистика обновлена в интерфейсе');
2239                return true;
2240            }
2241            
2242            // Ждем 100мс перед следующей попыткой
2243            await sleep(100);
2244            attempts++;
2245        }
2246        
2247        console.error('Элемент dbStatsBlock не найден после', maxAttempts, 'попыток!');
2248        return false;
2249    }
2250
2251    // Функция для фильтрации списка контактов
2252    function filterContactsList(searchQuery) {
2253        loadAndDisplayContacts(searchQuery);
2254    }
2255
2256    // ============= ФУНКЦИИ ДЛЯ СБОРА КОНТАКТОВ =============
2257    
2258    // Функция для извлечения ID из URL чата
2259    function extractChatId(chatElement) {
2260        // Ищем атрибут deeplink в элементе чата
2261        const deeplink = chatElement.getAttribute('deeplink');
2262        if (deeplink) {
2263            // Пробуем оба формата:
2264            // 1. Обычный: ?group=customers_v2&id=ec4ee215-e8f6-49bd-968a-8334a98d8aa7
2265            // 2. Поиск: /communications/chats/chat?id=3d7d80d4-5da1-4cb1-8221-2e332a048d39&context=dialog_list
2266            const match = deeplink.match(/[?&]id=([a-f0-9-]+)/);
2267            if (match) {
2268                console.log('Извлечен ID из deeplink:', match[1]);
2269                return match[1];
2270            }
2271        }
2272        
2273        // Запасной вариант - ищем ссылку внутри элемента
2274        const link = chatElement.querySelector('a[href*="id="]');
2275        if (link) {
2276            const href = link.getAttribute('href');
2277            const match = href.match(/[?&]id=([a-f0-9-]+)/);
2278            if (match) {
2279                console.log('Извлечен ID из ссылки:', match[1]);
2280                return match[1];
2281            }
2282        }
2283        
2284        console.log('Не удалось извлечь ID чата');
2285        return null;
2286    }
2287    
2288    // Функция для получения данных чата из элемента списка
2289    function getChatDataFromElement(chatElement) {
2290        const chatName = chatElement.querySelector('.index_chatTitle_TiXTq')?.textContent?.trim() || 
2291                        chatElement.querySelector('.index_chatTitle_x9txX')?.textContent?.trim() || 
2292                        'Неизвестно';
2293        
2294        // Ищем элемент с датой - используем правильный селектор
2295        // Для обычных чатов: .index_chatDate_z4mNc
2296        // Для результатов поиска: .index_chatDate_WJ-+mb
2297        const dateElement = chatElement.querySelector('.index_chatDate_z4mNc, .index_chatDate_WJ-+mb');
2298        const dateText = dateElement?.textContent?.trim();
2299        
2300        console.log('📋 Получение данных чата:', chatName);
2301        console.log('   Элемент даты найден:', !!dateElement);
2302        console.log('   Текст даты:', dateText || 'НЕТ');
2303        
2304        const lastMessageDate = dateText ? parseDate(dateText) : null;
2305        
2306        if (lastMessageDate) {
2307            console.log('   ✅ Дата распознана:', lastMessageDate.toLocaleDateString('ru-RU'));
2308        } else {
2309            console.log('   ❌ Дата НЕ распознана');
2310        }
2311        
2312        return {
2313            name: chatName,
2314            lastMessageDate: lastMessageDate ? lastMessageDate.toISOString() : null,
2315            lastSentDate: null,
2316            messageCount: 0
2317        };
2318    }
2319    
2320    // Функция для скролла списка чатов
2321    async function scrollChatList() {
2322        console.log('=== НАЧАЛО СКРОЛЛА СПИСКА ЧАТОВ ===');
2323        
2324        // Проверяем, применен ли фильтр Ozon
2325        const clearFilterBtn = document.querySelector('.c8s110-a2, .c8s110-c0, .c8s110-a4, .c8s110-a5');
2326        const hasOzonFilter = !!clearFilterBtn;
2327        
2328        console.log('Фильтр Ozon активен:', hasOzonFilter);
2329        
2330        // ВАЖНО: Находим ПРАВИЛЬНЫЙ элемент списка чатов (тот, который реально скроллится)
2331        // Может быть несколько элементов с классом .om_1_f0, нам нужен тот, у которого scrollHeight > clientHeight
2332        const allChatLists = document.querySelectorAll('.om_1_f0');
2333        console.log('Найдено элементов .om_1_f0:', allChatLists.length);
2334        
2335        const chatList = Array.from(allChatLists).find(el => el.scrollHeight > el.clientHeight);
2336        
2337        if (!chatList) {
2338            console.error('Скроллируемый список чатов не найден');
2339            // Пробуем скроллить window как запасной вариант
2340            console.log('Пробуем скроллить window...');
2341            window.scrollBy({ top: 500, behavior: 'smooth' });
2342            await sleep(5000);
2343            return true;
2344        }
2345        
2346        console.log('Найден скроллируемый список чатов:', {
2347            scrollTop: chatList.scrollTop,
2348            scrollHeight: chatList.scrollHeight,
2349            clientHeight: chatList.clientHeight
2350        });
2351        
2352        // Сохраняем текущую позицию скролла
2353        const oldScrollTop = chatList.scrollTop;
2354        console.log('Текущий scrollTop:', oldScrollTop);
2355        
2356        // АГРЕССИВНЫЙ СКРОЛЛ: Скроллим сразу к концу списка
2357        console.log('Выполняем агрессивный скролл к концу списка...');
2358        const targetScroll = chatList.scrollHeight - chatList.clientHeight;
2359        console.log('Целевой scrollTop:', targetScroll);
2360        
2361        // Скроллим большими шагами к концу
2362        const steps = 5;
2363        const stepSize = Math.ceil((targetScroll - oldScrollTop) / steps);
2364        
2365        for (let i = 0; i < steps; i++) {
2366            chatList.scrollTop += stepSize;
2367            console.log(`Агрессивный скролл ${i + 1}/${steps}: scrollTop =`, chatList.scrollTop);
2368            await sleep(300); // Небольшая задержка между шагами
2369        }
2370        
2371        // Финальный скролл точно в конец
2372        chatList.scrollTop = targetScroll;
2373        console.log('Финальный scrollTop (в конец):', chatList.scrollTop);
2374        
2375        console.log('scrollTop изменен с', oldScrollTop, 'на', chatList.scrollTop);
2376        
2377        // УВЕЛИЧЕНО: Ждем 5 секунд для подгрузки и рендеринга новых чатов
2378        console.log('Ждем 5 секунд для подгрузки и рендеринга новых чатов...');
2379        await sleep(5000);
2380        
2381        console.log('Финальный scrollTop после ожидания:', chatList.scrollTop);
2382        console.log('=== СКРОЛЛ ЗАВЕРШЕН ===');
2383        return true;
2384    }
2385    
2386    // Основная функция сбора базы контактов
2387    async function collectContactsDatabase(updateExisting = false, testMode = false) {
2388        console.log('=== НАЧАЛО СБОРА БАЗЫ КОНТАКТОВ ===');
2389        console.log('Режим:', updateExisting ? 'Обновление существующей базы' : 'Полный сбор');
2390        console.log('Тестовый режим:', testMode ? 'ДА (только 1 контакт)' : 'НЕТ');
2391        
2392        // Загружаем существующую базу
2393        const db = await loadContactsDatabase();
2394        const initialCount = Object.keys(db.contacts).length;
2395        console.log('Начальное количество контактов в базе:', initialCount);
2396        
2397        // Загружаем черный список и конвертируем в Set для быстрого поиска O(1)
2398        const blacklistArray = await loadBlacklist();
2399        const blacklist = new Set(blacklistArray);
2400        console.log('Загружен черный список:', blacklist.size, 'ID');
2401        
2402        // В режиме обновления - создаем Set из ID контактов для быстрой проверки
2403        const existingContactIds = updateExisting ? new Set(Object.keys(db.contacts)) : null;
2404        
2405        // ОПТИМИЗАЦИЯ: Используем только Set для ID (без WeakSet для DOM элементов)
2406        // Set с ID занимает меньше памяти чем WeakSet с DOM элементами
2407        const recentlyProcessedIds = new Set();
2408        const MAX_RECENT_IDS = 500; // УМЕНЬШЕНО с 1000 до 500
2409        const recentIdsQueue = [];
2410        
2411        function addToRecentIds(chatId) {
2412            if (recentlyProcessedIds.has(chatId)) return false;
2413            
2414            recentlyProcessedIds.add(chatId);
2415            recentIdsQueue.push(chatId);
2416            
2417            // Если превысили лимит - удаляем самый старый ID
2418            if (recentIdsQueue.length > MAX_RECENT_IDS) {
2419                const oldestId = recentIdsQueue.shift();
2420                recentlyProcessedIds.delete(oldestId);
2421            }
2422            
2423            return true;
2424        }
2425        
2426        // ИСПРАВЛЕНИЕ: Трекеры для уникальных операций
2427        const newContactsSet = new Set(); // ID новых контактов
2428        const updatedContactsSet = new Set(); // ID обновленных контактов
2429        let skippedBlacklist = 0;
2430        let scrollAttempts = 0;
2431        let noNewChatsCount = 0;
2432        const maxNoNewChatsAttempts = 7;
2433        
2434        // ОПТИМИЗАЦИЯ: Сохраняем реже - каждые 1000 контактов
2435        let contactsSinceLastSave = 0;
2436        const saveEveryNContacts = 1000;
2437        
2438        // НОВАЯ ОПТИМИЗАЦИЯ: Периодическая очистка скрытых чатов из DOM
2439        let contactsSinceLastCleanup = 0;
2440        const cleanupEveryNContacts = 300; // Очищаем каждые 300 обработанных чатов
2441        const maxHiddenChatsToRemove = 200; // Удаляем максимум 200 скрытых чатов за раз
2442        
2443        // Функция для очистки скрытых чатов из DOM
2444        function cleanupHiddenChats() {
2445            console.log('🧹 ПЕРИОДИЧЕСКАЯ ОЧИСТКА: Поиск скрытых чатов в DOM...');
2446            
2447            const allChatsInDom = document.querySelectorAll('.index_chat_4fr82, .index_chat_EHlBq');
2448            console.log(`📊 Всего чатов в DOM: ${allChatsInDom.length}`);
2449            
2450            // Находим скрытые чаты (не видимые на экране)
2451            const hiddenChats = Array.from(allChatsInDom).filter(chat => {
2452                const style = window.getComputedStyle(chat);
2453                const rect = chat.getBoundingClientRect();
2454                
2455                // Чат считается скрытым если:
2456                // 1. display: none или visibility: hidden
2457                // 2. Находится далеко за пределами viewport (более чем на 1000px)
2458                const isStyleHidden = style.display === 'none' || 
2459                                     style.visibility === 'hidden' || 
2460                                     style.opacity === '0';
2461                const isFarOffscreen = rect.bottom < -1000 || rect.top > window.innerHeight + 1000;
2462                
2463                return isStyleHidden || isFarOffscreen;
2464            });
2465            
2466            console.log(`🔍 Найдено скрытых чатов: ${hiddenChats.length}`);
2467            
2468            if (hiddenChats.length === 0) {
2469                console.log('✅ Нет скрытых чатов для удаления');
2470                return 0;
2471            }
2472            
2473            // Удаляем максимум maxHiddenChatsToRemove скрытых чатов
2474            const chatsToRemove = Math.min(hiddenChats.length, maxHiddenChatsToRemove);
2475            let removedCount = 0;
2476            
2477            for (let i = 0; i < chatsToRemove; i++) {
2478                if (hiddenChats[i]) {
2479                    hiddenChats[i].remove();
2480                    removedCount++;
2481                }
2482            }
2483            
2484            console.log(`🗑️ ПЕРИОДИЧЕСКАЯ ОЧИСТКА: Удалено ${removedCount} скрытых чатов из DOM`);
2485            
2486            // ХАКИ ДЛЯ СБРОСА REACT-ВИРТУАЛИЗАЦИИ
2487            if (removedCount > 0) {
2488                console.log('🔄 Применяем хаки для сброса React-виртуализации...');
2489                
2490                try {
2491                    // 1. Эмулируем событие resize - заставляет react-window/react-virtualized пересчитать размеры
2492                    window.dispatchEvent(new Event('resize'));
2493                    console.log('✅ Отправлено событие resize');
2494                    
2495                    // 2. Эмулируем событие scroll - триггерит пересчет видимых элементов
2496                    const chatListContainer = document.querySelector('.om_1_f0');
2497                    if (chatListContainer) {
2498                        chatListContainer.dispatchEvent(new Event('scroll', { bubbles: true }));
2499                        console.log('✅ Отправлено событие scroll на контейнер чатов');
2500                    }
2501                    
2502                    // 3. Принудительный скролл на 1px вверх и обратно - форсирует перерисовку
2503                    if (chatListContainer) {
2504                        const originalScrollTop = chatListContainer.scrollTop;
2505                        chatListContainer.scrollTop = Math.max(0, originalScrollTop - 1);
2506                        setTimeout(() => {
2507                            chatListContainer.scrollTop = originalScrollTop;
2508                            console.log('✅ Выполнен принудительный микро-скролл');
2509                        }, 50);
2510                    }
2511                    
2512                    // 4. Эмулируем изменение размера окна (для react-virtualized)
2513                    if (typeof window.ResizeObserver !== 'undefined') {
2514                        // Триггерим ResizeObserver если он используется
2515                        const resizeEvent = new CustomEvent('resize');
2516                        document.body.dispatchEvent(resizeEvent);
2517                        console.log('✅ Отправлено кастомное событие resize на body');
2518                    }
2519                    
2520                } catch (error) {
2521                    console.warn('⚠️ Ошибка при применении хаков виртуализации:', error);
2522                }
2523            }
2524            
2525            // Проверяем результат
2526            const remainingChats = document.querySelectorAll('.index_chat_4fr82, .index_chat_EHlBq').length;
2527            console.log(`📊 Осталось чатов в DOM: ${remainingChats}`);
2528            
2529            return removedCount;
2530        }
2531        
2532        while (!shouldStop) {
2533            // Проверка на паузу
2534            while (isPaused && !shouldStop) {
2535                updateStatus('Пауза сбора базы', `Новых: ${newContactsSet.size}, Обновлено: ${updatedContactsSet.size}, Пропущено (ЧС): ${skippedBlacklist}`);
2536                await sleep(500);
2537            }
2538            
2539            if (shouldStop) {
2540                console.log('Сбор базы остановлен пользователем');
2541                break;
2542            }
2543            
2544            // Проверка тестового режима
2545            if (testMode && (newContactsSet.size >= 1 || updatedContactsSet.size >= 1)) {
2546                console.log('Тестовый режим: достигнут лимит, завершаем');
2547                shouldStop = true;
2548                break;
2549            }
2550            
2551            // Проверка режима обновления
2552            if (updateExisting && existingContactIds) {
2553                const remainingToUpdate = Array.from(existingContactIds).filter(id => !recentlyProcessedIds.has(id));
2554                console.log('Осталось обновить контактов:', remainingToUpdate.length, 'из', existingContactIds.size);
2555                
2556                if (remainingToUpdate.length === 0) {
2557                    console.log('Все контакты из базы обновлены, завершаем');
2558                    break;
2559                }
2560            }
2561            
2562            // Получаем текущие видимые чаты
2563            const currentChats = getAllChats();
2564            console.log(`Попытка ${scrollAttempts + 1}: найдено ${currentChats.length} чатов в DOM`);
2565            
2566            // Фильтруем только новые чаты
2567            const newChats = currentChats.filter(chat => {
2568                const chatId = extractChatId(chat);
2569                if (!chatId) return false;
2570                
2571                // Проверяем кольцевой буфер последних ID
2572                if (recentlyProcessedIds.has(chatId)) return false;
2573                
2574                return true;
2575            });
2576            
2577            console.log('Новых необработанных чатов:', newChats.length);
2578            console.log('В кольцевом буфере ID:', recentlyProcessedIds.size);
2579            
2580            // Если нет новых чатов - увеличиваем счетчик
2581            if (newChats.length === 0) {
2582                noNewChatsCount++;
2583                console.log(`Новые чаты не появились (${noNewChatsCount}/${maxNoNewChatsAttempts})`);
2584                
2585                if (noNewChatsCount >= maxNoNewChatsAttempts) {
2586                    console.log('Достигнут конец списка чатов (новые чаты не подгружаются после', maxNoNewChatsAttempts, 'попыток)');
2587                    break;
2588                }
2589                
2590                // Скроллим список чатов для подгрузки новых
2591                console.log('Нет новых чатов, скроллим для подгрузки...');
2592                
2593                // ОБНОВЛЯЕМ СТАТУС ПЕРЕД СКРОЛЛОМ
2594                updateStatus(
2595                    'Сбор базы: прокрутка списка...',
2596                    `Обработано: ${newContactsSet.size + updatedContactsSet.size}, Новых: ${newContactsSet.size}, Обновлено: ${updatedContactsSet.size}, Пропущено (ЧС): ${skippedBlacklist}`
2597                );
2598                
2599                await scrollChatList();
2600                await sleep(3000); // УМЕНЬШЕНО с 5000 до 3000
2601                scrollAttempts++;
2602                continue;
2603            } else {
2604                noNewChatsCount = 0;
2605            }
2606            
2607            // Обрабатываем новые чаты
2608            for (const chat of newChats) {
2609                // Проверка на паузу
2610                while (isPaused && !shouldStop) {
2611                    updateStatus('Пауза сбора базы', `Новых: ${newContactsSet.size}, Обновлено: ${updatedContactsSet.size}, Пропущено (ЧС): ${skippedBlacklist}`);
2612                    await sleep(500);
2613                }
2614                
2615                if (shouldStop) break;
2616                
2617                const chatId = extractChatId(chat);
2618                const chatName = chat.querySelector('.index_chatTitle_TiXTq')?.textContent || 
2619                               chat.querySelector('.index_chatTitle_x9txX')?.textContent || 
2620                               'Неизвестно';
2621                
2622                // Добавляем ID в кольцевой буфер
2623                const isNew = addToRecentIds(chatId);
2624                if (!isNew) {
2625                    console.log('Чат уже обработан (в буфере):', chatName);
2626                    continue;
2627                }
2628                
2629                // Проверяем черный список
2630                if (blacklist.has(chatId)) {
2631                    console.log(`⛔ Чат в черном списке, пропускаем: ${chatName} (${chatId})`);
2632                    skippedBlacklist++;
2633                    continue;
2634                }
2635                
2636                // Получаем данные чата из элемента списка
2637                const chatData = getChatDataFromElement(chat);
2638                
2639                // Проверяем, есть ли уже в базе
2640                const isExisting = db.contacts[chatId];
2641                
2642                if (isExisting) {
2643                    // Обновляем существующий контакт
2644                    db.contacts[chatId].lastMessageDate = chatData.lastMessageDate || db.contacts[chatId].lastMessageDate;
2645                    updatedContactsSet.add(chatId);
2646                    console.log(`🔄 Обновлен контакт: ${chatName} (${chatId})`);
2647                } else {
2648                    // Добавляем новый контакт
2649                    addContactToDatabase(db, chatId, chatData);
2650                    newContactsSet.add(chatId);
2651                    console.log(`➕ Добавлен новый контакт: ${chatName} (${chatId})`);
2652                }
2653                
2654                updateStatus(
2655                    `Сбор базы: ${chatName}`,
2656                    `Обработано: ${newContactsSet.size + updatedContactsSet.size}, Новых: ${newContactsSet.size}, Обновлено: ${updatedContactsSet.size}, Пропущено (ЧС): ${skippedBlacklist}`
2657                );
2658                
2659                // Периодическое сохранение
2660                contactsSinceLastSave++;
2661                if (contactsSinceLastSave >= saveEveryNContacts) {
2662                    console.log(`Промежуточное сохранение базы (${newContactsSet.size + updatedContactsSet.size} контактов)...`);
2663                    await saveContactsDatabase(db);
2664                    contactsSinceLastSave = 0;
2665                }
2666                
2667                // НОВАЯ ЛОГИКА: Периодическая очистка скрытых чатов
2668                contactsSinceLastCleanup++;
2669                if (contactsSinceLastCleanup >= cleanupEveryNContacts) {
2670                    const removedCount = cleanupHiddenChats();
2671                    
2672                    // ОБНОВЛЯЕМ СТАТУС ПОСЛЕ ОЧИСТКИ
2673                    if (removedCount > 0) {
2674                        updateStatus(
2675                            `Сбор базы: очистка памяти (удалено ${removedCount} скрытых чатов)`,
2676                            `Обработано: ${newContactsSet.size + updatedContactsSet.size}, Новых: ${newContactsSet.size}, Обновлено: ${updatedContactsSet.size}, Пропущено (ЧС): ${skippedBlacklist}`
2677                        );
2678                        
2679                        // Небольшая пауза после очистки для стабильности
2680                        await sleep(500);
2681                    }
2682                    
2683                    contactsSinceLastCleanup = 0;
2684                }
2685            }
2686            
2687            // Скроллим для подгрузки следующей порции
2688            if (!shouldStop && newChats.length > 0) {
2689                await scrollChatList();
2690                await sleep(3000);
2691            }
2692        }
2693        
2694        // Финальное сохранение базы
2695        console.log('Финальное сохранение базы данных...');
2696        await saveContactsDatabase(db);
2697        
2698        // ОПТИМИЗАЦИЯ: Финальная очистка памяти
2699        console.log('🧹 Выполняем финальную очистку памяти...');
2700        
2701        // Очищаем кольцевой буфер до минимума
2702        while (recentIdsQueue.length > 100) {
2703            const oldestId = recentIdsQueue.shift();
2704            recentlyProcessedIds.delete(oldestId);
2705        }
2706        console.log('Кольцевой буфер очищен до 100 ID');
2707        
2708        // ФИНАЛЬНАЯ ОЧИСТКА: Удаляем скрытые чаты
2709        console.log('🧹 ФИНАЛЬНАЯ ОЧИСТКА: Удаление скрытых чатов из DOM...');
2710        const finalRemovedCount = cleanupHiddenChats();
2711        
2712        // Проверяем результат
2713        const remainingChats = document.querySelectorAll('.index_chat_4fr82, .index_chat_EHlBq').length;
2714        console.log(`📊 Финальный результат: осталось ${remainingChats} чатов в DOM`);
2715        
2716        if (remainingChats > 200) {
2717            console.warn(`⚠️ ВНИМАНИЕ: После очистки осталось ${remainingChats} чатов в DOM`);
2718            console.warn('Это может указывать на проблемы с виртуализацией Ozon');
2719            
2720            // Показываем предупреждение пользователю
2721            const shouldContinue = confirm(
2722                '⚠️ ВНИМАНИЕ: В DOM осталось много чатов!\n\n' +
2723                `После очистки осталось ${remainingChats} чатов в DOM.\n` +
2724                'Виртуализация Ozon работает некорректно.\n\n' +
2725                'Рекомендуется:\n' +
2726                '1. Перезагрузить страницу\n' +
2727                '2. Продолжить сбор\n\n' +
2728                'Нажмите OK для подтверждения.'
2729            );
2730        } else {
2731            console.log(`✅ Очистка успешна: ${remainingChats} чатов в DOM - в пределах нормы`);
2732        }
2733        
2734        // ОБНОВЛЯЕМ СТАТУС ПОСЛЕ ФИНАЛЬНОЙ ОЧИСТКИ
2735        updateStatus(
2736            `Сбор базы завершен! (удалено ${finalRemovedCount} скрытых чатов)`,
2737            `Обработано: ${newContactsSet.size + updatedContactsSet.size}, Новых: ${newContactsSet.size}, Обновлено: ${updatedContactsSet.size}, Пропущено (ЧС): ${skippedBlacklist}`
2738        );
2739    }
2740
2741    // Функция для создания модального окна
2742    function createModal() {
2743        const modalHTML = `
2744            <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;">
2745                <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);">
2746                    <h2 style="margin: 0 0 20px 0; color: #333; font-size: 24px;">Массовая рассылка</h2>
2747                    
2748                    <!-- Блок управления базой данных -->
2749                    <div style="margin-bottom: 25px; padding: 20px; background: #f9f9f9; border-radius: 8px; border: 1px solid #e0e0e0;">
2750                        <h3 style="margin: 0 0 15px 0; color: #555; font-size: 18px;">📊 База данных контактов</h3>
2751                        
2752                        <div id="dbStatsBlock" style="margin-bottom: 15px; padding: 15px; background: #f0f8ff; border-radius: 6px; border-left: 4px solid #0066cc;">
2753                            <div style="font-weight: 600; color: #0066cc; margin-bottom: 10px;">Статистика базы:</div>
2754                            <div style="color: #333; font-size: 14px;">Загрузка...</div>
2755                        </div>
2756                        
2757                        <div style="margin-bottom: 15px;">
2758                            <label style="display: flex; align-items: center; cursor: pointer;">
2759                                <input type="checkbox" id="testModeCollect" style="width: 18px; height: 18px; margin-right: 10px; cursor: pointer;">
2760                                <span style="color: #555; font-weight: 600; font-size: 14px;">Тестовый режим сбора (только 1 контакт)</span>
2761                            </label>
2762                        </div>
2763                        
2764                        <div style="display: flex; flex-wrap: wrap; gap: 10px;">
2765                            <button id="collectDbButton" style="padding: 10px 18px; background: #4caf50; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 600;">📥 Собрать базу</button>
2766                            <button id="updateDbButton" style="padding: 10px 18px; background: #2196f3; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 600;">🔄 Обновить базу</button>
2767                            <button id="viewDbButton" style="padding: 10px 18px; background: #9c27b0; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 600;">👁️ Просмотр</button>
2768                            <button id="blacklistButton" style="padding: 10px 18px; background: #f44336; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 600;">🚫 Черный список</button>
2769                            <button id="exportDbButton" style="padding: 10px 18px; background: #00bcd4; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 600;">📤 Экспорт базы</button>
2770                            <button id="importDbButton" style="padding: 10px 18px; background: #009688; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 600;">📥 Импорт базы</button>
2771                            <button id="refreshStatsButton" style="padding: 10px 18px; background: #ff9800; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 600;">📊 Обновить статистику</button>
2772                            <button id="clearDbButton" style="padding: 10px 18px; background: #f44336; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 600;">🗑️ Очистить</button>
2773                            <button id="pauseDbButton" style="display: none; padding: 10px 18px; background: #ff9800; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 600;">⏸️ Пауза</button>
2774                            <button id="stopDbButton" style="display: none; padding: 10px 18px; background: #f44336; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 600;">⏹️ Стоп</button>
2775                        </div>
2776                    </div>
2777                    
2778                    <!-- Выбор режима рассылки -->
2779                    <div style="margin-bottom: 25px; padding: 20px; background: #f9f9f9; border-radius: 8px; border: 1px solid #e0e0e0;">
2780                        <h3 style="margin: 0 0 15px 0; color: #555; font-size: 18px;">🎯 Режим рассылки</h3>
2781                        <div style="display: flex; flex-direction: column; gap: 12px;">
2782                            <label style="display: flex; align-items: center; cursor: pointer; padding: 12px; background: white; border-radius: 6px; border: 2px solid #ddd; transition: all 0.2s;">
2783                                <input type="radio" name="sendingMode" value="database" checked style="width: 18px; height: 18px; margin-right: 12px; cursor: pointer;">
2784                                <div>
2785                                    <div style="color: #333; font-weight: 600; font-size: 14px;">📊 По базе данных</div>
2786                                    <div style="color: #777; font-size: 12px; margin-top: 4px;">Отправка по собранной базе контактов с фильтром по дате</div>
2787                                </div>
2788                            </label>
2789                            <label style="display: flex; align-items: center; cursor: pointer; padding: 12px; background: white; border-radius: 6px; border: 2px solid #ddd; transition: all 0.2s;">
2790                                <input type="radio" name="sendingMode" value="visible" style="width: 18px; height: 18px; margin-right: 12px; cursor: pointer;">
2791                                <div>
2792                                    <div style="color: #333; font-weight: 600; font-size: 14px;">👁️ По видимым чатам</div>
2793                                    <div style="color: #777; font-size: 12px; margin-top: 4px;">Отправка только по чатам, видимым в списке (с учетом фильтров)</div>
2794                                </div>
2795                            </label>
2796                        </div>
2797                    </div>
2798                    
2799                    <div style="margin-bottom: 20px;">
2800                        <label style="display: block; margin-bottom: 8px; color: #555; font-weight: 600;">Текст сообщения:</label>
2801                        <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>
2802                    </div>
2803                    
2804                    <div style="margin-bottom: 25px;">
2805                        <label style="display: block; margin-bottom: 8px; color: #555; font-weight: 600;">Дата последнего сообщения (до какой даты отправляли):</label>
2806                        <input type="date" id="filterDate" style="width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px;">
2807                        <small style="color: #777; display: block; margin-top: 5px; margin-left: 28px;">Будут обработаны чаты с датой последнего сообщения до указанной включительно</small>
2808                    </div>
2809                    
2810                    <div style="margin-bottom: 20px;">
2811                        <label style="display: flex; align-items: center; cursor: pointer;">
2812                            <input type="checkbox" id="testMode" checked style="width: 18px; height: 18px; margin-right: 10px; cursor: pointer;">
2813                            <span style="color: #555; font-weight: 600;">Тестовый режим (отправить только 1 сообщение)</span>
2814                        </label>
2815                        <small style="color: #ff6600; display: block; margin-top: 5px; margin-left: 28px;">⚠️ Рекомендуется для первого запуска</small>
2816                    </div>
2817                    
2818                    <div id="statusBlock" style="display: none; margin-bottom: 20px; padding: 15px; background: #f0f8ff; border-radius: 6px; border-left: 4px solid #0066cc;">
2819                        <div style="font-weight: 600; color: #0066cc; margin-bottom: 5px;">Статус:</div>
2820                        <div id="statusText" style="color: #333;">Готов к запуску</div>
2821                        <div id="progressText" style="color: #666; margin-top: 5px; font-size: 13px;"></div>
2822                    </div>
2823                    
2824                    <div style="display: flex; gap: 10px; justify-content: flex-end; flex-wrap: wrap;">
2825                        <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>
2826                        <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>
2827                        <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>
2828                        <button id="emergencyStopButton" style="padding: 12px 24px; background: #d32f2f; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600;">🚨 Экстренная остановка</button>
2829                        <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>
2830                    </div>
2831                </div>
2832            </div>
2833        `;
2834        
2835        document.body.insertAdjacentHTML('beforeend', modalHTML);
2836        console.log('Модальное окно создано');
2837        
2838        // Обработчики событий
2839        document.getElementById('closeButton').addEventListener('click', closeModal);
2840        document.getElementById('startButton').addEventListener('click', () => {
2841            console.log('!!! КЛИК ПО КНОПКЕ ЗАПУСТИТЬ !!!');
2842            startBulkSending();
2843        });
2844        document.getElementById('pauseButton').addEventListener('click', togglePause);
2845        document.getElementById('stopButton').addEventListener('click', stopBulkSending);
2846        
2847        // Обработчик для кнопки черного списка
2848        document.getElementById('blacklistButton').addEventListener('click', openBlacklistModal);
2849        
2850        // Обработчики для кнопок управления базой
2851        document.getElementById('viewDbButton').addEventListener('click', openViewDatabaseModal);
2852        document.getElementById('exportDbButton').addEventListener('click', exportDatabase);
2853        document.getElementById('importDbButton').addEventListener('click', importDatabase);
2854        document.getElementById('clearDbButton').addEventListener('click', async () => {
2855            const cleared = await clearContactsDatabase();
2856            if (cleared) {
2857                alert('✅ База данных очищена!');
2858                await updateDatabaseStats();
2859            }
2860        });
2861        
2862        document.getElementById('collectDbButton').addEventListener('click', async () => {
2863            const testModeCollect = document.getElementById('testModeCollect').checked;
2864            const confirmMessage = testModeCollect 
2865                ? 'Начать тестовый сбор базы контактов (только 1 контакт)?'
2866                : 'Начать полный сбор базы контактов? Это может занять некоторое время.';
2867            
2868            if (confirm(confirmMessage)) {
2869                try {
2870                    // Показываем кнопки паузы и остановки
2871                    document.getElementById('collectDbButton').style.display = 'none';
2872                    document.getElementById('updateDbButton').style.display = 'none';
2873                    document.getElementById('pauseDbButton').style.display = 'inline-block';
2874                    document.getElementById('stopDbButton').style.display = 'inline-block';
2875                    
2876                    shouldStop = false;
2877                    isPaused = false;
2878                    await collectContactsDatabase(false, testModeCollect);
2879                } catch (error) {
2880                    console.error('❌ Ошибка при сборе базы:', error);
2881                    alert('❌ Ошибка при сборе базы: ' + error.message);
2882                } finally {
2883                    // ВСЕГДА возвращаем кнопки в исходное состояние
2884                    document.getElementById('collectDbButton').style.display = 'inline-block';
2885                    document.getElementById('updateDbButton').style.display = 'inline-block';
2886                    document.getElementById('pauseDbButton').style.display = 'none';
2887                    document.getElementById('stopDbButton').style.display = 'none';
2888                    
2889                    await updateDatabaseStats();
2890                }
2891            }
2892        });
2893        
2894        document.getElementById('updateDbButton').addEventListener('click', async () => {
2895            const testModeCollect = document.getElementById('testModeCollect').checked;
2896            const confirmMessage = testModeCollect 
2897                ? 'Обновить базу контактов в тестовом режиме (только 1 контакт)?'
2898                : 'Обновить существующую базу контактов?';
2899            
2900            if (confirm(confirmMessage)) {
2901                try {
2902                    // Показываем кнопки паузы и остановки
2903                    document.getElementById('collectDbButton').style.display = 'none';
2904                    document.getElementById('updateDbButton').style.display = 'none';
2905                    document.getElementById('pauseDbButton').style.display = 'inline-block';
2906                    document.getElementById('stopDbButton').style.display = 'inline-block';
2907                    
2908                    shouldStop = false;
2909                    isPaused = false;
2910                    await collectContactsDatabase(true, testModeCollect);
2911                } catch (error) {
2912                    console.error('❌ Ошибка при обновлении базы:', error);
2913                    alert('❌ Ошибка при обновлении базы: ' + error.message);
2914                } finally {
2915                    // ВСЕГДА возвращаем кнопки в исходное состояние
2916                    document.getElementById('collectDbButton').style.display = 'inline-block';
2917                    document.getElementById('updateDbButton').style.display = 'inline-block';
2918                    document.getElementById('pauseDbButton').style.display = 'none';
2919                    document.getElementById('stopDbButton').style.display = 'none';
2920                    
2921                    await updateDatabaseStats();
2922                }
2923            }
2924        });
2925        
2926        // Обработчик паузы
2927        document.getElementById('pauseDbButton').addEventListener('click', () => {
2928            isPaused = !isPaused;
2929            const pauseDbButton = document.getElementById('pauseDbButton');
2930            
2931            if (isPaused) {
2932                pauseDbButton.textContent = '▶️ Продолжить';
2933                pauseDbButton.style.background = '#4caf50';
2934                console.log('Сбор базы приостановлен');
2935            } else {
2936                pauseDbButton.textContent = '⏸️ Пауза';
2937                pauseDbButton.style.background = '#ff9800';
2938                console.log('Сбор базы возобновлен');
2939            }
2940        });
2941        
2942        // Обработчик остановки
2943        document.getElementById('stopDbButton').addEventListener('click', () => {
2944            if (confirm('Вы уверены, что хотите остановить сбор базы?')) {
2945                shouldStop = true;
2946                isPaused = false;
2947                console.log('Запрошена остановка сбора базы');
2948            }
2949        });
2950        
2951        // Обработчик для кнопки обновления статистики
2952        document.getElementById('refreshStatsButton').addEventListener('click', async () => {
2953            console.log('Принудительное обновление статистики');
2954            await updateDatabaseStats();
2955            
2956            // Также выводим в консоль для диагностики
2957            const db = await loadContactsDatabase();
2958            const dbString = JSON.stringify(db);
2959            const sizeBytes = dbString.length;
2960            const sizeKB = (sizeBytes / 1024).toFixed(2);
2961            const sizeMB = (sizeBytes / (1024 * 1024)).toFixed(4);
2962            const totalContacts = Object.keys(db.contacts).length;
2963            const avgContactSize = totalContacts > 0 ? (sizeBytes / totalContacts).toFixed(0) : 0;
2964            
2965            // Расчет максимального количества контактов (при лимите 50 MB)
2966            const estimatedMaxContacts = totalContacts > 0 ? Math.floor((50 * 1024 * 1024) / (sizeBytes / totalContacts)) : 0;
2967            
2968            console.log('=== ДИАГНОСТИКА БАЗЫ ДАННЫХ ===');
2969            console.log('Всего контактов в базе:', totalContacts);
2970            console.log('Размер базы:', sizeKB, 'KB (', sizeMB, 'MB )');
2971            console.log('Средний размер контакта:', avgContactSize, 'байт');
2972            console.log('Примерно поместится контактов (при лимите 50 MB):', estimatedMaxContacts);
2973            console.log('Первые 3 контакта:', Object.values(db.contacts).slice(0, 3));
2974            console.log('');
2975            console.log('📊 ЛИМИТЫ IndexedDB ПО БРАУЗЕРАМ:');
2976            console.log('Chrome/Edge: ~60% свободного места на диске (обычно несколько GB)');
2977            console.log('Firefox: ~50% свободного места на диске (обычно несколько GB)');
2978            console.log('Safari: ~1 GB');
2979            console.log('');
2980            console.log('✅ Ваша база легко поместится! IndexedDB поддерживает гораздо больше данных.');
2981            
2982            alert(`📊 Статистика базы данных:\n\nКонтактов: ${totalContacts}\nРазмер: ${sizeKB} KB (${sizeMB} MB)\nСредний размер контакта: ${avgContactSize} байт\n\nПримерно поместится: ~${estimatedMaxContacts.toLocaleString()} контактов\n(при консервативном лимите 50 MB)\n\n✅ IndexedDB поддерживает гораздо больше!\nChrome/Firefox: несколько GB\nSafari: ~1 GB`);
2983        });
2984        
2985        // Обработчик для кнопки экстренной остановки
2986        document.getElementById('emergencyStopButton').addEventListener('click', async () => {
2987            if (confirm('Экстренная остановка очистит все активные процессы рассылки. Продолжить?')) {
2988                await clearSendingState();
2989                shouldStop = true;
2990                isPaused = false;
2991                alert('Процесс остановлен! Все активные рассылки прекращены.');
2992                console.log('Экстренная остановка выполнена');
2993                
2994                // Обновляем интерфейс
2995                updateStatus('Остановлено', 'Процесс прерван экстренной остановкой');
2996            }
2997        });
2998        
2999        // Загружаем статистику при создании модального окна
3000        updateDatabaseStats();
3001    }
3002
3003    // Функция для фильтрации списка контактов
3004    function filterContactsList(searchQuery) {
3005        loadAndDisplayContacts(searchQuery);
3006    }
3007
3008    // Инициализация
3009    async function init() {
3010        console.log('Инициализация расширения');
3011        
3012        // Проверяем, что мы на странице мессенджера с покупателями
3013        if (!window.location.href.includes('group=customers_v2')) {
3014            console.log('Не на странице чатов с покупателями, расширение не активно');
3015            return;
3016        }
3017        
3018        console.log('Страница подходит, проверяем состояние');
3019        
3020        // Проверяем, есть ли сохраненное состояние рассылки
3021        const savedState = await loadSendingState();
3022        
3023        if (savedState && savedState.isActive) {
3024            console.log('Обнаружено активное состояние рассылки');
3025            
3026            // Проверяем, это рабочая вкладка или вкладка для отправки
3027            const urlParams = new URLSearchParams(window.location.search);
3028            const chatId = urlParams.get('id');
3029            
3030            if (chatId) {
3031                // Это вкладка с чатом - проверяем, есть ли этот ID в списке контактов
3032                const isInContactsList = savedState.contactsList.some(contact => contact.id === chatId);
3033                
3034                if (isInContactsList) {
3035                    console.log('🎯 Это рабочая вкладка для чата:', chatId);
3036                    console.log('⚡ СРАЗУ начинаем обработку без ожидания очереди');
3037                    
3038                    // Ждем загрузки страницы
3039                    console.log('Ждем загрузки страницы 3 секунды...');
3040                    await sleep(3000);
3041                    console.log('Ожидание завершено, начинаем обработку');
3042                    
3043                    // Получаем дату последнего сообщения в этом чате
3044                    const lastMessageDate = getLastMessageDate();
3045                    console.log('Дата последнего сообщения в чате:', lastMessageDate);
3046                    
3047                    // Получаем дату фильтра из состояния
3048                    const filterDate = savedState.filterDate ? new Date(savedState.filterDate) : null;
3049                    console.log('Дата фильтра из состояния:', filterDate);
3050                    
3051                    // Проверяем дату перед отправкой
3052                    let shouldSend = false;
3053                    if (lastMessageDate && filterDate) {
3054                        console.log('Сравнение дат:');
3055                        console.log('  Дата из чата:', lastMessageDate.toLocaleDateString('ru-RU'));
3056                        console.log('  Дата фильтра:', filterDate.toLocaleDateString('ru-RU'));
3057                        console.log('  Дата из чата <= Дата фильтра?', lastMessageDate <= filterDate);
3058                        
3059                        shouldSend = lastMessageDate <= filterDate;
3060                    } else {
3061                        console.log('Не удалось получить дату или фильтр, пропускаем отправку');
3062                        shouldSend = false;
3063                    }
3064                    
3065                    // Отправляем сообщение только если дата подходит
3066                    let success = false;
3067                    if (shouldSend) {
3068                        console.log('✅ Дата подходит, отправляем сообщение');
3069                        success = await sendMessage(savedState.messageText);
3070                    } else {
3071                        console.log('⏭️ Пропускаем отправку (дата не подходит)');
3072                        success = false;
3073                    }
3074                    
3075                    // Обновляем состояние АТОМАРНО
3076                    const currentState = await loadSendingState();
3077                    
3078                    if (!currentState || !currentState.isActive) {
3079                        console.log('Рассылка завершена, закрываем вкладку');
3080                        await sleep(300);
3081                        window.close();
3082                        return;
3083                    }
3084                    
3085                    if (success) {
3086                        console.log('✅ Сообщение отправлено');
3087                        
3088                        // Обновляем базу данных
3089                        const db = await loadContactsDatabase();
3090                        if (db.contacts[chatId]) {
3091                            db.contacts[chatId].lastSentDate = new Date().toISOString();
3092                            db.contacts[chatId].messageCount = (db.contacts[chatId].messageCount || 0) + 1;
3093                            await saveContactsDatabase(db);
3094                        }
3095                        
3096                        currentState.sent++;
3097                    } else {
3098                        console.error('❌ Ошибка отправки или дата не подходит');
3099                        currentState.skipped++;
3100                    }
3101                    
3102                    currentState.processed++;
3103                    
3104                    // Проверяем, нужно ли завершать
3105                    if (currentState.testMode && currentState.sent >= 1) {
3106                        console.log('Тестовый режим: достигнут лимит, завершаем');
3107                        await clearSendingState();
3108                        await sleep(300);
3109                        window.close();
3110                        return;
3111                    }
3112                    
3113                    if (currentState.processed >= currentState.totalContacts) {
3114                        console.log('Все контакты обработаны, завершаем');
3115                        await clearSendingState();
3116                        await sleep(300);
3117                        window.close();
3118                        return;
3119                    }
3120                    
3121                    // Обновляем currentContactId на следующий контакт
3122                    const nextContact = currentState.contactsList[currentState.processed];
3123                    if (nextContact) {
3124                        const oldContactId = currentState.currentContactId;
3125                        currentState.currentContactId = nextContact.id;
3126                        console.log('🔄 Обновлен currentContactId:', oldContactId, '->', nextContact.id);
3127                        console.log('   Следующий контакт:', nextContact.name);
3128                    }
3129                    
3130                    // Сохраняем обновленное состояние
3131                    await saveSendingState(currentState);
3132                    console.log('✅ Состояние сохранено');
3133                    
3134                    // Закрываем текущую вкладку
3135                    console.log('Закрываем текущую вкладку');
3136                    await sleep(300);
3137                    window.close();
3138                    
3139                    return; // Не создаем интерфейс
3140                }
3141                
3142                console.log('Это главная вкладка (ID чата не в списке контактов)');
3143                // Продолжаем создание интерфейса ниже
3144            } else {
3145                console.log('Это главная вкладка, ожидаем результатов от рабочих вкладок');
3146                
3147                // Создаем интерфейс и показываем прогресс
3148                createFloatingButton();
3149                createModal();
3150                openModal();
3151                
3152                // Запускаем мониторинг состояния
3153                monitorSendingProgress();
3154                
3155                return;
3156            }
3157        }
3158        
3159        // Создаем плавающую кнопку сразу
3160        createFloatingButton();
3161        createModal();
3162        
3163        console.log('Интерфейс создан');
3164    }
3165
3166    // Запуск при загрузке страницы
3167    if (document.readyState === 'loading') {
3168        document.addEventListener('DOMContentLoaded', init);
3169    } else {
3170        init();
3171    }
3172})();