Автоматическое назначение стратегии для выбранных кластеров в MP Manager
Size
53.6 KB
Version
1.1.45
Created
Feb 5, 2026
Updated
about 1 month ago
1// ==UserScript==
2// @name MP Manager - Автоназначение стратегии кластерам
3// @description Автоматическое назначение стратегии для выбранных кластеров в MP Manager
4// @version 1.1.45
5// @match https://*.app.mpmgr.ru/*
6// @icon https://app.mpmgr.ru/favicon.ico
7// @grant GM.setClipboard
8// @grant GM.openInTab
9// @grant GM.getValue
10// @grant GM.setValue
11// ==/UserScript==
12(function() {
13 'use strict';
14
15 console.log('MP Manager - Автоназначение стратегии кластерам: расширение загружено');
16
17 // Стратегия по умолчанию
18 const DEFAULT_STRATEGY = 'eyJjYW1wYWlnblR5cGUiOiJTZWFyY2hDYXRhbG9nIiwidmVyc2lvbiI6MiwiZGF0YSI6eyJzdGF0ZSI6IkVuYWJsZWQiLCJtb2RlIjp7InR5cGUiOiJQb3NpdGlvbiIsInBhdXNlVHlwZSI6IlJlc3VtZSJ9LCJzdHJhdGVneSI6eyJ0eXBlIjoiQ2FtcGFpZ25TdGF0cyIsIm1vZGUiOiJQbGFjZSIsImJpZE1vZGUiOiJDb3N0UGVyTWlsbGUiLCJjbHVzdGVyU3RhdHMiOnsibWF4QmlkIjo4MDAwLCJtaW5CaWQiOjQwMCwiaW50ZXJ2YWwiOiJUaHJlZURheXMiLCJydWxlcyI6W3sidHlwZSI6IkF2ZXJhZ2VQb3NpdGlvbiIsIm1vZGUiOiJWYWx1ZSIsInZhbHVlIjoyfSx7InR5cGUiOiJDb3N0UGVyQWRkZWRUb0NhcnQiLCJtb2RlIjoiQXZlcmFnZVZhbHVlIn1dfSwiY2FtcGFpZ25TdGF0cyI6eyJtYXhCaWQiOjgwMDAsIm1pbkJpZCI6NDAwLCJpbnRlcnZhbCI6IlRocmVlRGF5cyIsInJ1bGVzIjpbeyJ0eXBlIjoiQXZlcmFnZVBvc2l0aW9uIiwibW9kZSIOiJWYWx1ZSIsInZhbHVlIjoiIn0seyJ0eXBlIjoiQ29zdFBlcmFkZWRUb0NhcnQiLCJtb2RlIjoiQXZlcmFnZVZhbHVlIn1dIn0sInBsYWNlU3RyYXRlZ3kiOnsidHlwZSI6IktleXdvcmRzIiwia2V5d29yZHMiOnsia2V5d29yZHMiOlsi0LzQsNCz0L3QtSDQsSA2Il19fSwiaXNDbHVzdGVyIjp0cnVlfX0=';
19
20 console.log('Стратегия загружена, длина:', DEFAULT_STRATEGY.length);
21
22 // Функция для получения текущей стратегии
23 async function getCurrentStrategy() {
24 return await GM.getValue('mp_current_strategy', DEFAULT_STRATEGY);
25 }
26
27 // Функция для сохранения стратегий
28 async function getSavedStrategies() {
29 const strategies = await GM.getValue('mp_saved_strategies', []);
30 // Если нет сохраненных стратегий, добавляем стратегию по умолчанию
31 if (strategies.length === 0) {
32 return [{
33 name: 'Стратегия по умолчанию',
34 data: DEFAULT_STRATEGY
35 }];
36 }
37 return strategies;
38 }
39
40 // Функция ожидания элемента
41 function waitForElement(selector, timeout = 10000) {
42 return new Promise((resolve, reject) => {
43 const startTime = Date.now();
44
45 const checkElement = () => {
46 const element = document.querySelector(selector);
47 if (element) {
48 console.log(`Элемент найден: ${selector}`);
49 resolve(element);
50 } else if (Date.now() - startTime > timeout) {
51 console.error(`Таймаут ожидания элемента: ${selector}`);
52 reject(new Error(`Элемент не найден: ${selector}`));
53 } else {
54 setTimeout(checkElement, 100);
55 }
56 };
57
58 checkElement();
59 });
60 }
61
62 // Функция задержки
63 function delay(ms) {
64 return new Promise(resolve => setTimeout(resolve, ms));
65 }
66
67 // Основная функция автоматизации
68 async function applyStrategyToClusters() {
69 try {
70 console.log('Начинаем процесс назначения стратегии...');
71
72 // Шаг 1: Проверяем, что мы на вкладке "Кластеры"
73 const clustersTab = document.querySelector('button[aria-selected="true"]');
74 const isOnClustersTab = clustersTab && clustersTab.textContent.includes('Кластеры');
75
76 if (!isOnClustersTab) {
77 console.log('Переключаемся на вкладку Кластеры...');
78
79 // Ищем кнопку с вкладкой "Кластеры"
80 const clustersButton = Array.from(document.querySelectorAll('button[role="tab"]')).find(btn => {
81 const textContent = btn.textContent || '';
82 return textContent.includes('Кластеры');
83 });
84
85 if (clustersButton) {
86 console.log('Найдена вкладка Кластеры, кликаем...');
87 clustersButton.click();
88 await delay(2000);
89 console.log('✓ Переключились на вкладку Кластеры');
90 } else {
91 throw new Error('Вкладка "Кластеры" не найдена');
92 }
93 } else {
94 console.log('✓ Уже находимся на вкладке Кластеры');
95 }
96
97 // Шаг 2: Нажимаем на кнопку "Фильтры"
98 console.log('Ищем кнопку Фильтры...');
99 await delay(1000);
100
101 const filtersButton = Array.from(document.querySelectorAll('button')).find(btn =>
102 btn.textContent.includes('Фильтры')
103 );
104
105 if (!filtersButton) {
106 throw new Error('Кнопка "Фильтры" не найдена');
107 }
108
109 console.log('Кликаем на кнопку Фильтры...');
110 filtersButton.click();
111 await delay(1000);
112 console.log('✓ Открыто меню фильтров');
113
114 // Шаг 3: Находим выпадающее меню "Управление кластером"
115 console.log('Ищем поле Управление кластером...');
116 await delay(500);
117
118 // Ищем label "Управление кластером"
119 const managementLabel = Array.from(document.querySelectorAll('label')).find(l =>
120 l.textContent.includes('Управление кластером')
121 );
122
123 if (!managementLabel) {
124 throw new Error('Label "Управление кластером" не найден');
125 }
126
127 // Находим контейнер с полем
128 const formControl = managementLabel.closest('.MuiFormControl-root');
129 if (!formControl) {
130 throw new Error('Контейнер поля "Управление кластером" не найден');
131 }
132
133 // Проверяем текущее значение
134 const clusterManagementInput = formControl.querySelector('input.css-1mu660y');
135
136 if (!clusterManagementInput) {
137 throw new Error('Input поле "Управление кластером" не найдено');
138 }
139
140 console.log('Текущее значение поля:', clusterManagementInput.value);
141
142 // Если значение уже "Не задано", пропускаем шаги открытия меню
143 if (clusterManagementInput.value !== 'Не задано') {
144 console.log('Нужно изменить значение на "Не задано"...');
145
146 // Шаг 3.1: Кликаем на input поле для открытия меню (БЕЗ очистки!)
147 console.log('Кликаем на input поле для открытия меню...');
148 clusterManagementInput.click();
149 await delay(800);
150 console.log('✓ Input поле кликнуто');
151
152 // Шаг 4: Ищем меню и select "Значение" внутри него
153 console.log('Ищем меню с опциями...');
154 await delay(500);
155
156 const popoverMenu = document.querySelector('.MuiPaper-root.MuiPaper-elevation.MuiPaper-rounded.MuiPaper-elevation8.MuiPopover-paper.MuiMenu-paper.css-wh5gt5');
157
158 if (!popoverMenu) {
159 throw new Error('Меню не открылось');
160 }
161
162 console.log('✓ Меню найдено');
163
164 // Шаг 4.1: Находим select "Значение" и кнопку со стрелкой внутри него
165 const valueSelect = popoverMenu.querySelector('.MuiSelect-select.MuiSelect-outlined.MuiInputBase-input.MuiOutlinedInput-input.MuiInputBase-inputSizeSmall.css-17t22b0');
166
167 if (!valueSelect) {
168 throw new Error('Select "Значение" не найден в меню');
169 }
170
171 const selectInputBase = valueSelect.closest('.MuiInputBase-root');
172 const arrowButton = selectInputBase ? selectInputBase.querySelector('button.css-wyjwuq') : null;
173
174 if (!arrowButton) {
175 throw new Error('Кнопка со стрелкой не найдена в select');
176 }
177
178 console.log('Кликаем на кнопку со стрелкой в select Значение...');
179 arrowButton.click();
180 await delay(800);
181 console.log('✓ Кнопка со стрелкой кликнута');
182
183 // Шаг 5: Выбираем "Не задано" в выпадающем списке
184 console.log('Ищем пункт "Не задано"...');
185 await delay(500);
186
187 // Ищем пункт с data-value="Unavailable" или текстом "Не задано"
188 let notSetOption = document.querySelector('li[data-value="Unavailable"]');
189
190 if (!notSetOption) {
191 notSetOption = Array.from(document.querySelectorAll('.MuiMenuItem-root.css-e6mt4k')).find(item =>
192 item.textContent.includes('Не задано')
193 );
194 }
195
196 if (!notSetOption) {
197 throw new Error('Пункт "Не задано" не найден в меню');
198 }
199
200 console.log('Кликаем на "Не задано"...');
201 notSetOption.click();
202 await delay(2000);
203 console.log('✓ Выбран фильтр "Не задано"');
204 } else {
205 console.log('✓ Значение уже установлено на "Не задано", пропускаем');
206 }
207
208 // Шаг 6: Закрываем меню фильтров
209 console.log('Закрываем меню фильтров...');
210 const filtersButtonClose = Array.from(document.querySelectorAll('button')).find(btn =>
211 btn.textContent.includes('Фильтры')
212 );
213
214 if (filtersButtonClose) {
215 filtersButtonClose.click();
216 await delay(1500);
217 console.log('✓ Меню фильтров закрыто');
218 }
219
220 // Шаг 7: Ждем загрузки таблицы с кластерами
221 console.log('Ожидаем загрузки таблицы с кластерами...');
222 await delay(3000);
223
224 // Проверяем что таблица загрузилась
225 const tableRows = document.querySelectorAll('table tbody tr');
226 console.log(`Найдено строк в таблице: ${tableRows.length}`);
227
228 if (tableRows.length === 0) {
229 console.log('Таблица пустая, ждем еще...');
230 await delay(3000);
231 const tableRowsRetry = document.querySelectorAll('table tbody tr');
232 console.log(`Найдено строк после повторной проверки: ${tableRowsRetry.length}`);
233
234 if (tableRowsRetry.length === 0) {
235 throw new Error('Таблица кластеров пустая - нет кластеров с фильтром "Не задано"');
236 }
237 }
238
239 console.log('✓ Таблица загружена');
240
241 // Шаг 8: Выбираем все кластеры (чекбокс в заголовке таблицы)
242 console.log('Ищем чекбокс для выбора всех кластеров...');
243 await delay(1000);
244
245 // Сначала пробуем найти по новому классу
246 let checkboxContainer = document.querySelector('.MuiBox-root.css-1ym6vdf');
247
248 // Если не найден, пробуем старый класс (для обратной совместимости)
249 if (!checkboxContainer) {
250 checkboxContainer = document.querySelector('.MuiBox-root.css-q20har');
251 }
252
253 // Если все еще не найден, ищем чекбокс в thead напрямую
254 if (!checkboxContainer) {
255 const theadCheckbox = document.querySelector('thead input[type="checkbox"]');
256 if (theadCheckbox) {
257 checkboxContainer = theadCheckbox.closest('th, td');
258 console.log('Чекбокс найден через thead');
259 }
260 }
261
262 if (!checkboxContainer) {
263 throw new Error('Контейнер с чекбоксом не найден');
264 }
265
266 const selectAllCheckbox = checkboxContainer.querySelector('input[type="checkbox"]');
267 if (selectAllCheckbox) {
268 if (!selectAllCheckbox.checked) {
269 console.log('Выбираем все кластеры...');
270 selectAllCheckbox.click();
271 await delay(1500);
272 console.log('✓ Все кластеры выбраны');
273 } else {
274 console.log('✓ Все кластеры уже выбраны');
275 }
276 } else {
277 throw new Error('Чекбокс выбора всех кластеров не найден');
278 }
279
280 // Шаг 9: Ждем появления и кликаем на кнопку "Действия"
281 console.log('Ожидаем появления кнопки Действия...');
282 await delay(1000);
283
284 const actionsButton = await waitForElement('button:has(p.MuiTypography-root)', 5000)
285 .then(() => {
286 const buttons = Array.from(document.querySelectorAll('button'));
287 return buttons.find(btn => btn.textContent.includes('Действия'));
288 });
289
290 if (!actionsButton) {
291 throw new Error('Кнопка "Действия" не найдена после выбора кластеров');
292 }
293
294 console.log('Кликаем на кнопку Действия...');
295 actionsButton.click();
296 await delay(800);
297 console.log('✓ Меню Действия открыто');
298
299 // Шаг 10: Кликаем на раздел "Управление" в меню
300 console.log('Ищем раздел Управление в меню...');
301 await delay(300);
302
303 const managementSection = Array.from(document.querySelectorAll('.MuiTypography-root')).find(el =>
304 el.textContent.includes('Управление')
305 );
306
307 if (!managementSection) {
308 throw new Error('Раздел "Управление" не найден в меню');
309 }
310
311 console.log('Кликаем на раздел Управление...');
312 managementSection.closest('.MuiBox-root.css-hq58ok').click();
313 await delay(500);
314 console.log('✓ Подменю Управление открыто');
315
316 // Шаг 11: Ищем и кликаем на "Стратегия" в выпадающем меню
317 console.log('Ищем кнопку Стратегия в меню...');
318 await delay(300);
319
320 const strategyButton = await waitForElement('.MuiBox-root.css-1xblgnp', 3000)
321 .then(() => {
322 const menuButtons = Array.from(document.querySelectorAll('.MuiBox-root.css-1xblgnp button'));
323 return menuButtons.find(btn => btn.textContent.includes('Стратегия'));
324 });
325
326 if (!strategyButton) {
327 throw new Error('Кнопка "Стратегия" не найдена в меню');
328 }
329
330 console.log('Кликаем на кнопку Стратегия...');
331 strategyButton.click();
332 await delay(1000);
333 console.log('✓ Открыто окно стратегий');
334
335 // Шаг 12: Ищем диалоговое окно
336 console.log('Ищем диалоговое окно...');
337 const dialog = await waitForElement('.MuiDialog-container.MuiDialog-scrollPaper.css-fh1hs4', 5000);
338
339 if (dialog) {
340 console.log('✓ Диалоговое окно найдено');
341
342 await delay(500);
343
344 // Шаг 13: Вставляем стратегию в буфер обмена
345 console.log('Копируем стратегию в буфер обмена...');
346 const STRATEGY_DATA = await getCurrentStrategy();
347
348 // Пробуем несколько способов копирования
349 try {
350 // Способ 1: navigator.clipboard (современный)
351 await navigator.clipboard.writeText(STRATEGY_DATA);
352 console.log('✓ Стратегия скопирована через navigator.clipboard');
353 } catch {
354 console.log('navigator.clipboard не сработал, пробуем GM.setClipboard...');
355 // Способ 2: GM.setClipboard (fallback)
356 await GM.setClipboard(STRATEGY_DATA);
357 console.log('✓ Стратегия скопирована через GM.setClipboard');
358 }
359
360 console.log('Длина стратегии:', STRATEGY_DATA.length);
361
362 await delay(3000);
363
364 // Шаг 14: Ищем и кликаем кнопку "Вставить стратегию"
365 console.log('Ищем кнопку Вставить стратегию...');
366
367 let pasteButton = Array.from(document.querySelectorAll('button.css-5kbhos')).find(btn =>
368 btn.textContent.includes('Вставить')
369 );
370
371 if (!pasteButton) {
372 pasteButton = Array.from(dialog.querySelectorAll('button')).find(btn =>
373 btn.textContent.includes('Вставить')
374 );
375 }
376
377 if (!pasteButton) {
378 throw new Error('Кнопка "Вставить стратегию" не найдена в диалоге');
379 }
380
381 console.log('Кликаем на кнопку Вставить стратегию...');
382 pasteButton.click();
383 await delay(1000);
384 console.log('✓ Стратегия вставлена');
385
386 // Проверяем, нет ли сообщения об ошибке
387 await delay(500);
388 const errorMessage = document.querySelector('.MuiAlert-message, [role="alert"]');
389 if (errorMessage && errorMessage.textContent.includes('некорректн')) {
390 console.error('❌ Обнаружена ошибка валидации стратегии:', errorMessage.textContent);
391 throw new Error('Некорректная стратегия: ' + errorMessage.textContent);
392 }
393 }
394
395 // Шаг 15: Ищем и кликаем кнопку "Применить"
396 console.log('Ищем кнопку Применить...');
397 await delay(500);
398
399 const applyButton = Array.from(document.querySelectorAll('button.MuiButton-contained.css-eqlbov')).find(btn =>
400 btn.textContent.includes('Применить')
401 );
402
403 if (!applyButton) {
404 const buttons = Array.from(document.querySelectorAll('button'));
405 const applyBtn = buttons.find(btn =>
406 btn.textContent.includes('Применить') &&
407 btn.classList.contains('MuiButton-contained')
408 );
409
410 if (applyBtn) {
411 console.log('Кликаем на кнопку Применить...');
412 applyBtn.click();
413 console.log('✓ Стратегия применена в диалоге');
414 } else {
415 throw new Error('Кнопка "Применить" не найдена');
416 }
417 } else {
418 console.log('Кликаем на кнопку Применить...');
419 applyButton.click();
420 console.log('✓ Стратегия применена в диалоге');
421 }
422
423 // Шаг 16: Ждем закрытия диалога и ищем кнопку "Сохранить"
424 console.log('Ждем закрытия диалога...');
425 await delay(1500);
426
427 console.log('Ищем кнопку Сохранить...');
428 const saveButton = Array.from(document.querySelectorAll('button.MuiButton-contained.css-tn31lt')).find(btn =>
429 btn.textContent.includes('Сохранить')
430 );
431
432 if (!saveButton) {
433 const buttons = Array.from(document.querySelectorAll('button'));
434 const saveBtn = buttons.find(btn =>
435 btn.textContent.includes('Сохранить') &&
436 btn.classList.contains('MuiButton-sizeSmall')
437 );
438
439 if (saveBtn) {
440 console.log('Кликаем на кнопку Сохранить...');
441 saveBtn.click();
442 await delay(2000);
443 console.log('✅ Стратегия успешно применена и сохранена!');
444 } else {
445 throw new Error('Кнопка "Сохранить" не найдена');
446 }
447 } else {
448 console.log('Кликаем на кнопку Сохранить...');
449 saveButton.click();
450 await delay(2000);
451 console.log('✅ Стратегия успешно применена и сохранена!');
452 }
453
454 return true;
455
456 } catch (error) {
457 console.error('❌ Ошибка при применении стратегии:', error);
458 console.error('❌ Детали ошибки:', error.message);
459 console.error('❌ Stack trace:', error.stack);
460 return false;
461 }
462 }
463
464 // Функция для создания кнопки запуска
465 function createAutoStrategyButton() {
466 // Проверяем, что мы на странице кампании
467 if (!window.location.href.includes('/campaigns/auto-campaigns/')) {
468 return;
469 }
470
471 // Ждем загрузки страницы
472 const checkAndCreateButton = () => {
473 // Ищем контейнер для кнопки (рядом с кнопкой "Действия")
474 const actionsContainer = document.querySelector('.MuiBox-root.css-16hxjyz');
475
476 if (actionsContainer && !document.getElementById('auto-strategy-btn')) {
477 console.log('Создаем кнопку автоназначения стратегии...');
478
479 // Создаем контейнер для нашей кнопки
480 const buttonContainer = document.createElement('div');
481 buttonContainer.className = 'MuiBox-root css-0';
482 buttonContainer.style.marginLeft = '10px';
483
484 // Создаем кнопку
485 const button = document.createElement('button');
486 button.id = 'auto-strategy-btn';
487 button.className = 'MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButton-colorPrimary MuiButton-fullWidth css-1rll63h';
488 button.type = 'button';
489 button.style.backgroundColor = '#4caf50';
490 button.style.minWidth = '200px';
491 button.style.padding = '8px 16px';
492 button.style.borderRadius = '8px';
493 button.style.whiteSpace = 'nowrap';
494
495 const buttonText = document.createElement('p');
496 buttonText.className = 'MuiTypography-root MuiTypography-body1 css-16sc9v5';
497 buttonText.textContent = '🚀 Авто-Cтратегия';
498 buttonText.style.margin = '0';
499 buttonText.style.padding = '0';
500
501 button.appendChild(buttonText);
502 buttonContainer.appendChild(button);
503
504 // Вставляем кнопку рядом с "Действия"
505 actionsContainer.parentElement.insertBefore(buttonContainer, actionsContainer.nextSibling);
506
507 // Добавляем обработчик клика
508 button.addEventListener('click', async () => {
509 button.disabled = true;
510 buttonText.textContent = '⏳ Применяем стратегию...';
511
512 const success = await applyStrategyToClusters();
513
514 if (success) {
515 buttonText.textContent = '✅ Стратегия применена!';
516 setTimeout(() => {
517 buttonText.textContent = '🚀 Автоназначение стратегии';
518 button.disabled = false;
519 }, 3000);
520 } else {
521 buttonText.textContent = '❌ Ошибка';
522 setTimeout(() => {
523 buttonText.textContent = '🚀 Автоназначение стратегии';
524 button.disabled = false;
525 }, 3000);
526 }
527 });
528
529 console.log('✓ Кнопка автоназначения стратегии создана');
530 }
531 };
532
533 // Проверяем сразу и через интервалы
534 checkAndCreateButton();
535 const interval = setInterval(() => {
536 checkAndCreateButton();
537 // Останавливаем проверку после успешного создания кнопки
538 if (document.getElementById('auto-strategy-btn')) {
539 clearInterval(interval);
540 }
541 }, 1000);
542
543 // Останавливаем проверку через 10 секунд
544 setTimeout(() => clearInterval(interval), 10000);
545 }
546
547 // Функция для получения всех ссылок на кампании
548 async function getAllCampaignLinks() {
549 console.log('Начинаем загрузку всех кампаний...');
550
551 // Находим контейнер с прокруткой
552 const tableContainer = document.querySelector('.container.MuiBox-root.css-9hf803');
553
554 if (!tableContainer) {
555 console.error('Контейнер таблицы не найден');
556 return [];
557 }
558
559 console.log('Контейнер найден, начинаем прокрутку...');
560 console.log(`Высота контейнера: ${tableContainer.scrollHeight}px`);
561
562 // Собираем уникальные ссылки во время прокрутки
563 const uniqueLinks = new Set();
564
565 // Прокручиваем контейнер постепенно, чтобы загрузить все кампании
566 let previousLinksCount = 0;
567 let stableCount = 0;
568 const maxAttempts = 200; // Увеличиваем максимум попыток
569 let attempts = 0;
570 const scrollStep = 500; // Прокручиваем по 500px за раз
571
572 while (attempts < maxAttempts) {
573 // Собираем ссылки на текущем шаге
574 const currentLinks = document.querySelectorAll('a[href*="/campaigns/auto-campaigns/"][href*="/campaign"]');
575 currentLinks.forEach(link => {
576 uniqueLinks.add(link.href);
577 });
578
579 const currentCount = uniqueLinks.size;
580 console.log(`Загружено кампаний: ${currentCount}, прокрутка: ${tableContainer.scrollTop}/${tableContainer.scrollHeight}`);
581
582 // Прокручиваем контейнер постепенно
583 tableContainer.scrollTop += scrollStep;
584
585 // Ждем загрузки новых элементов
586 await delay(500);
587
588 // Если количество не изменилось
589 if (currentCount === previousLinksCount) {
590 stableCount++;
591 // Если количество стабильно 5 раз подряд - значит все загружено
592 if (stableCount >= 5) {
593 console.log('Все кампании загружены');
594 break;
595 }
596 } else {
597 stableCount = 0;
598 previousLinksCount = currentCount;
599 }
600
601 // Если достигли конца контейнера
602 if (tableContainer.scrollTop + tableContainer.clientHeight >= tableContainer.scrollHeight - 10) {
603 console.log('Достигнут конец контейнера');
604 // Ждем еще немного для загрузки последних элементов
605 await delay(1000);
606
607 // Собираем последние ссылки
608 const finalLinks = document.querySelectorAll('a[href*="/campaigns/auto-campaigns/"][href*="/campaign"]');
609 finalLinks.forEach(link => {
610 uniqueLinks.add(link.href);
611 });
612
613 // Проверяем еще раз количество
614 if (uniqueLinks.size === previousLinksCount) {
615 break;
616 }
617 previousLinksCount = uniqueLinks.size;
618 }
619
620 attempts++;
621 }
622
623 // Преобразуем Set в массив
624 const links = Array.from(uniqueLinks);
625
626 console.log(`Найдено кампаний: ${links.length}`);
627 console.log(`Всего попыток прокрутки: ${attempts}`);
628 return links;
629 }
630
631 // Функция для перехода к следующей кампании
632 async function processNextCampaign() {
633 const campaignLinks = await GM.getValue('mp_campaign_links', []);
634 const currentIndex = await GM.getValue('mp_current_index', 0);
635 const nextIndex = currentIndex + 1;
636
637 console.log(`Обработано кампаний: ${nextIndex} из ${campaignLinks.length}`);
638
639 if (nextIndex < campaignLinks.length) {
640 // Сохраняем новый индекс
641 await GM.setValue('mp_current_index', nextIndex);
642
643 // Открываем следующую кампанию
644 console.log(`Открываем кампанию ${nextIndex + 1}...`);
645 await GM.openInTab(campaignLinks[nextIndex], false);
646 } else {
647 // Все кампании обработаны
648 console.log('✅ Все кампании обработаны!');
649 await GM.setValue('mp_bulk_processing', false);
650 await GM.setValue('mp_current_index', 0);
651 await GM.setValue('mp_campaign_links', []);
652 alert('Обработка завершена! Все кампании обработаны.');
653 }
654 }
655
656 // Функция для создания модального окна управления стратегиями
657 function createStrategyManagementModal() {
658 return new Promise(async (resolve) => {
659 // Создаем оверлей
660 const overlay = document.createElement('div');
661 overlay.style.cssText = `
662 position: fixed;
663 top: 0;
664 left: 0;
665 width: 100%;
666 height: 100%;
667 background: rgba(0, 0, 0, 0.5);
668 display: flex;
669 justify-content: center;
670 align-items: center;
671 z-index: 10000;
672 `;
673
674 // Создаем модальное окно
675 const modal = document.createElement('div');
676 modal.style.cssText = `
677 background: white;
678 border-radius: 12px;
679 padding: 24px;
680 min-width: 500px;
681 max-width: 600px;
682 max-height: 80vh;
683 overflow-y: auto;
684 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
685 `;
686
687 // Заголовок
688 const title = document.createElement('h2');
689 title.textContent = 'Управление стратегиями';
690 title.style.cssText = `
691 margin: 0 0 20px 0;
692 font-size: 24px;
693 font-weight: bold;
694 color: #333;
695 `;
696 modal.appendChild(title);
697
698 // Контейнер для списка стратегий
699 const listContainer = document.createElement('div');
700 listContainer.style.cssText = `
701 margin-bottom: 20px;
702 max-height: 300px;
703 overflow-y: auto;
704 `;
705
706 // Функция для обновления списка
707 const updateList = async () => {
708 const currentStrategies = await GM.getValue('mp_saved_strategies', []);
709 listContainer.innerHTML = '';
710
711 if (currentStrategies.length === 0) {
712 const emptyMessage = document.createElement('p');
713 emptyMessage.textContent = 'Нет сохраненных стратегий. Используется стратегия по умолчанию.';
714 emptyMessage.style.cssText = `
715 color: #666;
716 font-style: italic;
717 padding: 20px;
718 text-align: center;
719 `;
720 listContainer.appendChild(emptyMessage);
721 } else {
722 currentStrategies.forEach((strategy, index) => {
723 const item = document.createElement('div');
724 item.style.cssText = `
725 display: flex;
726 justify-content: space-between;
727 align-items: center;
728 padding: 12px;
729 margin-bottom: 8px;
730 background: #f5f5f5;
731 border-radius: 8px;
732 border: 1px solid #ddd;
733 `;
734
735 const nameSpan = document.createElement('span');
736 nameSpan.textContent = strategy.name;
737 nameSpan.style.cssText = `
738 font-size: 16px;
739 color: #333;
740 flex: 1;
741 `;
742
743 const deleteBtn = document.createElement('button');
744 deleteBtn.textContent = '🗑️ Удалить';
745 deleteBtn.style.cssText = `
746 background: #f44336;
747 color: white;
748 border: none;
749 padding: 6px 12px;
750 border-radius: 6px;
751 cursor: pointer;
752 font-size: 14px;
753 `;
754 deleteBtn.onmouseover = () => deleteBtn.style.background = '#d32f2f';
755 deleteBtn.onmouseout = () => deleteBtn.style.background = '#f44336';
756 deleteBtn.onclick = async () => {
757 if (confirm(`Удалить стратегию "${strategy.name}"?`)) {
758 currentStrategies.splice(index, 1);
759 await GM.setValue('mp_saved_strategies', currentStrategies);
760 updateList();
761 }
762 };
763
764 item.appendChild(nameSpan);
765 item.appendChild(deleteBtn);
766 listContainer.appendChild(item);
767 });
768 }
769 };
770
771 await updateList();
772 modal.appendChild(listContainer);
773
774 // Кнопка добавления стратегии
775 const addButton = document.createElement('button');
776 addButton.textContent = '➕ Добавить новую стратегию';
777 addButton.style.cssText = `
778 width: 100%;
779 background: #4caf50;
780 color: white;
781 border: none;
782 padding: 12px;
783 border-radius: 8px;
784 cursor: pointer;
785 font-size: 16px;
786 font-weight: bold;
787 margin-bottom: 12px;
788 `;
789 addButton.onmouseover = () => addButton.style.background = '#45a049';
790 addButton.onmouseout = () => addButton.style.background = '#4caf50';
791 addButton.onclick = async () => {
792 const name = prompt('Введите название стратегии:');
793 if (!name) return;
794
795 const data = prompt('Вставьте код стратегии (скопируйте из MP Manager):');
796 if (!data) return;
797
798 const currentStrategies = await GM.getValue('mp_saved_strategies', []);
799 currentStrategies.push({ name, data });
800 await GM.setValue('mp_saved_strategies', currentStrategies);
801 alert(`Стратегия "${name}" сохранена!`);
802 updateList();
803 };
804 modal.appendChild(addButton);
805
806 // Кнопка закрытия
807 const closeButton = document.createElement('button');
808 closeButton.textContent = 'Закрыть';
809 closeButton.style.cssText = `
810 width: 100%;
811 background: #666;
812 color: white;
813 border: none;
814 padding: 12px;
815 border-radius: 8px;
816 cursor: pointer;
817 font-size: 16px;
818 `;
819 closeButton.onmouseover = () => closeButton.style.background = '#555';
820 closeButton.onmouseout = () => closeButton.style.background = '#666';
821 closeButton.onclick = () => {
822 document.body.removeChild(overlay);
823 resolve();
824 };
825 modal.appendChild(closeButton);
826
827 overlay.appendChild(modal);
828 document.body.appendChild(overlay);
829
830 // Закрытие по клику на оверлей
831 overlay.onclick = (e) => {
832 if (e.target === overlay) {
833 document.body.removeChild(overlay);
834 resolve();
835 }
836 };
837 });
838 }
839
840 // Функция для создания кнопки на странице списка кампаний
841 function createBulkProcessButton() {
842 // Проверяем, что мы на странице списка кампаний
843 if (!window.location.href.includes('/advert/campaigns')) {
844 return;
845 }
846
847 const checkAndCreateButton = async () => {
848 // Ищем контейнер MuiBox-root css-1omrdwk для размещения кнопки
849 const targetContainer = document.querySelector('.MuiBox-root.css-1omrdwk');
850
851 if (targetContainer && !document.getElementById('bulk-strategy-btn')) {
852 console.log('Создаем кнопку массового назначения стратегии...');
853
854 // Создаем кнопку
855 const button = document.createElement('button');
856 button.id = 'bulk-strategy-btn';
857 button.className = 'MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButton-colorPrimary css-1rll63h';
858 button.type = 'button';
859 button.style.cssText = `
860 background-color: #4caf50 !important;
861 min-width: 300px !important;
862 padding: 12px 20px !important;
863 font-size: 14px !important;
864 font-weight: bold !important;
865 border-radius: 8px !important;
866 white-space: nowrap !important;
867 height: auto !important;
868 line-height: 1.5 !important;
869 `;
870
871 const buttonText = document.createElement('span');
872 buttonText.textContent = '🚀 Авто-Стратегии';
873 buttonText.style.cssText = `
874 display: block !important;
875 padding: 0 !important;
876 margin: 0 !important;
877 `;
878
879 button.appendChild(buttonText);
880
881 // Вставляем кнопку в контейнер
882 targetContainer.appendChild(button);
883
884 // Создаем кнопку управления стратегиями
885 const manageButton = document.createElement('button');
886 manageButton.id = 'manage-strategies-btn';
887 manageButton.className = 'MuiButtonBase-root MuiButton-root MuiButton-outlined MuiButton-outlinedPrimary MuiButton-sizeMedium css-1rll63h';
888 manageButton.type = 'button';
889 manageButton.style.marginLeft = '10px';
890 manageButton.style.padding = '10px 20px';
891 manageButton.style.fontSize = '14px';
892
893 const manageButtonText = document.createElement('span');
894 manageButtonText.textContent = '⚙️ Управление стратегиями';
895
896 manageButton.appendChild(manageButtonText);
897 targetContainer.appendChild(manageButton);
898
899 // Обработчик для управления стратегиями
900 manageButton.addEventListener('click', async () => {
901 await createStrategyManagementModal();
902 });
903
904 // Проверяем, идет ли уже обработка
905 const isProcessing = await GM.getValue('mp_bulk_processing', false);
906 if (isProcessing) {
907 const currentIndex = await GM.getValue('mp_current_index', 0);
908 const totalCampaigns = await GM.getValue('mp_total_campaigns', 0);
909 button.style.backgroundColor = '#f44336';
910 buttonText.textContent = `⏹️ Остановить (${currentIndex + 1}/${totalCampaigns})`;
911 }
912
913 // Добавляем обработчик клика
914 button.addEventListener('click', async () => {
915 const isProcessing = await GM.getValue('mp_bulk_processing', false);
916
917 if (isProcessing) {
918 // Останавливаем обработку
919 const confirmed = confirm('Остановить автоматическую обработку кампаний?');
920 if (confirmed) {
921 await GM.setValue('mp_bulk_processing', false);
922 await GM.setValue('mp_current_index', 0);
923 await GM.setValue('mp_campaign_links', []);
924 button.style.backgroundColor = '#4caf50';
925 buttonText.textContent = '🚀 Автоназначение стратегии всем кампаниям';
926 alert('Обработка остановлена');
927 }
928 return;
929 }
930
931 button.disabled = true;
932 buttonText.textContent = '⏳ Загружаем список кампаний...';
933
934 const campaignLinks = await getAllCampaignLinks();
935 const campaignCount = campaignLinks.length;
936
937 if (campaignCount === 0) {
938 alert('Не найдено ни одной кампании для обработки');
939 button.disabled = false;
940 buttonText.textContent = '🚀 Автоназначение стратегии всем кампаниям';
941 return;
942 }
943
944 // Получаем список сохраненных стратегий
945 const strategies = await getSavedStrategies();
946
947 // Всегда показываем диалог выбора стратегии
948 const strategyList = strategies.map((s, i) => `${i + 1}. ${s.name}`).join('\n');
949 const choice = prompt(`Найдено кампаний: ${campaignCount}\n\nВыберите стратегию (введите номер):\n\n${strategyList}\n\nИли нажмите "Отмена" для выхода`);
950
951 if (!choice) {
952 button.disabled = false;
953 buttonText.textContent = '🚀 Автоназначение стратегии всем кампаниям';
954 return;
955 }
956
957 const choiceIndex = parseInt(choice) - 1;
958 if (choiceIndex < 0 || choiceIndex >= strategies.length) {
959 alert('Неверный номер стратегии');
960 button.disabled = false;
961 buttonText.textContent = '🚀 Автоназначение стратегии всем кампаниям';
962 return;
963 }
964
965 const strategyChoice = strategies[choiceIndex].data;
966 await GM.setValue('mp_current_strategy', strategyChoice);
967 console.log(`Выбрана стратегия: ${strategies[choiceIndex].name}`);
968
969 const confirmed = confirm(`Начать автоматическое назначение стратегии для всех ${campaignCount} кампаний?\n\nПроцесс будет полностью автоматическим.`);
970
971 if (!confirmed) {
972 button.disabled = false;
973 buttonText.textContent = '🚀 Автоназначение стратегии всем кампаниям';
974 return;
975 }
976
977 buttonText.textContent = '⏳ Запуск обработки...';
978
979 // Сохраняем список кампаний
980 await GM.setValue('mp_campaign_links', campaignLinks);
981 await GM.setValue('mp_current_index', 0);
982 await GM.setValue('mp_bulk_processing', true);
983 await GM.setValue('mp_total_campaigns', campaignLinks.length);
984
985 console.log('✓ Данные сохранены, открываем первую кампанию...');
986
987 // Открываем первую кампанию
988 await GM.openInTab(campaignLinks[0], false);
989
990 button.style.backgroundColor = '#f44336';
991 buttonText.textContent = '⏹️ Остановить обработку';
992 button.disabled = false;
993 });
994
995 console.log('✓ Кнопка массового назначения стратегии создана');
996 }
997 };
998
999 // Проверяем сразу и через интервалы
1000 checkAndCreateButton();
1001 const interval = setInterval(() => {
1002 checkAndCreateButton();
1003 if (document.getElementById('bulk-strategy-btn')) {
1004 clearInterval(interval);
1005 }
1006 }, 1000);
1007
1008 setTimeout(() => clearInterval(interval), 10000);
1009 }
1010
1011 // Инициализация
1012 async function init() {
1013 console.log('Инициализация расширения MP Manager...');
1014
1015 // Проверяем, нужно ли автоматически запустить обработку
1016 if (window.location.href.includes('/campaigns/auto-campaigns/')) {
1017 const isProcessing = await GM.getValue('mp_bulk_processing', false);
1018
1019 if (isProcessing) {
1020 console.log('🤖 Обнаружена активная массовая обработка');
1021 // Запускаем автоматическую обработку через 3 секунды после загрузки
1022 setTimeout(async () => {
1023 console.log('🚀 Автоматический запуск обработки кампании...');
1024 const success = await applyStrategyToClusters();
1025
1026 if (success) {
1027 console.log('✅ Кампания обработана, переходим к следующей...');
1028 await delay(2000);
1029 await processNextCampaign();
1030 // Закрываем текущую вкладку
1031 window.close();
1032 } else {
1033 console.error('❌ Ошибка обработки, пропускаем кампанию...');
1034 await delay(2000);
1035 await processNextCampaign();
1036 window.close();
1037 }
1038 }, 3000);
1039 }
1040 }
1041
1042 // Ждем полной загрузки DOM
1043 if (document.readyState === 'loading') {
1044 document.addEventListener('DOMContentLoaded', () => {
1045 createAutoStrategyButton();
1046 createBulkProcessButton();
1047 });
1048 } else {
1049 createAutoStrategyButton();
1050 createBulkProcessButton();
1051 }
1052
1053 // Наблюдаем за изменениями в DOM (для SPA)
1054 const observer = new MutationObserver(() => {
1055 if (!document.getElementById('auto-strategy-btn')) {
1056 createAutoStrategyButton();
1057 }
1058 if (!document.getElementById('bulk-strategy-btn')) {
1059 createBulkProcessButton();
1060 }
1061 });
1062
1063 observer.observe(document.body, {
1064 childList: true,
1065 subtree: true
1066 });
1067 }
1068
1069 // Запускаем инициализацию
1070 init();
1071
1072})();