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

Автоматическое назначение стратегии для выбранных кластеров в MP Manager

Size

53.6 KB

Version

1.1.45

Created

Feb 5, 2026

Updated

about 1 month ago

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