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