MP Manager Position OZON Strategy Setter

Автоматическая установка стратегии рекламной кампании на основе статистики

Size

44.0 KB

Version

1.8.134

Created

Feb 18, 2026

Updated

about 1 month ago

1// ==UserScript==
2// @name		MP Manager Position OZON Strategy Setter
3// @description		Автоматическая установка стратегии рекламной кампании на основе статистики
4// @version		1.8.134
5// @match		https://*.app.mpmgr.ru/*
6// @icon		https://app.mpmgr.ru/favicon.ico
7// @grant		GM.getValue
8// @grant		GM.setValue
9// @grant		GM.deleteValue
10// @grant		GM.openInTab
11// @grant		GM.setClipboard
12// ==/UserScript==
13(function() {
14    'use strict';
15
16    console.log('Ozon Campaign Manager загружен');
17
18    // Глобальные переменные для массовой обработки
19    let isProcessing = false;
20    let strategyType = 'position'; // 'position' или 'autopilot'
21    let targetPosition = 1;
22    let minBid = 10;
23    let maxBid = 100;
24    let desiredAdSpendPercent = 10;
25
26    // Функция для ожидания
27    function wait(ms) {
28        return new Promise(resolve => setTimeout(resolve, ms));
29    }
30
31    // Функция для создания UI панели на странице списка кампаний
32    function createCampaignListUI() {
33        console.log('Создание UI панели управления кампаниями');
34        
35        // Удаляем старую панель, если она существует
36        const oldPanel = document.getElementById('ozon-campaign-manager-panel');
37        if (oldPanel) {
38            oldPanel.remove();
39        }
40        
41        const panel = document.createElement('div');
42        panel.id = 'ozon-campaign-manager-panel';
43        panel.style.cssText = `
44            position: fixed;
45            top: 20px;
46            right: 20px;
47            background: linear-gradient(135deg, #005bff 0%, #0041b8 100%);
48            padding: 20px;
49            border-radius: 12px;
50            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
51            z-index: 10000;
52            min-width: 320px;
53            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
54            color: white;
55            transition: all 0.3s ease;
56            cursor: move;
57        `;
58
59        // Создаем заголовок
60        const header = document.createElement('div');
61        header.id = 'panel-header';
62        header.style.cssText = 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; cursor: pointer;';
63        header.innerHTML = `
64            <h3 style="margin: 0; font-size: 18px; font-weight: 600;">🎯 Управление кампаниями Ozon</h3>
65            <span id="toggle-icon" style="font-size: 18px;"></span>
66        `;
67        panel.appendChild(header);
68
69        // Создаем контент панели
70        const content = document.createElement('div');
71        content.id = 'panel-content';
72        content.style.display = 'none';
73        
74        // Селектор типа стратегии
75        const strategyTypeContainer = document.createElement('div');
76        strategyTypeContainer.style.marginBottom = '15px';
77        
78        const strategyLabel = document.createElement('label');
79        strategyLabel.style.cssText = 'display: block; margin-bottom: 8px; font-size: 13px; font-weight: 500;';
80        strategyLabel.textContent = 'Тип стратегии:';
81        strategyTypeContainer.appendChild(strategyLabel);
82        
83        const strategySelect = document.createElement('select');
84        strategySelect.id = 'strategy-type';
85        strategySelect.style.cssText = `
86            width: 100%;
87            padding: 10px;
88            border: none;
89            border-radius: 8px;
90            font-size: 14px;
91            box-sizing: border-box;
92            background: rgba(255, 255, 255, 0.95);
93            color: #333;
94            cursor: pointer;
95        `;
96        
97        const positionOption = document.createElement('option');
98        positionOption.value = 'position';
99        positionOption.textContent = 'Ставка под позицию';
100        strategySelect.appendChild(positionOption);
101        
102        const autopilotOption = document.createElement('option');
103        autopilotOption.value = 'autopilot';
104        autopilotOption.textContent = 'Автопилот';
105        strategySelect.appendChild(autopilotOption);
106        
107        // Добавляем обработчик СРАЗУ на элемент, до добавления в DOM
108        strategySelect.addEventListener('change', (e) => {
109            strategyType = e.target.value;
110            const positionSettingsEl = document.getElementById('position-settings');
111            const autopilotSettingsEl = document.getElementById('autopilot-settings');
112            const strategyName = document.getElementById('strategy-name');
113            
114            console.log('Переключение стратегии на:', strategyType);
115            
116            if (strategyType === 'position') {
117                if (positionSettingsEl) positionSettingsEl.style.display = 'block';
118                if (autopilotSettingsEl) autopilotSettingsEl.style.display = 'none';
119                if (strategyName) strategyName.textContent = 'Ставка под позицию';
120            } else {
121                if (positionSettingsEl) positionSettingsEl.style.display = 'none';
122                if (autopilotSettingsEl) autopilotSettingsEl.style.display = 'block';
123                if (strategyName) strategyName.textContent = 'Автопилот';
124            }
125            
126            updateCampaignsCount();
127        });
128        
129        strategyTypeContainer.appendChild(strategySelect);
130        content.appendChild(strategyTypeContainer);
131
132        // Настройки для "Ставка под позицию"
133        const positionSettings = document.createElement('div');
134        positionSettings.id = 'position-settings';
135        positionSettings.style.display = 'block';
136        positionSettings.innerHTML = `
137            <div style="margin-bottom: 15px;">
138                <label style="display: block; margin-bottom: 8px; font-size: 13px; font-weight: 500;">
139                    Позиция:
140                </label>
141                <input 
142                    type="number" 
143                    id="target-position" 
144                    value="1" 
145                    min="1" 
146                    max="100"
147                    style="
148                        width: 100%;
149                        padding: 10px;
150                        border: none;
151                        border-radius: 8px;
152                        font-size: 14px;
153                        box-sizing: border-box;
154                        background: rgba(255, 255, 255, 0.95);
155                        color: #333;
156                    "
157                />
158            </div>
159            <div style="margin-bottom: 15px;">
160                <label style="display: block; margin-bottom: 8px; font-size: 13px; font-weight: 500;">
161                    Минимальная ставка (₽):
162                </label>
163                <input 
164                    type="number" 
165                    id="min-bid-position" 
166                    value="10" 
167                    min="1"
168                    step="0.01"
169                    style="
170                        width: 100%;
171                        padding: 10px;
172                        border: none;
173                        border-radius: 8px;
174                        font-size: 14px;
175                        box-sizing: border-box;
176                        background: rgba(255, 255, 255, 0.95);
177                        color: #333;
178                    "
179                />
180            </div>
181            <div style="margin-bottom: 15px;">
182                <label style="display: block; margin-bottom: 8px; font-size: 13px; font-weight: 500;">
183                    Максимальная ставка (₽):
184                </label>
185                <input 
186                    type="number" 
187                    id="max-bid-position" 
188                    value="100" 
189                    min="1"
190                    step="0.01"
191                    style="
192                        width: 100%;
193                        padding: 10px;
194                        border: none;
195                        border-radius: 8px;
196                        font-size: 14px;
197                        box-sizing: border-box;
198                        background: rgba(255, 255, 255, 0.95);
199                        color: #333;
200                    "
201                />
202            </div>
203        `;
204        content.appendChild(positionSettings);
205
206        // Настройки для "Автопилот"
207        const autopilotSettings = document.createElement('div');
208        autopilotSettings.id = 'autopilot-settings';
209        autopilotSettings.style.display = 'none';
210        autopilotSettings.innerHTML = `
211            <div style="margin-bottom: 15px;">
212                <label style="display: block; margin-bottom: 8px; font-size: 13px; font-weight: 500;">
213                    Минимальная ставка (₽):
214                </label>
215                <input 
216                    type="number" 
217                    id="min-bid-autopilot" 
218                    value="10" 
219                    min="1"
220                    step="0.01"
221                    style="
222                        width: 100%;
223                        padding: 10px;
224                        border: none;
225                        border-radius: 8px;
226                        font-size: 14px;
227                        box-sizing: border-box;
228                        background: rgba(255, 255, 255, 0.95);
229                        color: #333;
230                    "
231                />
232            </div>
233            <div style="margin-bottom: 15px;">
234                <label style="display: block; margin-bottom: 8px; font-size: 13px; font-weight: 500;">
235                    Максимальная ставка (₽):
236                </label>
237                <input 
238                    type="number" 
239                    id="max-bid-autopilot" 
240                    value="100" 
241                    min="1"
242                    step="0.01"
243                    style="
244                        width: 100%;
245                        padding: 10px;
246                        border: none;
247                        border-radius: 8px;
248                        font-size: 14px;
249                        box-sizing: border-box;
250                        background: rgba(255, 255, 255, 0.95);
251                        color: #333;
252                    "
253                />
254            </div>
255            <div style="margin-bottom: 15px;">
256                <label style="display: block; margin-bottom: 8px; font-size: 13px; font-weight: 500;">
257                    Желаемый % рекламных расходов:
258                </label>
259                <input 
260                    type="number" 
261                    id="ad-spend-percent" 
262                    value="10" 
263                    min="0.1"
264                    max="100"
265                    step="0.1"
266                    style="
267                        width: 100%;
268                        padding: 10px;
269                        border: none;
270                        border-radius: 8px;
271                        font-size: 14px;
272                        box-sizing: border-box;
273                        background: rgba(255, 255, 255, 0.95);
274                        color: #333;
275                    "
276                />
277            </div>
278        `;
279        content.appendChild(autopilotSettings);
280
281        // Информация о кампаниях
282        const campaignsInfo = document.createElement('div');
283        campaignsInfo.id = 'campaigns-info';
284        campaignsInfo.style.cssText = `
285            margin-bottom: 15px;
286            padding: 10px;
287            border-radius: 8px;
288            font-size: 12px;
289            background: rgba(255, 255, 255, 0.2);
290        `;
291        campaignsInfo.innerHTML = 'Найдено кампаний "<span id="strategy-name">Ставка под позицию</span>": <span id="campaigns-count">0</span>';
292        content.appendChild(campaignsInfo);
293
294        // Кнопка "Запустить обработку"
295        const startBtn = document.createElement('button');
296        startBtn.id = 'start-processing-btn';
297        startBtn.textContent = 'Запустить обработку';
298        startBtn.style.cssText = `
299            width: 100%;
300            padding: 12px;
301            background: white;
302            color: #005bff;
303            border: none;
304            border-radius: 8px;
305            font-size: 14px;
306            font-weight: 600;
307            cursor: pointer;
308            transition: all 0.3s ease;
309            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
310            margin-bottom: 10px;
311        `;
312        startBtn.onmouseover = function() {
313            this.style.transform = 'translateY(-2px)';
314            this.style.boxShadow = '0 6px 16px rgba(0, 0, 0, 0.2)';
315        };
316        startBtn.onmouseout = function() {
317            this.style.transform = 'translateY(0)';
318            this.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
319        };
320        startBtn.addEventListener('click', startProcessing);
321        content.appendChild(startBtn);
322
323        // Кнопка "Остановить обработку"
324        const stopBtn = document.createElement('button');
325        stopBtn.id = 'stop-processing-btn';
326        stopBtn.textContent = 'Остановить обработку';
327        stopBtn.style.cssText = `
328            width: 100%;
329            padding: 12px;
330            background: #ff3b30;
331            color: white;
332            border: none;
333            border-radius: 8px;
334            font-size: 14px;
335            font-weight: 600;
336            cursor: pointer;
337            transition: all 0.3s ease;
338            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
339            margin-bottom: 10px;
340            display: none;
341        `;
342        stopBtn.onmouseover = function() {
343            this.style.transform = 'translateY(-2px)';
344            this.style.boxShadow = '0 6px 16px rgba(0, 0, 0, 0.2)';
345        };
346        stopBtn.onmouseout = function() {
347            this.style.transform = 'translateY(0)';
348            this.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
349        };
350        stopBtn.addEventListener('click', stopProcessing);
351        content.appendChild(stopBtn);
352
353        // Сообщение о статусе
354        const statusMessage = document.createElement('div');
355        statusMessage.id = 'status-message';
356        statusMessage.style.cssText = `
357            margin-top: 15px;
358            padding: 10px;
359            border-radius: 8px;
360            font-size: 12px;
361            background: rgba(255, 255, 255, 0.2);
362            display: none;
363        `;
364        content.appendChild(statusMessage);
365
366        // Прогресс
367        const progress = document.createElement('div');
368        progress.id = 'progress';
369        progress.style.cssText = 'margin-top: 15px; display: none;';
370        progress.innerHTML = `
371            <div style="margin-bottom: 5px; font-size: 12px;">
372                Прогресс: <span id="progress-text">0/0</span>
373            </div>
374            <div style="
375                width: 100%;
376                height: 8px;
377                background: rgba(255, 255, 255, 0.3);
378                border-radius: 4px;
379                overflow: hidden;
380            ">
381                <div id="progress-bar" style="
382                    width: 0%;
383                    height: 100%;
384                    background: white;
385                    transition: width 0.3s ease;
386                "></div>
387            </div>
388        `;
389        content.appendChild(progress);
390
391        panel.appendChild(content);
392        
393        // Добавляем обработчик клика на заголовок для разворачивания/сворачивания
394        header.addEventListener('click', () => {
395            const icon = document.getElementById('toggle-icon');
396            if (content.style.display === 'none') {
397                content.style.display = 'block';
398                icon.textContent = '▲';
399            } else {
400                content.style.display = 'none';
401                icon.textContent = '▼';
402            }
403        });
404
405        // Добавляем возможность перетаскивания
406        makeDraggable(panel);
407
408        document.body.appendChild(panel);
409        
410        console.log('UI панель добавлена в DOM');
411        
412        // Подсчитываем количество кампаний
413        updateCampaignsCount();
414    }
415
416    // Функция для создания перетаскиваемого элемента
417    function makeDraggable(element) {
418        let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
419        let isDragging = false;
420
421        element.onmousedown = dragMouseDown;
422
423        function dragMouseDown(e) {
424            if (e.target.tagName === 'INPUT' || e.target.tagName === 'BUTTON' || e.target.tagName === 'SELECT' || e.target.tagName === 'TEXTAREA') {
425                return;
426            }
427            
428            e.preventDefault();
429            isDragging = true;
430            pos3 = e.clientX;
431            pos4 = e.clientY;
432            document.onmouseup = closeDragElement;
433            document.onmousemove = elementDrag;
434        }
435
436        function elementDrag(e) {
437            if (!isDragging) return;
438            pos1 = pos3 - e.clientX;
439            pos2 = pos4 - e.clientY;
440            pos3 = e.clientX;
441            pos4 = e.clientY;
442            element.style.top = (element.offsetTop - pos2) + 'px';
443            element.style.left = (element.offsetLeft - pos1) + 'px';
444            element.style.right = 'auto';
445            element.style.bottom = 'auto';
446        }
447
448        function closeDragElement() {
449            isDragging = false;
450            document.onmouseup = null;
451            document.onmousemove = null;
452        }
453    }
454
455    // Функция для обновления количества кампаний
456    function updateCampaignsCount() {
457        const campaigns = findCampaignsByStrategy();
458        const countElement = document.getElementById('campaigns-count');
459        if (countElement) {
460            countElement.textContent = campaigns.length;
461        }
462        const strategyName = strategyType === 'position' ? 'Ставка под позицию' : 'Автопилот';
463        console.log(`Найдено кампаний "${strategyName}": ${campaigns.length}`);
464    }
465
466    // Функция для поиска кампаний по выбранной стратегии
467    function findCampaignsByStrategy() {
468        const campaigns = [];
469        const searchText = strategyType === 'position' ? 'Ставка под позицию' : 'Автопилот';
470        
471        const allElements = document.querySelectorAll('.MuiTypography-body2');
472        
473        for (const element of allElements) {
474            if (element.textContent.includes(searchText)) {
475                const row = element.closest('tr');
476                if (row) {
477                    const link = row.querySelector('a[href*="/campaigns/auto-campaigns/"]');
478                    if (link && link.href) {
479                        campaigns.push(link.href);
480                    }
481                }
482            }
483        }
484        
485        return campaigns;
486    }
487
488    // Функция для поиска всех кампаний с прокруткой страницы
489    async function findAllCampaignsByStrategy() {
490        const searchText = strategyType === 'position' ? 'Ставка под позицию' : 'Автопилот';
491        console.log(`Начинаем поиск всех кампаний "${searchText}" с прокруткой...`);
492        showStatus(`Поиск кампаний "${searchText}"... Прокручиваем страницу...`);
493        
494        const container = document.querySelector('.container.MuiBox-root.css-9hf803');
495        if (!container) {
496            console.error('Контейнер таблицы не найден');
497            showStatus('Ошибка: контейнер таблицы не найден', true);
498            return [];
499        }
500        
501        console.log('Контейнер найден, начинаем прокрутку');
502        
503        const campaigns = new Set();
504        let previousCount = 0;
505        let noChangeCount = 0;
506        const maxNoChangeAttempts = 5;
507        const scrollStep = 500;
508        
509        while (noChangeCount < maxNoChangeAttempts) {
510            const allElements = document.querySelectorAll('.MuiTypography-body2');
511            
512            for (const element of allElements) {
513                if (element.textContent.includes(searchText)) {
514                    const row = element.closest('tr');
515                    if (row) {
516                        const link = row.querySelector('a[href*="/campaigns/auto-campaigns/"]');
517                        if (link && link.href) {
518                            campaigns.add(link.href);
519                        }
520                    }
521                }
522            }
523            
524            const currentCount = campaigns.size;
525            console.log(`Найдено кампаний: ${currentCount}`);
526            showStatus(`Поиск кампаний "${searchText}"... Найдено: ${currentCount}`);
527            
528            if (currentCount === previousCount) {
529                noChangeCount++;
530            } else {
531                noChangeCount = 0;
532                previousCount = currentCount;
533            }
534            
535            const currentScroll = container.scrollTop;
536            const maxScroll = container.scrollHeight - container.clientHeight;
537            
538            if (currentScroll < maxScroll) {
539                container.scrollBy(0, scrollStep);
540                console.log(`Прокрутка: ${container.scrollTop} / ${maxScroll}`);
541            }
542            
543            await wait(1500);
544        }
545        
546        container.scrollTop = 0;
547        
548        const campaignsArray = Array.from(campaigns);
549        console.log(`Всего найдено кампаний "${searchText}": ${campaignsArray.length}`);
550        
551        return campaignsArray;
552    }
553
554    // Функция для показа статуса
555    function showStatus(message, isError = false) {
556        const statusDiv = document.getElementById('status-message');
557        if (statusDiv) {
558            statusDiv.textContent = message;
559            statusDiv.style.display = 'block';
560            statusDiv.style.background = isError ? 'rgba(255, 59, 48, 0.9)' : 'rgba(52, 199, 89, 0.9)';
561            console.log(`Статус: ${message}`);
562        }
563    }
564
565    // Функция для обновления прогресса
566    function updateProgress(current, total) {
567        const progressDiv = document.getElementById('progress');
568        const progressText = document.getElementById('progress-text');
569        const progressBar = document.getElementById('progress-bar');
570        
571        if (progressDiv && progressText && progressBar) {
572            progressDiv.style.display = 'block';
573            progressText.textContent = `${current}/${total}`;
574            const percentage = (current / total) * 100;
575            progressBar.style.width = `${percentage}%`;
576        }
577    }
578
579    // Функция для запуска обработки кампаний
580    async function startProcessing() {
581        if (isProcessing) {
582            showStatus('Обработка уже выполняется', true);
583            return;
584        }
585
586        try {
587            isProcessing = true;
588            console.log('Начало обработки кампаний');
589            
590            document.getElementById('start-processing-btn').style.display = 'none';
591            document.getElementById('stop-processing-btn').style.display = 'block';
592            
593            if (strategyType === 'position') {
594                targetPosition = parseInt(document.getElementById('target-position').value);
595                minBid = parseFloat(document.getElementById('min-bid-position').value);
596                maxBid = parseFloat(document.getElementById('max-bid-position').value);
597                
598                if (!targetPosition || targetPosition <= 0) {
599                    showStatus('Ошибка: введите корректную позицию', true);
600                    isProcessing = false;
601                    document.getElementById('start-processing-btn').style.display = 'block';
602                    document.getElementById('stop-processing-btn').style.display = 'none';
603                    return;
604                }
605            } else {
606                minBid = parseFloat(document.getElementById('min-bid-autopilot').value);
607                maxBid = parseFloat(document.getElementById('max-bid-autopilot').value);
608                desiredAdSpendPercent = parseFloat(document.getElementById('ad-spend-percent').value);
609                
610                if (!desiredAdSpendPercent || desiredAdSpendPercent <= 0) {
611                    showStatus('Ошибка: введите корректный % рекламных расходов', true);
612                    isProcessing = false;
613                    document.getElementById('start-processing-btn').style.display = 'block';
614                    document.getElementById('stop-processing-btn').style.display = 'none';
615                    return;
616                }
617            }
618            
619            if (!minBid || minBid <= 0) {
620                showStatus('Ошибка: введите корректную минимальную ставку', true);
621                isProcessing = false;
622                document.getElementById('start-processing-btn').style.display = 'block';
623                document.getElementById('stop-processing-btn').style.display = 'none';
624                return;
625            }
626            
627            if (!maxBid || maxBid <= 0) {
628                showStatus('Ошибка: введите корректную максимальную ставку', true);
629                isProcessing = false;
630                document.getElementById('start-processing-btn').style.display = 'block';
631                document.getElementById('stop-processing-btn').style.display = 'none';
632                return;
633            }
634            
635            if (minBid > maxBid) {
636                showStatus('Ошибка: минимальная ставка больше максимальной', true);
637                isProcessing = false;
638                document.getElementById('start-processing-btn').style.display = 'block';
639                document.getElementById('stop-processing-btn').style.display = 'none';
640                return;
641            }
642            
643            if (strategyType === 'position') {
644                console.log(`Параметры: позиция=${targetPosition}, мин.ставка=${minBid}, макс.ставка=${maxBid}`);
645            } else {
646                console.log(`Параметры: мин.ставка=${minBid}, макс.ставка=${maxBid}, % расходов=${desiredAdSpendPercent}`);
647            }
648
649            const campaigns = await findAllCampaignsByStrategy();
650            
651            const strategyName = strategyType === 'position' ? 'Ставка под позицию' : 'Автопилот';
652            if (campaigns.length === 0) {
653                showStatus(`Ошибка: кампании "${strategyName}" не найдены`, true);
654                isProcessing = false;
655                document.getElementById('start-processing-btn').style.display = 'block';
656                document.getElementById('stop-processing-btn').style.display = 'none';
657                return;
658            }
659
660            console.log(`Найдено кампаний для обработки: ${campaigns.length}`);
661            showStatus(`Начинаем обработку ${campaigns.length} кампаний...`);
662            updateProgress(0, campaigns.length);
663
664            await GM.setValue('ozonProcessingData', JSON.stringify({
665                campaigns: campaigns,
666                currentIndex: 0,
667                totalCampaigns: campaigns.length,
668                strategyType: strategyType,
669                targetPosition: targetPosition,
670                minBid: minBid,
671                maxBid: maxBid,
672                desiredAdSpendPercent: desiredAdSpendPercent
673            }));
674
675            if (campaigns.length > 0) {
676                console.log(`Открытие кампании 1/${campaigns.length}: ${campaigns[0]}`);
677                window.open(campaigns[0], '_blank');
678            }
679
680        } catch (error) {
681            console.error('Ошибка при запуске обработки:', error);
682            showStatus(`Ошибка: ${error.message}`, true);
683            isProcessing = false;
684            document.getElementById('start-processing-btn').style.display = 'block';
685            document.getElementById('stop-processing-btn').style.display = 'none';
686        }
687    }
688
689    // Функция для остановки обработки
690    async function stopProcessing() {
691        console.log('Остановка обработки кампаний');
692        
693        try {
694            await GM.deleteValue('ozonProcessingData');
695            
696            isProcessing = false;
697            
698            document.getElementById('start-processing-btn').style.display = 'block';
699            document.getElementById('stop-processing-btn').style.display = 'none';
700            
701            showStatus('Обработка остановлена', false);
702            
703            console.log('Обработка успешно остановлена');
704        } catch (error) {
705            console.error('Ошибка при остановке обработки:', error);
706            showStatus(`Ошибка при остановке: ${error.message}`, true);
707        }
708    }
709
710    // Функция для обработки кампании (на странице кампании)
711    async function processCampaign() {
712        try {
713            console.log('Начало обработки кампании');
714            showCampaignStatus('Загрузка данных...');
715
716            const dataJson = await GM.getValue('ozonProcessingData', null);
717            if (!dataJson) {
718                console.log('Нет данных для обработки');
719                return;
720            }
721
722            const data = JSON.parse(dataJson);
723            console.log('Данные обработки:', data);
724
725            await wait(2000);
726
727            if (data.strategyType === 'position') {
728                await processPositionBidCampaign(data);
729            } else {
730                await processAutopilotCampaign(data);
731            }
732
733        } catch (error) {
734            console.error('Ошибка при обработке кампании:', error);
735            showCampaignStatus(`Ошибка: ${error.message}`, true);
736            
737            const dataJson = await GM.getValue('ozonProcessingData', null);
738            if (dataJson) {
739                const data = JSON.parse(dataJson);
740                await handleCampaignError(data);
741            }
742        }
743    }
744
745    // Функция для обработки кампании "Ставка под позицию"
746    async function processPositionBidCampaign(data) {
747        showCampaignStatus('Установка позиции...');
748        const positionInputs = document.querySelectorAll('input[type="number"]');
749        let positionInput = null;
750        
751        for (const input of positionInputs) {
752            const label = document.querySelector(`label[for="${input.id}"]`);
753            if (label && label.textContent.includes('Позиция')) {
754                positionInput = input;
755                break;
756            }
757        }
758
759        if (!positionInput) {
760            positionInput = document.querySelector('input[name*="position"][type="number"]');
761        }
762
763        if (!positionInput) {
764            showCampaignStatus('Ошибка: поле "Позиция" не найдено', true);
765            await handleCampaignError(data);
766            return;
767        }
768
769        positionInput.focus();
770        positionInput.value = data.targetPosition.toString();
771        positionInput.dispatchEvent(new Event('input', { bubbles: true }));
772        positionInput.dispatchEvent(new Event('change', { bubbles: true }));
773        console.log(`Позиция установлена: ${data.targetPosition}`);
774        await wait(500);
775
776        showCampaignStatus('Установка минимальной ставки...');
777        let minBidInput = null;
778        
779        for (const input of positionInputs) {
780            const label = document.querySelector(`label[for="${input.id}"]`);
781            if (label && label.textContent.includes('Минимальная ставка')) {
782                minBidInput = input;
783                break;
784            }
785        }
786
787        if (!minBidInput) {
788            minBidInput = document.querySelector('input[name*="minBid"][type="number"]');
789        }
790
791        if (!minBidInput) {
792            showCampaignStatus('Ошибка: поле "Минимальная ставка" не найдено', true);
793            await handleCampaignError(data);
794            return;
795        }
796
797        minBidInput.focus();
798        minBidInput.value = data.minBid.toString();
799        minBidInput.dispatchEvent(new Event('input', { bubbles: true }));
800        minBidInput.dispatchEvent(new Event('change', { bubbles: true }));
801        console.log(`Минимальная ставка установлена: ${data.minBid}`);
802        await wait(500);
803
804        showCampaignStatus('Установка максимальной ставки...');
805        let maxBidInput = null;
806        
807        for (const input of positionInputs) {
808            const label = document.querySelector(`label[for="${input.id}"]`);
809            if (label && label.textContent.includes('Максимальная ставка')) {
810                maxBidInput = input;
811                break;
812            }
813        }
814
815        if (!maxBidInput) {
816            maxBidInput = document.querySelector('input[name*="maxBid"][type="number"]');
817        }
818
819        if (!maxBidInput) {
820            showCampaignStatus('Ошибка: поле "Максимальная ставка" не найдено', true);
821            await handleCampaignError(data);
822            return;
823        }
824
825        maxBidInput.focus();
826        maxBidInput.value = data.maxBid.toString();
827        maxBidInput.dispatchEvent(new Event('input', { bubbles: true }));
828        maxBidInput.dispatchEvent(new Event('change', { bubbles: true }));
829        console.log(`Максимальная ставка установлена: ${data.maxBid}`);
830        await wait(500);
831
832        await saveCampaign(data);
833    }
834
835    // Функция для обработки кампании "Автопилот"
836    async function processAutopilotCampaign(data) {
837        const numberInputs = document.querySelectorAll('input[type="number"]');
838        
839        showCampaignStatus('Установка минимальной ставки...');
840        let minBidInput = null;
841        
842        for (const input of numberInputs) {
843            const label = document.querySelector(`label[for="${input.id}"]`);
844            if (label && label.textContent.includes('Минимальная ставка')) {
845                minBidInput = input;
846                break;
847            }
848        }
849
850        if (!minBidInput) {
851            minBidInput = document.querySelector('input[name*="minBid"][type="number"]');
852        }
853
854        if (!minBidInput) {
855            showCampaignStatus('Ошибка: поле "Минимальная ставка" не найдено', true);
856            await handleCampaignError(data);
857            return;
858        }
859
860        minBidInput.focus();
861        minBidInput.value = data.minBid.toString();
862        minBidInput.dispatchEvent(new Event('input', { bubbles: true }));
863        minBidInput.dispatchEvent(new Event('change', { bubbles: true }));
864        console.log(`Минимальная ставка установлена: ${data.minBid}`);
865        await wait(500);
866
867        showCampaignStatus('Установка максимальной ставки...');
868        let maxBidInput = null;
869        
870        for (const input of numberInputs) {
871            const label = document.querySelector(`label[for="${input.id}"]`);
872            if (label && label.textContent.includes('Максимальная ставка')) {
873                maxBidInput = input;
874                break;
875            }
876        }
877
878        if (!maxBidInput) {
879            maxBidInput = document.querySelector('input[name*="maxBid"][type="number"]');
880        }
881
882        if (!maxBidInput) {
883            showCampaignStatus('Ошибка: поле "Максимальная ставка" не найдено', true);
884            await handleCampaignError(data);
885            return;
886        }
887
888        maxBidInput.focus();
889        maxBidInput.value = data.maxBid.toString();
890        maxBidInput.dispatchEvent(new Event('input', { bubbles: true }));
891        maxBidInput.dispatchEvent(new Event('change', { bubbles: true }));
892        console.log(`Максимальная ставка установлена: ${data.maxBid}`);
893        await wait(500);
894
895        showCampaignStatus('Установка % рекламных расходов...');
896        let adSpendInput = null;
897        
898        // Ищем поле по name атрибуту с паттерном autoCampaignProducts.*.strategy.campaignStats.rules.*.value
899        adSpendInput = document.querySelector('input[type="number"][name*="strategy.campaignStats.rules"][name*=".value"]');
900        
901        if (!adSpendInput) {
902            // Пробуем найти по label
903            for (const input of numberInputs) {
904                const label = document.querySelector(`label[for="${input.id}"]`);
905                if (label && (label.textContent.includes('Желаемый % рекламных расходов') || label.textContent.includes('рекламных расходов'))) {
906                    adSpendInput = input;
907                    break;
908                }
909            }
910        }
911
912        if (!adSpendInput) {
913            showCampaignStatus('Ошибка: поле "% рекламных расходов" не найдено', true);
914            await handleCampaignError(data);
915            return;
916        }
917
918        adSpendInput.focus();
919        adSpendInput.value = data.desiredAdSpendPercent.toString();
920        adSpendInput.dispatchEvent(new Event('input', { bubbles: true }));
921        adSpendInput.dispatchEvent(new Event('change', { bubbles: true }));
922        console.log(`% рекламных расходов установлен: ${data.desiredAdSpendPercent}`);
923        await wait(500);
924
925        await saveCampaign(data);
926    }
927
928    // Функция для сохранения кампании
929    async function saveCampaign(data) {
930        showCampaignStatus('Сохранение...');
931        const saveButtons = document.querySelectorAll('button');
932        let saveButton = null;
933
934        for (const btn of saveButtons) {
935            if (btn.textContent.trim() === 'Сохранить') {
936                saveButton = btn;
937                break;
938            }
939        }
940
941        if (!saveButton) {
942            showCampaignStatus('Ошибка: кнопка "Сохранить" не найдена', true);
943            await handleCampaignError(data);
944            return;
945        }
946
947        saveButton.click();
948        console.log('Клик на "Сохранить" выполнен');
949        await wait(2000);
950
951        showCampaignStatus('✅ Кампания обработана успешно!');
952        console.log('Кампания обработана успешно');
953
954        await processNextCampaign(data);
955    }
956
957    // Функция для перехода к следующей кампании
958    async function processNextCampaign(data) {
959        const nextIndex = data.currentIndex + 1;
960        
961        console.log(`Обработано кампаний: ${nextIndex} из ${data.totalCampaigns}`);
962        
963        const dataJson = await GM.getValue('ozonProcessingData', null);
964        if (!dataJson) {
965            console.log('Обработка была остановлена пользователем');
966            showCampaignStatus('⚠️ Обработка остановлена', true);
967            await wait(2000);
968            window.close();
969            return;
970        }
971        
972        if (nextIndex < data.campaigns.length) {
973            data.currentIndex = nextIndex;
974            await GM.setValue('ozonProcessingData', JSON.stringify(data));
975            
976            await wait(1000);
977            
978            console.log(`Открываем кампанию ${nextIndex + 1}: ${data.campaigns[nextIndex]}`);
979            window.open(data.campaigns[nextIndex], '_blank');
980            
981            await wait(500);
982            window.close();
983        } else {
984            console.log('Все кампании обработаны');
985            await GM.deleteValue('ozonProcessingData');
986            
987            showCampaignStatus('✅ Все кампании обработаны!');
988            
989            await wait(3000);
990            window.close();
991        }
992    }
993
994    // Функция для обработки ошибок
995    async function handleCampaignError(data) {
996        console.log('Обработка ошибки, ждем 5 секунд перед переходом к следующей кампании');
997        showCampaignStatus('⚠️ Ошибка! Переход к следующей кампании через 5 секунд...', true);
998        
999        for (let i = 5; i > 0; i--) {
1000            showCampaignStatus(`⚠️ Ошибка! Переход через ${i} сек...`, true);
1001            await wait(1000);
1002        }
1003        
1004        await processNextCampaign(data);
1005    }
1006
1007    // Функция для показа статуса на странице кампании
1008    function showCampaignStatus(message, isError = false) {
1009        let statusDiv = document.getElementById('ozon-campaign-status');
1010        
1011        if (!statusDiv) {
1012            statusDiv = document.createElement('div');
1013            statusDiv.id = 'ozon-campaign-status';
1014            statusDiv.style.cssText = `
1015                position: fixed;
1016                top: 20px;
1017                right: 20px;
1018                padding: 15px 20px;
1019                border-radius: 8px;
1020                font-size: 14px;
1021                font-weight: 600;
1022                z-index: 10000;
1023                box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
1024                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1025            `;
1026            document.body.appendChild(statusDiv);
1027        }
1028        
1029        statusDiv.textContent = message;
1030        statusDiv.style.background = isError ? '#ff3b30' : '#34c759';
1031        statusDiv.style.color = 'white';
1032        console.log(`Статус кампании: ${message}`);
1033    }
1034
1035    // Инициализация
1036    function init() {
1037        console.log('Инициализация Ozon Campaign Manager');
1038        
1039        if (window.location.href.includes('/advert/campaigns') && window.location.href.includes('marketplace=Ozon')) {
1040            console.log('Страница списка кампаний Ozon обнаружена');
1041            setTimeout(() => {
1042                if (document.body) {
1043                    createCampaignListUI();
1044                } else {
1045                    console.error('Body не найден');
1046                }
1047            }, 1000);
1048        } else if (window.location.href.includes('/campaigns/auto-campaigns/') && window.location.href.includes('/ozon/campaign')) {
1049            console.log('Страница кампании Ozon обнаружена');
1050            
1051            GM.getValue('ozonProcessingData', null).then(dataJson => {
1052                if (dataJson) {
1053                    console.log('Найдены данные для автоматической обработки');
1054                    setTimeout(() => {
1055                        processCampaign();
1056                    }, 2000);
1057                } else {
1058                    console.log('Нет данных для автоматической обработки');
1059                }
1060            });
1061        } else {
1062            console.log('Не на странице кампаний Ozon, UI не создается');
1063        }
1064    }
1065
1066    init();
1067
1068})();