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

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