Автоматическая установка стратегии рекламной кампании на основе статистики
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})();