MP Manager - Автоназначение стратегии кластерам

Автоматическое назначение стратегии для выбранных кластеров в 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})();
MP Manager - Автоназначение стратегии кластерам | Robomonkey