Ozon BASE Messenger Bulk Sender

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

Size

147.0 KB

Version

1.1.168

Created

Feb 2, 2026

Updated

15 days ago

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