Автоматическое назначение стратегии для выбранных кластеров в MP Manager
Size
53.4 KB
Version
1.1.45
Created
Feb 5, 2026
Updated
12 days 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 selectAllCheckbox = document.querySelector('.MuiFormControlLabel-root.css-ixot9 input[type="checkbox"]');
247
248 // Если не найден, пробуем альтернативные селекторы
249 if (!selectAllCheckbox) {
250 console.log('Пробуем альтернативный селектор для чекбокса...');
251 selectAllCheckbox = document.querySelector('label.MuiFormControlLabel-root input[type="checkbox"][data-indeterminate]');
252 }
253
254 if (!selectAllCheckbox) {
255 console.log('Пробуем найти чекбокс в заголовке таблицы...');
256 const tableHeader = document.querySelector('thead');
257 if (tableHeader) {
258 selectAllCheckbox = tableHeader.querySelector('input[type="checkbox"]');
259 }
260 }
261
262 if (selectAllCheckbox) {
263 if (!selectAllCheckbox.checked) {
264 console.log('Выбираем все кластеры...');
265 selectAllCheckbox.click();
266 await delay(1500);
267 console.log('✓ Все кластеры выбраны');
268 } else {
269 console.log('✓ Все кластеры уже выбраны');
270 }
271 } else {
272 throw new Error('Чекбокс выбора всех кластеров не найден');
273 }
274
275 // Шаг 9: Ждем появления и кликаем на кнопку "Действия"
276 console.log('Ожидаем появления кнопки Действия...');
277 await delay(1000);
278
279 const actionsButton = await waitForElement('button:has(p.MuiTypography-root)', 5000)
280 .then(() => {
281 const buttons = Array.from(document.querySelectorAll('button'));
282 return buttons.find(btn => btn.textContent.includes('Действия'));
283 });
284
285 if (!actionsButton) {
286 throw new Error('Кнопка "Действия" не найдена после выбора кластеров');
287 }
288
289 console.log('Кликаем на кнопку Действия...');
290 actionsButton.click();
291 await delay(800);
292 console.log('✓ Меню Действия открыто');
293
294 // Шаг 10: Кликаем на раздел "Управление" в меню
295 console.log('Ищем раздел Управление в меню...');
296 await delay(300);
297
298 const managementSection = Array.from(document.querySelectorAll('.MuiTypography-root')).find(el =>
299 el.textContent.includes('Управление')
300 );
301
302 if (!managementSection) {
303 throw new Error('Раздел "Управление" не найден в меню');
304 }
305
306 console.log('Кликаем на раздел Управление...');
307 managementSection.closest('.MuiBox-root.css-hq58ok').click();
308 await delay(500);
309 console.log('✓ Подменю Управление открыто');
310
311 // Шаг 11: Ищем и кликаем на "Стратегия" в выпадающем меню
312 console.log('Ищем кнопку Стратегия в меню...');
313 await delay(300);
314
315 const strategyButton = await waitForElement('.MuiBox-root.css-1xblgnp', 3000)
316 .then(() => {
317 const menuButtons = Array.from(document.querySelectorAll('.MuiBox-root.css-1xblgnp button'));
318 return menuButtons.find(btn => btn.textContent.includes('Стратегия'));
319 });
320
321 if (!strategyButton) {
322 throw new Error('Кнопка "Стратегия" не найдена в меню');
323 }
324
325 console.log('Кликаем на кнопку Стратегия...');
326 strategyButton.click();
327 await delay(1000);
328 console.log('✓ Открыто окно стратегий');
329
330 // Шаг 12: Ищем диалоговое окно
331 console.log('Ищем диалоговое окно...');
332 const dialog = await waitForElement('.MuiDialog-container.MuiDialog-scrollPaper.css-fh1hs4', 5000);
333
334 if (dialog) {
335 console.log('✓ Диалоговое окно найдено');
336
337 await delay(500);
338
339 // Шаг 13: Вставляем стратегию в буфер обмена
340 console.log('Копируем стратегию в буфер обмена...');
341 const STRATEGY_DATA = await getCurrentStrategy();
342
343 // Пробуем несколько способов копирования
344 try {
345 // Способ 1: navigator.clipboard (современный)
346 await navigator.clipboard.writeText(STRATEGY_DATA);
347 console.log('✓ Стратегия скопирована через navigator.clipboard');
348 } catch (clipboardError) {
349 console.log('navigator.clipboard не сработал, пробуем GM.setClipboard...');
350 // Способ 2: GM.setClipboard (fallback)
351 await GM.setClipboard(STRATEGY_DATA);
352 console.log('✓ Стратегия скопирована через GM.setClipboard');
353 }
354
355 console.log('Длина стратегии:', STRATEGY_DATA.length);
356
357 await delay(3000);
358
359 // Шаг 14: Ищем и кликаем кнопку "Вставить стратегию"
360 console.log('Ищем кнопку Вставить стратегию...');
361
362 let pasteButton = Array.from(document.querySelectorAll('button.css-5kbhos')).find(btn =>
363 btn.textContent.includes('Вставить')
364 );
365
366 if (!pasteButton) {
367 pasteButton = Array.from(dialog.querySelectorAll('button')).find(btn =>
368 btn.textContent.includes('Вставить')
369 );
370 }
371
372 if (!pasteButton) {
373 throw new Error('Кнопка "Вставить стратегию" не найдена в диалоге');
374 }
375
376 console.log('Кликаем на кнопку Вставить стратегию...');
377 pasteButton.click();
378 await delay(1000);
379 console.log('✓ Стратегия вставлена');
380
381 // Проверяем, нет ли сообщения об ошибке
382 await delay(500);
383 const errorMessage = document.querySelector('.MuiAlert-message, [role="alert"]');
384 if (errorMessage && errorMessage.textContent.includes('некорректн')) {
385 console.error('❌ Обнаружена ошибка валидации стратегии:', errorMessage.textContent);
386 throw new Error('Некорректная стратегия: ' + errorMessage.textContent);
387 }
388 }
389
390 // Шаг 15: Ищем и кликаем кнопку "Применить"
391 console.log('Ищем кнопку Применить...');
392 await delay(500);
393
394 const applyButton = Array.from(document.querySelectorAll('button.MuiButton-contained.css-eqlbov')).find(btn =>
395 btn.textContent.includes('Применить')
396 );
397
398 if (!applyButton) {
399 const buttons = Array.from(document.querySelectorAll('button'));
400 const applyBtn = buttons.find(btn =>
401 btn.textContent.includes('Применить') &&
402 btn.classList.contains('MuiButton-contained')
403 );
404
405 if (applyBtn) {
406 console.log('Кликаем на кнопку Применить...');
407 applyBtn.click();
408 console.log('✓ Стратегия применена в диалоге');
409 } else {
410 throw new Error('Кнопка "Применить" не найдена');
411 }
412 } else {
413 console.log('Кликаем на кнопку Применить...');
414 applyButton.click();
415 console.log('✓ Стратегия применена в диалоге');
416 }
417
418 // Шаг 16: Ждем закрытия диалога и ищем кнопку "Сохранить"
419 console.log('Ждем закрытия диалога...');
420 await delay(1500);
421
422 console.log('Ищем кнопку Сохранить...');
423 const saveButton = Array.from(document.querySelectorAll('button.MuiButton-contained.css-tn31lt')).find(btn =>
424 btn.textContent.includes('Сохранить')
425 );
426
427 if (!saveButton) {
428 const buttons = Array.from(document.querySelectorAll('button'));
429 const saveBtn = buttons.find(btn =>
430 btn.textContent.includes('Сохранить') &&
431 btn.classList.contains('MuiButton-sizeSmall')
432 );
433
434 if (saveBtn) {
435 console.log('Кликаем на кнопку Сохранить...');
436 saveBtn.click();
437 await delay(2000);
438 console.log('✅ Стратегия успешно применена и сохранена!');
439 } else {
440 throw new Error('Кнопка "Сохранить" не найдена');
441 }
442 } else {
443 console.log('Кликаем на кнопку Сохранить...');
444 saveButton.click();
445 await delay(2000);
446 console.log('✅ Стратегия успешно применена и сохранена!');
447 }
448
449 return true;
450
451 } catch (error) {
452 console.error('❌ Ошибка при применении стратегии:', error);
453 console.error('❌ Детали ошибки:', error.message);
454 console.error('❌ Stack trace:', error.stack);
455 return false;
456 }
457 }
458
459 // Функция для создания кнопки запуска
460 function createAutoStrategyButton() {
461 // Проверяем, что мы на странице кампании
462 if (!window.location.href.includes('/campaigns/auto-campaigns/')) {
463 return;
464 }
465
466 // Ждем загрузки страницы
467 const checkAndCreateButton = () => {
468 // Ищем контейнер для кнопки (рядом с кнопкой "Действия")
469 const actionsContainer = document.querySelector('.MuiBox-root.css-16hxjyz');
470
471 if (actionsContainer && !document.getElementById('auto-strategy-btn')) {
472 console.log('Создаем кнопку автоназначения стратегии...');
473
474 // Создаем контейнер для нашей кнопки
475 const buttonContainer = document.createElement('div');
476 buttonContainer.className = 'MuiBox-root css-0';
477 buttonContainer.style.marginLeft = '10px';
478
479 // Создаем кнопку
480 const button = document.createElement('button');
481 button.id = 'auto-strategy-btn';
482 button.className = 'MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButton-colorPrimary MuiButton-fullWidth css-1rll63h';
483 button.type = 'button';
484 button.style.backgroundColor = '#4caf50';
485 button.style.minWidth = '200px';
486 button.style.padding = '8px 16px';
487 button.style.borderRadius = '8px';
488 button.style.whiteSpace = 'nowrap';
489
490 const buttonText = document.createElement('p');
491 buttonText.className = 'MuiTypography-root MuiTypography-body1 css-16sc9v5';
492 buttonText.textContent = '🚀 Авто-Cтратегия';
493 buttonText.style.margin = '0';
494 buttonText.style.padding = '0';
495
496 button.appendChild(buttonText);
497 buttonContainer.appendChild(button);
498
499 // Вставляем кнопку рядом с "Действия"
500 actionsContainer.parentElement.insertBefore(buttonContainer, actionsContainer.nextSibling);
501
502 // Добавляем обработчик клика
503 button.addEventListener('click', async () => {
504 button.disabled = true;
505 buttonText.textContent = '⏳ Применяем стратегию...';
506
507 const success = await applyStrategyToClusters();
508
509 if (success) {
510 buttonText.textContent = '✅ Стратегия применена!';
511 setTimeout(() => {
512 buttonText.textContent = '🚀 Автоназначение стратегии';
513 button.disabled = false;
514 }, 3000);
515 } else {
516 buttonText.textContent = '❌ Ошибка';
517 setTimeout(() => {
518 buttonText.textContent = '🚀 Автоназначение стратегии';
519 button.disabled = false;
520 }, 3000);
521 }
522 });
523
524 console.log('✓ Кнопка автоназначения стратегии создана');
525 }
526 };
527
528 // Проверяем сразу и через интервалы
529 checkAndCreateButton();
530 const interval = setInterval(() => {
531 checkAndCreateButton();
532 // Останавливаем проверку после успешного создания кнопки
533 if (document.getElementById('auto-strategy-btn')) {
534 clearInterval(interval);
535 }
536 }, 1000);
537
538 // Останавливаем проверку через 10 секунд
539 setTimeout(() => clearInterval(interval), 10000);
540 }
541
542 // Функция для получения всех ссылок на кампании
543 async function getAllCampaignLinks() {
544 console.log('Начинаем загрузку всех кампаний...');
545
546 // Находим контейнер с прокруткой
547 const tableContainer = document.querySelector('.container.MuiBox-root.css-9hf803');
548
549 if (!tableContainer) {
550 console.error('Контейнер таблицы не найден');
551 return [];
552 }
553
554 console.log('Контейнер найден, начинаем прокрутку...');
555 console.log(`Высота контейнера: ${tableContainer.scrollHeight}px`);
556
557 // Собираем уникальные ссылки во время прокрутки
558 const uniqueLinks = new Set();
559
560 // Прокручиваем контейнер постепенно, чтобы загрузить все кампании
561 let previousLinksCount = 0;
562 let stableCount = 0;
563 const maxAttempts = 200; // Увеличиваем максимум попыток
564 let attempts = 0;
565 const scrollStep = 500; // Прокручиваем по 500px за раз
566
567 while (attempts < maxAttempts) {
568 // Собираем ссылки на текущем шаге
569 const currentLinks = document.querySelectorAll('a[href*="/campaigns/auto-campaigns/"][href*="/campaign"]');
570 currentLinks.forEach(link => {
571 uniqueLinks.add(link.href);
572 });
573
574 const currentCount = uniqueLinks.size;
575 console.log(`Загружено кампаний: ${currentCount}, прокрутка: ${tableContainer.scrollTop}/${tableContainer.scrollHeight}`);
576
577 // Прокручиваем контейнер постепенно
578 tableContainer.scrollTop += scrollStep;
579
580 // Ждем загрузки новых элементов
581 await delay(500);
582
583 // Если количество не изменилось
584 if (currentCount === previousLinksCount) {
585 stableCount++;
586 // Если количество стабильно 5 раз подряд - значит все загружено
587 if (stableCount >= 5) {
588 console.log('Все кампании загружены');
589 break;
590 }
591 } else {
592 stableCount = 0;
593 previousLinksCount = currentCount;
594 }
595
596 // Если достигли конца контейнера
597 if (tableContainer.scrollTop + tableContainer.clientHeight >= tableContainer.scrollHeight - 10) {
598 console.log('Достигнут конец контейнера');
599 // Ждем еще немного для загрузки последних элементов
600 await delay(1000);
601
602 // Собираем последние ссылки
603 const finalLinks = document.querySelectorAll('a[href*="/campaigns/auto-campaigns/"][href*="/campaign"]');
604 finalLinks.forEach(link => {
605 uniqueLinks.add(link.href);
606 });
607
608 // Проверяем еще раз количество
609 if (uniqueLinks.size === previousLinksCount) {
610 break;
611 }
612 previousLinksCount = uniqueLinks.size;
613 }
614
615 attempts++;
616 }
617
618 // Преобразуем Set в массив
619 const links = Array.from(uniqueLinks);
620
621 console.log(`Найдено кампаний: ${links.length}`);
622 console.log(`Всего попыток прокрутки: ${attempts}`);
623 return links;
624 }
625
626 // Функция для перехода к следующей кампании
627 async function processNextCampaign() {
628 const campaignLinks = await GM.getValue('mp_campaign_links', []);
629 const currentIndex = await GM.getValue('mp_current_index', 0);
630 const nextIndex = currentIndex + 1;
631
632 console.log(`Обработано кампаний: ${nextIndex} из ${campaignLinks.length}`);
633
634 if (nextIndex < campaignLinks.length) {
635 // Сохраняем новый индекс
636 await GM.setValue('mp_current_index', nextIndex);
637
638 // Открываем следующую кампанию
639 console.log(`Открываем кампанию ${nextIndex + 1}...`);
640 await GM.openInTab(campaignLinks[nextIndex], false);
641 } else {
642 // Все кампании обработаны
643 console.log('✅ Все кампании обработаны!');
644 await GM.setValue('mp_bulk_processing', false);
645 await GM.setValue('mp_current_index', 0);
646 await GM.setValue('mp_campaign_links', []);
647 alert('Обработка завершена! Все кампании обработаны.');
648 }
649 }
650
651 // Функция для создания модального окна управления стратегиями
652 function createStrategyManagementModal() {
653 return new Promise(async (resolve) => {
654 // Создаем оверлей
655 const overlay = document.createElement('div');
656 overlay.style.cssText = `
657 position: fixed;
658 top: 0;
659 left: 0;
660 width: 100%;
661 height: 100%;
662 background: rgba(0, 0, 0, 0.5);
663 display: flex;
664 justify-content: center;
665 align-items: center;
666 z-index: 10000;
667 `;
668
669 // Создаем модальное окно
670 const modal = document.createElement('div');
671 modal.style.cssText = `
672 background: white;
673 border-radius: 12px;
674 padding: 24px;
675 min-width: 500px;
676 max-width: 600px;
677 max-height: 80vh;
678 overflow-y: auto;
679 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
680 `;
681
682 // Заголовок
683 const title = document.createElement('h2');
684 title.textContent = 'Управление стратегиями';
685 title.style.cssText = `
686 margin: 0 0 20px 0;
687 font-size: 24px;
688 font-weight: bold;
689 color: #333;
690 `;
691 modal.appendChild(title);
692
693 // Контейнер для списка стратегий
694 const listContainer = document.createElement('div');
695 listContainer.style.cssText = `
696 margin-bottom: 20px;
697 max-height: 300px;
698 overflow-y: auto;
699 `;
700
701 // Функция для обновления списка
702 const updateList = async () => {
703 const currentStrategies = await GM.getValue('mp_saved_strategies', []);
704 listContainer.innerHTML = '';
705
706 if (currentStrategies.length === 0) {
707 const emptyMessage = document.createElement('p');
708 emptyMessage.textContent = 'Нет сохраненных стратегий. Используется стратегия по умолчанию.';
709 emptyMessage.style.cssText = `
710 color: #666;
711 font-style: italic;
712 padding: 20px;
713 text-align: center;
714 `;
715 listContainer.appendChild(emptyMessage);
716 } else {
717 currentStrategies.forEach((strategy, index) => {
718 const item = document.createElement('div');
719 item.style.cssText = `
720 display: flex;
721 justify-content: space-between;
722 align-items: center;
723 padding: 12px;
724 margin-bottom: 8px;
725 background: #f5f5f5;
726 border-radius: 8px;
727 border: 1px solid #ddd;
728 `;
729
730 const nameSpan = document.createElement('span');
731 nameSpan.textContent = strategy.name;
732 nameSpan.style.cssText = `
733 font-size: 16px;
734 color: #333;
735 flex: 1;
736 `;
737
738 const deleteBtn = document.createElement('button');
739 deleteBtn.textContent = '🗑️ Удалить';
740 deleteBtn.style.cssText = `
741 background: #f44336;
742 color: white;
743 border: none;
744 padding: 6px 12px;
745 border-radius: 6px;
746 cursor: pointer;
747 font-size: 14px;
748 `;
749 deleteBtn.onmouseover = () => deleteBtn.style.background = '#d32f2f';
750 deleteBtn.onmouseout = () => deleteBtn.style.background = '#f44336';
751 deleteBtn.onclick = async () => {
752 if (confirm(`Удалить стратегию "${strategy.name}"?`)) {
753 currentStrategies.splice(index, 1);
754 await GM.setValue('mp_saved_strategies', currentStrategies);
755 updateList();
756 }
757 };
758
759 item.appendChild(nameSpan);
760 item.appendChild(deleteBtn);
761 listContainer.appendChild(item);
762 });
763 }
764 };
765
766 await updateList();
767 modal.appendChild(listContainer);
768
769 // Кнопка добавления стратегии
770 const addButton = document.createElement('button');
771 addButton.textContent = '➕ Добавить новую стратегию';
772 addButton.style.cssText = `
773 width: 100%;
774 background: #4caf50;
775 color: white;
776 border: none;
777 padding: 12px;
778 border-radius: 8px;
779 cursor: pointer;
780 font-size: 16px;
781 font-weight: bold;
782 margin-bottom: 12px;
783 `;
784 addButton.onmouseover = () => addButton.style.background = '#45a049';
785 addButton.onmouseout = () => addButton.style.background = '#4caf50';
786 addButton.onclick = async () => {
787 const name = prompt('Введите название стратегии:');
788 if (!name) return;
789
790 const data = prompt('Вставьте код стратегии (скопируйте из MP Manager):');
791 if (!data) return;
792
793 const currentStrategies = await GM.getValue('mp_saved_strategies', []);
794 currentStrategies.push({ name, data });
795 await GM.setValue('mp_saved_strategies', currentStrategies);
796 alert(`Стратегия "${name}" сохранена!`);
797 updateList();
798 };
799 modal.appendChild(addButton);
800
801 // Кнопка закрытия
802 const closeButton = document.createElement('button');
803 closeButton.textContent = 'Закрыть';
804 closeButton.style.cssText = `
805 width: 100%;
806 background: #666;
807 color: white;
808 border: none;
809 padding: 12px;
810 border-radius: 8px;
811 cursor: pointer;
812 font-size: 16px;
813 `;
814 closeButton.onmouseover = () => closeButton.style.background = '#555';
815 closeButton.onmouseout = () => closeButton.style.background = '#666';
816 closeButton.onclick = () => {
817 document.body.removeChild(overlay);
818 resolve();
819 };
820 modal.appendChild(closeButton);
821
822 overlay.appendChild(modal);
823 document.body.appendChild(overlay);
824
825 // Закрытие по клику на оверлей
826 overlay.onclick = (e) => {
827 if (e.target === overlay) {
828 document.body.removeChild(overlay);
829 resolve();
830 }
831 };
832 });
833 }
834
835 // Функция для создания кнопки на странице списка кампаний
836 function createBulkProcessButton() {
837 // Проверяем, что мы на странице списка кампаний
838 if (!window.location.href.includes('/advert/campaigns')) {
839 return;
840 }
841
842 const checkAndCreateButton = async () => {
843 // Ищем контейнер MuiBox-root css-1omrdwk для размещения кнопки
844 const targetContainer = document.querySelector('.MuiBox-root.css-1omrdwk');
845
846 if (targetContainer && !document.getElementById('bulk-strategy-btn')) {
847 console.log('Создаем кнопку массового назначения стратегии...');
848
849 // Создаем кнопку
850 const button = document.createElement('button');
851 button.id = 'bulk-strategy-btn';
852 button.className = 'MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButton-colorPrimary css-1rll63h';
853 button.type = 'button';
854 button.style.cssText = `
855 background-color: #4caf50 !important;
856 min-width: 300px !important;
857 padding: 12px 20px !important;
858 font-size: 14px !important;
859 font-weight: bold !important;
860 border-radius: 8px !important;
861 white-space: nowrap !important;
862 height: auto !important;
863 line-height: 1.5 !important;
864 `;
865
866 const buttonText = document.createElement('span');
867 buttonText.textContent = '🚀 Авто-Стратегии';
868 buttonText.style.cssText = `
869 display: block !important;
870 padding: 0 !important;
871 margin: 0 !important;
872 `;
873
874 button.appendChild(buttonText);
875
876 // Вставляем кнопку в контейнер
877 targetContainer.appendChild(button);
878
879 // Создаем кнопку управления стратегиями
880 const manageButton = document.createElement('button');
881 manageButton.id = 'manage-strategies-btn';
882 manageButton.className = 'MuiButtonBase-root MuiButton-root MuiButton-outlined MuiButton-outlinedPrimary MuiButton-sizeMedium css-1rll63h';
883 manageButton.type = 'button';
884 manageButton.style.marginLeft = '10px';
885 manageButton.style.padding = '10px 20px';
886 manageButton.style.fontSize = '14px';
887
888 const manageButtonText = document.createElement('span');
889 manageButtonText.textContent = '⚙️ Управление стратегиями';
890
891 manageButton.appendChild(manageButtonText);
892 targetContainer.appendChild(manageButton);
893
894 // Обработчик для управления стратегиями
895 manageButton.addEventListener('click', async () => {
896 await createStrategyManagementModal();
897 });
898
899 // Проверяем, идет ли уже обработка
900 const isProcessing = await GM.getValue('mp_bulk_processing', false);
901 if (isProcessing) {
902 const currentIndex = await GM.getValue('mp_current_index', 0);
903 const totalCampaigns = await GM.getValue('mp_total_campaigns', 0);
904 button.style.backgroundColor = '#f44336';
905 buttonText.textContent = `⏹️ Остановить (${currentIndex + 1}/${totalCampaigns})`;
906 }
907
908 // Добавляем обработчик клика
909 button.addEventListener('click', async () => {
910 const isProcessing = await GM.getValue('mp_bulk_processing', false);
911
912 if (isProcessing) {
913 // Останавливаем обработку
914 const confirmed = confirm('Остановить автоматическую обработку кампаний?');
915 if (confirmed) {
916 await GM.setValue('mp_bulk_processing', false);
917 await GM.setValue('mp_current_index', 0);
918 await GM.setValue('mp_campaign_links', []);
919 button.style.backgroundColor = '#4caf50';
920 buttonText.textContent = '🚀 Автоназначение стратегии всем кампаниям';
921 alert('Обработка остановлена');
922 }
923 return;
924 }
925
926 button.disabled = true;
927 buttonText.textContent = '⏳ Загружаем список кампаний...';
928
929 const campaignLinks = await getAllCampaignLinks();
930 const campaignCount = campaignLinks.length;
931
932 if (campaignCount === 0) {
933 alert('Не найдено ни одной кампании для обработки');
934 button.disabled = false;
935 buttonText.textContent = '🚀 Автоназначение стратегии всем кампаниям';
936 return;
937 }
938
939 // Получаем список сохраненных стратегий
940 const strategies = await getSavedStrategies();
941
942 // Всегда показываем диалог выбора стратегии
943 const strategyList = strategies.map((s, i) => `${i + 1}. ${s.name}`).join('\n');
944 const choice = prompt(`Найдено кампаний: ${campaignCount}\n\nВыберите стратегию (введите номер):\n\n${strategyList}\n\nИли нажмите "Отмена" для выхода`);
945
946 if (!choice) {
947 button.disabled = false;
948 buttonText.textContent = '🚀 Автоназначение стратегии всем кампаниям';
949 return;
950 }
951
952 const choiceIndex = parseInt(choice) - 1;
953 if (choiceIndex < 0 || choiceIndex >= strategies.length) {
954 alert('Неверный номер стратегии');
955 button.disabled = false;
956 buttonText.textContent = '🚀 Автоназначение стратегии всем кампаниям';
957 return;
958 }
959
960 const strategyChoice = strategies[choiceIndex].data;
961 await GM.setValue('mp_current_strategy', strategyChoice);
962 console.log(`Выбрана стратегия: ${strategies[choiceIndex].name}`);
963
964 const confirmed = confirm(`Начать автоматическое назначение стратегии для всех ${campaignCount} кампаний?\n\nПроцесс будет полностью автоматическим.`);
965
966 if (!confirmed) {
967 button.disabled = false;
968 buttonText.textContent = '🚀 Автоназначение стратегии всем кампаниям';
969 return;
970 }
971
972 buttonText.textContent = '⏳ Запуск обработки...';
973
974 // Сохраняем список кампаний
975 await GM.setValue('mp_campaign_links', campaignLinks);
976 await GM.setValue('mp_current_index', 0);
977 await GM.setValue('mp_bulk_processing', true);
978 await GM.setValue('mp_total_campaigns', campaignLinks.length);
979
980 console.log('✓ Данные сохранены, открываем первую кампанию...');
981
982 // Открываем первую кампанию
983 await GM.openInTab(campaignLinks[0], false);
984
985 button.style.backgroundColor = '#f44336';
986 buttonText.textContent = '⏹️ Остановить обработку';
987 button.disabled = false;
988 });
989
990 console.log('✓ Кнопка массового назначения стратегии создана');
991 }
992 };
993
994 // Проверяем сразу и через интервалы
995 checkAndCreateButton();
996 const interval = setInterval(() => {
997 checkAndCreateButton();
998 if (document.getElementById('bulk-strategy-btn')) {
999 clearInterval(interval);
1000 }
1001 }, 1000);
1002
1003 setTimeout(() => clearInterval(interval), 10000);
1004 }
1005
1006 // Инициализация
1007 async function init() {
1008 console.log('Инициализация расширения MP Manager...');
1009
1010 // Проверяем, нужно ли автоматически запустить обработку
1011 if (window.location.href.includes('/campaigns/auto-campaigns/')) {
1012 const isProcessing = await GM.getValue('mp_bulk_processing', false);
1013
1014 if (isProcessing) {
1015 console.log('🤖 Обнаружена активная массовая обработка');
1016 // Запускаем автоматическую обработку через 3 секунды после загрузки
1017 setTimeout(async () => {
1018 console.log('🚀 Автоматический запуск обработки кампании...');
1019 const success = await applyStrategyToClusters();
1020
1021 if (success) {
1022 console.log('✅ Кампания обработана, переходим к следующей...');
1023 await delay(2000);
1024 await processNextCampaign();
1025 // Закрываем текущую вкладку
1026 window.close();
1027 } else {
1028 console.error('❌ Ошибка обработки, пропускаем кампанию...');
1029 await delay(2000);
1030 await processNextCampaign();
1031 window.close();
1032 }
1033 }, 3000);
1034 }
1035 }
1036
1037 // Ждем полной загрузки DOM
1038 if (document.readyState === 'loading') {
1039 document.addEventListener('DOMContentLoaded', () => {
1040 createAutoStrategyButton();
1041 createBulkProcessButton();
1042 });
1043 } else {
1044 createAutoStrategyButton();
1045 createBulkProcessButton();
1046 }
1047
1048 // Наблюдаем за изменениями в DOM (для SPA)
1049 const observer = new MutationObserver(() => {
1050 if (!document.getElementById('auto-strategy-btn')) {
1051 createAutoStrategyButton();
1052 }
1053 if (!document.getElementById('bulk-strategy-btn')) {
1054 createBulkProcessButton();
1055 }
1056 });
1057
1058 observer.observe(document.body, {
1059 childList: true,
1060 subtree: true
1061 });
1062 }
1063
1064 // Запускаем инициализацию
1065 init();
1066
1067})();