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