Ozon BASE Messenger Bulk Sender

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

Size

143.0 KB

Version

1.1.165

Created

Jan 30, 2026

Updated

2 months ago

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