Автоматическая установка стратегии рекламной кампании на основе статистики
Size
139.8 KB
Version
1.8.116
Created
Feb 17, 2026
Updated
20 days ago
1// ==UserScript==
2// @name MP Manager Cluster Auto Strategy Setter
3// @description Автоматическая установка стратегии рекламной кампании на основе статистики
4// @version 1.8.116
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('MP Manager Auto Strategy Setter загружен');
17
18 // Константы
19 const STRATEGY_CODE = 'eyJjYW1wYWlnblR5cGUiOiJTZWFyY2hDYXRhbG9nIiwidmVyc2lvbiI6MiwiZGF0YSI6eyJzdGF0ZSI6IkVuYWJsZWQiLCJtb2RlIjp7InR5cGUiOiJQb3NpdGlvbiIsInBhdXNlVHlwZSI6IlJlc3VtZSJ9LCJzdHJhdGVneSI6eyJ0eXBlIjoiQ2FtcGFpZ25TdGF0cyIsIm1vZGUiOiJQbGFjZSIsImJpZE1vZGUiOiJDb3N0UGVyQ2xpY2siLCJjYW1wYWlnblN0YXRzIjp7Im1heFByaWNlIjo2MCwibWluUHJpY2UiOjEsInBsYXRmb3JtcyI6W10sImludGVydmFsIjoiV2VlayIsInJ1bGVzIjpbeyJ0eXBlIjoiQ29zdFBlckFkZGVkVG9DYXJ0IiwibW9kZSI6IlZhbHVlIiwidmFsdWUiOiIifV19fSwicGxhY2VTdHJhdGVneSI6eyJ0eXBlIjoiS2V5d29yZHMiLCJrZXl3b3JkcyI6eyJrZXl3b3JkcyI6W119fSwiaXNDbHVzdGVyIjpmYWxzZX19';
20 const CLUSTER_STRATEGY_CODE = 'eyJjYW1wYWlnblR5cGUiOiJTZWFyY2hDYXRhbG9nIiwidmVyc2lvbiI6MiwiZGF0YSI6eyJzdGF0ZSI6IkVuYWJsZWQiLCJtb2RlIjp7InR5cGUiOiJQb3NpdGlvbiIsInBhdXNlVHlwZSI6IlJlc3VtZSJ9LCJzdHJhdGVneSI6eyJ0eXBlIjoiQ2FtcGFpZ25TdGF0cyIsIm1vZGUiOiJQbGFjZSIsImJpZE1vZGUiOiJDb3N0UGVyQ2xpY2siLCJjYW1wYWlnblN0YXRzIjp7Im1heFByaWNlIjo2MCwibWluUHJpY2UiOjEsInBsYXRmb3JtcyI6W10sImludGVydmFsIjoiV2VlayIsInJ1bGVzIjpbeyJ0eXBlIjoiQ29zdFBlckFkZGVkVG9DYXJ0IiwibW9kZSI6IlZhbHVlIiwidmFsdWUiOiIifV19fSwicGxhY2VTdHJhdGVneSI6eyJ0eXBlIjoiS2V5d29yZHMiLCJrZXl3b3JkcyI6eyJrZXl3b3JkcyI6W119fSwiaXNDbHVzdGVyIjp0cnVlfX0=';
21
22 // Стратегия для удержания места (обычная)
23 const HOLD_POSITION_STRATEGY_CODE = 'eyJjYW1wYWlnblR5cGUiOiJTZWFyY2hDYXRhbG9nIiwidmVyc2lvbiI6MiwiZGF0YSI6eyJzdGF0ZSI6IkVuYWJsZWQiLCJtb2RlIjp7InR5cGUiOiJQb3NpdGlvbiIsInBhdXNlVHlwZSI6IlJlc3VtZSJ9LCJzdHJhdGVneSI6eyJ0eXBlIjoiQ2FtcGFpZ25TdGF0cyIsIm1vZGUiOiJQbGFjZSIsImJpZE1vZGUiOiJDb3N0UGVyQ2xpY2siLCJjYW1wYWlnblN0YXRzIjp7Im1heFByaWNlIjo2MCwibWluUHJpY2UiOjEsInBsYXRmb3JtcyI6W10sImludGVydmFsIjoiV2VlayIsInJ1bGVzIjpbeyJ0eXBlIjoiQ29zdFBlckFkZGVkVG9DYXJ0IiwibW9kZSI6IlZhbHVlIiwidmFsdWUiOiIifV19fSwicGxhY2VTdHJhdGVneSI6eyJ0eXBlIjoiS2V5d29yZHMiLCJrZXl3b3JkcyI6eyJrZXl3b3JkcyI6W119fSwiaXNDbHVzdGVyIjpmYWxzZX19';
24
25 // Стратегия для удержания места (кластерная)
26 const HOLD_POSITION_CLUSTER_STRATEGY_CODE = 'eyJjYW1wYWlnblR5cGUiOiJTZWFyY2hDYXRhbG9nIiwidmVyc2lvbiI6MiwiZGF0YSI6eyJzdGF0ZSI6IkVuYWJsZWQiLCJtb2RlIjp7InR5cGUiOiJQb3NpdGlvbiIsInBhdXNlVHlwZSI6IlJlc3VtZSJ9LCJzdHJhdGVneSI6eyJ0eXBlIjoiQ2FtcGFpZ25TdGF0cyIsIm1vZGUiOiJQbGFjZSIsImJpZE1vZGUiOiJDb3N0UGVyQ2xpY2siLCJjbHVzdGVyU3RhdHMiOnsibWF4QmlkIjo4MDAwLCJtaW5CaWQiOjQwMCwiaW50ZXJ2YWwiOiJUaHJlZURheXMiLCJydWxlcyI6W3sidHlwZSI6IkF2ZXJhZ2VQb3NpdGlvbiIsIm1vZGUiOiJWYWx1ZSIsInZhbHVlIjoyfSx7InR5cGUiOiJDb3N0UGVyQWRkZWRUb0NhcnQiLCJtb2RlIjoiQXZlcmFnZVZhbHVlIn1dfSwicGxhY2VTdHJhdGVneSI6eyJ0eXBlIjoiS2V5d29yZHMiLCJrZXl3b3JkcyI6eyJrZXl3b3JkcyI6W119fSwiaXNDbHVzdGVyIjp0cnVlfX0=';
27
28 // Глобальные переменные для массовой обработки
29 let isBulkProcessing = false;
30 let bulkDesiredPercentage = 30;
31 let bulkPaused = false;
32
33 // Функция для получения сохраненных стратегий
34 async function getSavedStrategies() {
35 let strategies = await GM.getValue('saved_strategies', []);
36
37 // Миграция: добавляем ID к стратегиям, у которых его нет
38 let needsMigration = false;
39 strategies = strategies.map((strategy, index) => {
40 if (!strategy.id) {
41 needsMigration = true;
42 return {
43 ...strategy,
44 id: 'strategy_migrated_' + Date.now() + '_' + index
45 };
46 }
47 return strategy;
48 });
49
50 // Сохраняем обновленные стратегии, если была миграция
51 if (needsMigration) {
52 await GM.setValue('saved_strategies', strategies);
53 console.log('Миграция стратегий выполнена: добавлены ID');
54 }
55
56 // Если нет сохраненных стратегий, добавляем стратегию по умолчанию
57 if (strategies.length === 0) {
58 return [{
59 id: 'default',
60 name: 'Стратегия по умолчанию',
61 data: STRATEGY_CODE
62 }];
63 }
64 return strategies;
65 }
66
67 // Функция для получения сохраненных стратегий для кластеров
68 async function getSavedClusterStrategies() {
69 let strategies = await GM.getValue('saved_cluster_strategies', []);
70
71 // Миграция: добавляем ID к стратегиям, у которых его нет
72 let needsMigration = false;
73 strategies = strategies.map((strategy, index) => {
74 if (!strategy.id) {
75 needsMigration = true;
76 return {
77 ...strategy,
78 id: 'cluster_strategy_migrated_' + Date.now() + '_' + index
79 };
80 }
81 return strategy;
82 });
83
84 // Сохраняем обновленные стратегии, если была миграция
85 if (needsMigration) {
86 await GM.setValue('saved_cluster_strategies', strategies);
87 console.log('Миграция кластерных стратегий выполнена: добавлены ID');
88 }
89
90 // Если нет сохраненных стратегий, добавляем стратегию по умолчанию
91 if (strategies.length === 0) {
92 return [{
93 id: 'cluster_default',
94 name: 'Кластерная стратегия по умолчанию',
95 data: CLUSTER_STRATEGY_CODE
96 }];
97 }
98 return strategies;
99 }
100
101 // Функция для получения текущей стратегии
102 async function getCurrentStrategy() {
103 const currentId = await GM.getValue('current_strategy_id', 'default');
104 const strategies = await getSavedStrategies();
105 const strategy = strategies.find(s => s.id === currentId);
106 return strategy ? strategy.data : STRATEGY_CODE;
107 }
108
109 // Функция для получения текущей кластерной стратегии
110 async function getCurrentClusterStrategy() {
111 const currentId = await GM.getValue('current_cluster_strategy_id', 'cluster_default');
112 const strategies = await getSavedClusterStrategies();
113 const strategy = strategies.find(s => s.id === currentId);
114 return strategy ? strategy.data : CLUSTER_STRATEGY_CODE;
115 }
116
117 // Функция для получения стратегии удержания места
118 async function getHoldPositionStrategy() {
119 const holdPositionId = await GM.getValue('hold_position_strategy_id', null);
120 if (holdPositionId) {
121 const strategies = await getSavedStrategies();
122 const strategy = strategies.find(s => s.id === holdPositionId);
123 if (strategy) {
124 return strategy.data;
125 }
126 }
127 // Если стратегия удержания места не установлена, используем текущую выбранную стратегию
128 const currentId = await GM.getValue('current_strategy_id', 'default');
129 if (currentId !== 'default') {
130 const strategies = await getSavedStrategies();
131 const strategy = strategies.find(s => s.id === currentId);
132 if (strategy) {
133 console.log('⚠️ Стратегия удержания места не установлена, используем текущую стратегию:', strategy.name);
134 return strategy.data;
135 }
136 }
137 // Возвращаем специальную стратегию удержания места по умолчанию
138 return HOLD_POSITION_STRATEGY_CODE;
139 }
140
141 // Функция для получения кластерной стратегии удержания места
142 async function getHoldPositionClusterStrategy() {
143 const holdPositionId = await GM.getValue('hold_position_cluster_strategy_id', null);
144 if (holdPositionId) {
145 const strategies = await getSavedClusterStrategies();
146 const strategy = strategies.find(s => s.id === holdPositionId);
147 if (strategy) {
148 return strategy.data;
149 }
150 }
151 // Возвращаем специальную кластерную стратегию удержания места по умолчанию
152 return HOLD_POSITION_CLUSTER_STRATEGY_CODE;
153 }
154
155 // Функция для создания модального окна управления стратегиями
156 function createStrategyManagementModal() {
157 return new Promise(async (resolve) => {
158 // Создаем оверлей
159 const overlay = document.createElement('div');
160 overlay.style.cssText = `
161 position: fixed;
162 top: 0;
163 left: 0;
164 width: 100%;
165 height: 100%;
166 background: rgba(0, 0, 0, 0.5);
167 display: flex;
168 justify-content: center;
169 align-items: center;
170 z-index: 10001;
171 `;
172
173 // Создаем модальное окно
174 const modal = document.createElement('div');
175 modal.style.cssText = `
176 background: white;
177 border-radius: 12px;
178 padding: 24px;
179 min-width: 500px;
180 max-width: 600px;
181 max-height: 80vh;
182 overflow-y: auto;
183 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
184 `;
185
186 // Заголовок
187 const title = document.createElement('h2');
188 title.textContent = 'Управление стратегиями';
189 title.style.cssText = `
190 margin: 0 0 20px 0;
191 font-size: 24px;
192 font-weight: bold;
193 color: #333;
194 `;
195 modal.appendChild(title);
196
197 // Контейнер для списка стратегий
198 const listContainer = document.createElement('div');
199 listContainer.style.cssText = `
200 margin-bottom: 20px;
201 max-height: 300px;
202 overflow-y: auto;
203 `;
204
205 // Функция для обновления списка
206 const updateList = async () => {
207 const currentStrategies = await getSavedStrategies();
208 const selectedStrategy = await GM.getValue('current_strategy_id', 'default');
209 const holdPositionStrategyId = await GM.getValue('hold_position_strategy_id', null);
210 listContainer.innerHTML = '';
211
212 if (currentStrategies.length === 0) {
213 const emptyMessage = document.createElement('p');
214 emptyMessage.textContent = 'Нет сохраненных стратегий. Используется стратегия по умолчанию.';
215 emptyMessage.style.cssText = `
216 color: #666;
217 font-style: italic;
218 padding: 20px;
219 text-align: center;
220 `;
221 listContainer.appendChild(emptyMessage);
222 } else {
223 currentStrategies.forEach((strategy, index) => {
224 const item = document.createElement('div');
225 const isSelected = strategy.id === selectedStrategy;
226 const isHoldPosition = strategy.id === holdPositionStrategyId;
227 item.style.cssText = `
228 display: flex;
229 justify-content: space-between;
230 align-items: center;
231 padding: 12px;
232 margin-bottom: 8px;
233 background: ${isSelected ? '#e3f2fd' : '#f5f5f5'};
234 border-radius: 8px;
235 border: ${isSelected ? '2px solid #2196f3' : '1px solid #ddd'};
236 `;
237
238 const nameSpan = document.createElement('span');
239 nameSpan.textContent = strategy.name + (isSelected ? ' ✓' : '') + (isHoldPosition ? ' 📍' : '');
240 nameSpan.style.cssText = `
241 font-size: 16px;
242 color: #333;
243 flex: 1;
244 font-weight: ${isSelected ? 'bold' : 'normal'};
245 `;
246
247 const buttonsContainer = document.createElement('div');
248 buttonsContainer.style.cssText = `
249 display: flex;
250 gap: 8px;
251 `;
252
253 // Кнопка выбора
254 if (!isSelected) {
255 const selectBtn = document.createElement('button');
256 selectBtn.textContent = '✓ Выбрать';
257 selectBtn.style.cssText = `
258 background: #2196f3;
259 color: white;
260 border: none;
261 padding: 6px 12px;
262 border-radius: 6px;
263 cursor: pointer;
264 font-size: 14px;
265 `;
266 selectBtn.onmouseover = () => selectBtn.style.background = '#1976d2';
267 selectBtn.onmouseout = () => selectBtn.style.background = '#2196f3';
268 selectBtn.onclick = async () => {
269 await GM.setValue('current_strategy_id', strategy.id);
270 console.log(`Выбрана стратегия: ${strategy.name} (ID: ${strategy.id})`);
271 await updateList();
272 };
273 buttonsContainer.appendChild(selectBtn);
274 }
275
276 // Кнопка "Удержание места"
277 const holdBtn = document.createElement('button');
278 holdBtn.textContent = isHoldPosition ? '📍 Удалить' : '📍 Удержание';
279 holdBtn.style.cssText = `
280 background: ${isHoldPosition ? '#ff9800' : '#4caf50'};
281 color: white;
282 border: none;
283 padding: 6px 12px;
284 border-radius: 6px;
285 cursor: pointer;
286 font-size: 14px;
287 `;
288 holdBtn.onmouseover = () => holdBtn.style.background = isHoldPosition ? '#f57c00' : '#45a049';
289 holdBtn.onmouseout = () => holdBtn.style.background = isHoldPosition ? '#ff9800' : '#4caf50';
290 holdBtn.onclick = async () => {
291 if (isHoldPosition) {
292 await GM.deleteValue('hold_position_strategy_id');
293 console.log('Стратегия удержания места удалена');
294 } else {
295 await GM.setValue('hold_position_strategy_id', strategy.id);
296 console.log(`Установлена стратегия удержания места: ${strategy.name} (ID: ${strategy.id})`);
297 }
298 await updateList();
299 };
300 buttonsContainer.appendChild(holdBtn);
301
302 // Кнопка удаления
303 const deleteBtn = document.createElement('button');
304 deleteBtn.textContent = '🗑️';
305 deleteBtn.style.cssText = `
306 background: #f44336;
307 color: white;
308 border: none;
309 padding: 6px 12px;
310 border-radius: 6px;
311 cursor: pointer;
312 font-size: 14px;
313 `;
314 deleteBtn.onmouseover = () => deleteBtn.style.background = '#d32f2f';
315 deleteBtn.onmouseout = () => deleteBtn.style.background = '#f44336';
316 deleteBtn.onclick = async () => {
317 if (confirm(`Удалить стратегию "${strategy.name}"?`)) {
318 currentStrategies.splice(index, 1);
319 await GM.setValue('saved_strategies', currentStrategies);
320 // Если удаляем выбранную стратегию, возвращаемся к дефолтной
321 if (isSelected) {
322 await GM.setValue('current_strategy_id', 'default');
323 }
324 // Если удаляем стратегию удержания места, очищаем её
325 if (isHoldPosition) {
326 await GM.deleteValue('hold_position_strategy_id');
327 }
328 await updateList();
329 }
330 };
331 buttonsContainer.appendChild(deleteBtn);
332
333 item.appendChild(nameSpan);
334 item.appendChild(buttonsContainer);
335 listContainer.appendChild(item);
336 });
337 }
338 };
339
340 await updateList();
341 modal.appendChild(listContainer);
342
343 // Кнопка добавления стратегии
344 const addButton = document.createElement('button');
345 addButton.textContent = '➕ Добавить новую стратегию';
346 addButton.style.cssText = `
347 width: 100%;
348 background: #4caf50;
349 color: white;
350 border: none;
351 padding: 12px;
352 border-radius: 8px;
353 cursor: pointer;
354 font-size: 16px;
355 font-weight: bold;
356 margin-bottom: 12px;
357 `;
358 addButton.onmouseover = () => addButton.style.background = '#45a049';
359 addButton.onmouseout = () => addButton.style.background = '#4caf50';
360 addButton.onclick = async () => {
361 const name = prompt('Введите название стратегии:');
362 if (!name) return;
363
364 const data = prompt('Вставьте код стратегии (скопируйте из MP Manager):');
365 if (!data) return;
366
367 const currentStrategies = await GM.getValue('saved_strategies', []);
368 const newId = 'strategy_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
369 currentStrategies.push({ id: newId, name, data });
370 await GM.setValue('saved_strategies', currentStrategies);
371 alert(`Стратегия "${name}" сохранена!`);
372 await updateList();
373 };
374 modal.appendChild(addButton);
375
376 // Кнопка закрытия
377 const closeButton = document.createElement('button');
378 closeButton.textContent = 'Закрыть';
379 closeButton.style.cssText = `
380 width: 100%;
381 background: #666;
382 color: white;
383 border: none;
384 padding: 12px;
385 border-radius: 8px;
386 cursor: pointer;
387 font-size: 16px;
388 `;
389 closeButton.onmouseover = () => closeButton.style.background = '#555';
390 closeButton.onmouseout = () => closeButton.style.background = '#666';
391 closeButton.onclick = () => {
392 document.body.removeChild(overlay);
393 resolve();
394 };
395 modal.appendChild(closeButton);
396
397 overlay.appendChild(modal);
398 document.body.appendChild(overlay);
399
400 // Закрытие по клику на оверлей
401 overlay.onclick = (e) => {
402 if (e.target === overlay) {
403 document.body.removeChild(overlay);
404 resolve();
405 }
406 };
407 });
408 }
409
410 // Функция для создания модального окна управления кластерными стратегиями
411 function createClusterStrategyManagementModal() {
412 return new Promise(async (resolve) => {
413 // Создаем оверлей
414 const overlay = document.createElement('div');
415 overlay.style.cssText = `
416 position: fixed;
417 top: 0;
418 left: 0;
419 width: 100%;
420 height: 100%;
421 background: rgba(0, 0, 0, 0.5);
422 display: flex;
423 justify-content: center;
424 align-items: center;
425 z-index: 10001;
426 `;
427
428 // Создаем модальное окно
429 const modal = document.createElement('div');
430 modal.style.cssText = `
431 background: white;
432 border-radius: 12px;
433 padding: 24px;
434 min-width: 500px;
435 max-width: 600px;
436 max-height: 80vh;
437 overflow-y: auto;
438 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
439 `;
440
441 // Заголовок
442 const title = document.createElement('h2');
443 title.textContent = 'Управление стратегиями для кластеров';
444 title.style.cssText = `
445 margin: 0 0 20px 0;
446 font-size: 24px;
447 font-weight: bold;
448 color: #333;
449 `;
450 modal.appendChild(title);
451
452 // Контейнер для списка стратегий
453 const listContainer = document.createElement('div');
454 listContainer.style.cssText = `
455 margin-bottom: 20px;
456 max-height: 300px;
457 overflow-y: auto;
458 `;
459
460 // Функция для обновления списка
461 const updateList = async () => {
462 const currentStrategies = await getSavedClusterStrategies();
463 const selectedStrategy = await GM.getValue('current_cluster_strategy_id', 'cluster_default');
464 const holdPositionStrategyId = await GM.getValue('hold_position_cluster_strategy_id', null);
465 listContainer.innerHTML = '';
466
467 if (currentStrategies.length === 0) {
468 const emptyMessage = document.createElement('p');
469 emptyMessage.textContent = 'Нет сохраненных кластерных стратегий. Используется стратегия по умолчанию.';
470 emptyMessage.style.cssText = `
471 color: #666;
472 font-style: italic;
473 padding: 20px;
474 text-align: center;
475 `;
476 listContainer.appendChild(emptyMessage);
477 } else {
478 currentStrategies.forEach((strategy, index) => {
479 const item = document.createElement('div');
480 const isSelected = strategy.id === selectedStrategy;
481 const isHoldPosition = strategy.id === holdPositionStrategyId;
482 item.style.cssText = `
483 display: flex;
484 justify-content: space-between;
485 align-items: center;
486 padding: 12px;
487 margin-bottom: 8px;
488 background: ${isSelected ? '#e3f2fd' : '#f5f5f5'};
489 border-radius: 8px;
490 border: ${isSelected ? '2px solid #2196f3' : '1px solid #ddd'};
491 `;
492
493 const nameSpan = document.createElement('span');
494 nameSpan.textContent = strategy.name + (isSelected ? ' ✓' : '') + (isHoldPosition ? ' 📍' : '');
495 nameSpan.style.cssText = `
496 font-size: 16px;
497 color: #333;
498 flex: 1;
499 font-weight: ${isSelected ? 'bold' : 'normal'};
500 `;
501
502 const buttonsContainer = document.createElement('div');
503 buttonsContainer.style.cssText = `
504 display: flex;
505 gap: 8px;
506 `;
507
508 // Кнопка выбора
509 if (!isSelected) {
510 const selectBtn = document.createElement('button');
511 selectBtn.textContent = '✓ Выбрать';
512 selectBtn.style.cssText = `
513 background: #2196f3;
514 color: white;
515 border: none;
516 padding: 6px 12px;
517 border-radius: 6px;
518 cursor: pointer;
519 font-size: 14px;
520 `;
521 selectBtn.onmouseover = () => selectBtn.style.background = '#1976d2';
522 selectBtn.onmouseout = () => selectBtn.style.background = '#2196f3';
523 selectBtn.onclick = async () => {
524 await GM.setValue('current_cluster_strategy_id', strategy.id);
525 console.log(`Выбрана кластерная стратегия: ${strategy.name} (ID: ${strategy.id})`);
526 await updateList();
527 };
528 buttonsContainer.appendChild(selectBtn);
529 }
530
531 // Кнопка "Удержание места"
532 const holdBtn = document.createElement('button');
533 holdBtn.textContent = isHoldPosition ? '📍 Удалить' : '📍 Удержание';
534 holdBtn.style.cssText = `
535 background: ${isHoldPosition ? '#ff9800' : '#4caf50'};
536 color: white;
537 border: none;
538 padding: 6px 12px;
539 border-radius: 6px;
540 cursor: pointer;
541 font-size: 14px;
542 `;
543 holdBtn.onmouseover = () => holdBtn.style.background = isHoldPosition ? '#f57c00' : '#45a049';
544 holdBtn.onmouseout = () => holdBtn.style.background = isHoldPosition ? '#ff9800' : '#4caf50';
545 holdBtn.onclick = async () => {
546 if (isHoldPosition) {
547 await GM.deleteValue('hold_position_cluster_strategy_id');
548 console.log('Кластерная стратегия удержания места удалена');
549 } else {
550 await GM.setValue('hold_position_cluster_strategy_id', strategy.id);
551 console.log(`Установлена кластерная стратегия удержания места: ${strategy.name} (ID: ${strategy.id})`);
552 }
553 await updateList();
554 };
555 buttonsContainer.appendChild(holdBtn);
556
557 // Кнопка удаления
558 const deleteBtn = document.createElement('button');
559 deleteBtn.textContent = '🗑️';
560 deleteBtn.style.cssText = `
561 background: #f44336;
562 color: white;
563 border: none;
564 padding: 6px 12px;
565 border-radius: 6px;
566 cursor: pointer;
567 font-size: 14px;
568 `;
569 deleteBtn.onmouseover = () => deleteBtn.style.background = '#d32f2f';
570 deleteBtn.onmouseout = () => deleteBtn.style.background = '#f44336';
571 deleteBtn.onclick = async () => {
572 if (confirm(`Удалить кластерную стратегию "${strategy.name}"?`)) {
573 currentStrategies.splice(index, 1);
574 await GM.setValue('saved_cluster_strategies', currentStrategies);
575 // Если удаляем выбранную стратегию, возвращаемся к дефолтной
576 if (isSelected) {
577 await GM.setValue('current_cluster_strategy_id', 'cluster_default');
578 }
579 // Если удаляем стратегию удержания места, очищаем её
580 if (isHoldPosition) {
581 await GM.deleteValue('hold_position_cluster_strategy_id');
582 }
583 await updateList();
584 }
585 };
586 buttonsContainer.appendChild(deleteBtn);
587
588 item.appendChild(nameSpan);
589 item.appendChild(buttonsContainer);
590 listContainer.appendChild(item);
591 });
592 }
593 };
594
595 await updateList();
596 modal.appendChild(listContainer);
597
598 // Кнопка добавления стратегии
599 const addButton = document.createElement('button');
600 addButton.textContent = '➕ Добавить новую кластерную стратегию';
601 addButton.style.cssText = `
602 width: 100%;
603 background: #4caf50;
604 color: white;
605 border: none;
606 padding: 12px;
607 border-radius: 8px;
608 cursor: pointer;
609 font-size: 16px;
610 font-weight: bold;
611 margin-bottom: 12px;
612 `;
613 addButton.onmouseover = () => addButton.style.background = '#45a049';
614 addButton.onmouseout = () => addButton.style.background = '#4caf50';
615 addButton.onclick = async () => {
616 const name = prompt('Введите название кластерной стратегии:');
617 if (!name) return;
618
619 const data = prompt('Вставьте код кластерной стратегии (скопируйте из MP Manager):');
620 if (!data) return;
621
622 const currentStrategies = await GM.getValue('saved_cluster_strategies', []);
623 const newId = 'cluster_strategy_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
624 currentStrategies.push({ id: newId, name, data });
625 await GM.setValue('saved_cluster_strategies', currentStrategies);
626 alert(`Кластерная стратегия "${name}" сохранена!`);
627 await updateList();
628 };
629 modal.appendChild(addButton);
630
631 // Кнопка закрытия
632 const closeButton = document.createElement('button');
633 closeButton.textContent = 'Закрыть';
634 closeButton.style.cssText = `
635 width: 100%;
636 background: #666;
637 color: white;
638 border: none;
639 padding: 12px;
640 border-radius: 8px;
641 cursor: pointer;
642 font-size: 16px;
643 `;
644 closeButton.onmouseover = () => closeButton.style.background = '#555';
645 closeButton.onmouseout = () => closeButton.style.background = '#666';
646 closeButton.onclick = () => {
647 document.body.removeChild(overlay);
648 resolve();
649 };
650 modal.appendChild(closeButton);
651
652 overlay.appendChild(modal);
653 document.body.appendChild(overlay);
654
655 // Закрытие по клику на оверлей
656 overlay.onclick = (e) => {
657 if (e.target === overlay) {
658 document.body.removeChild(overlay);
659 resolve();
660 }
661 };
662 });
663 }
664
665 // Функция для создания модального окна управления стратегиями удержания места
666 function createHoldPositionStrategyManagementModal() {
667 return new Promise(async (resolve) => {
668 // Создаем оверлей
669 const overlay = document.createElement('div');
670 overlay.style.cssText = `
671 position: fixed;
672 top: 0;
673 left: 0;
674 width: 100%;
675 height: 100%;
676 background: rgba(0, 0, 0, 0.5);
677 display: flex;
678 justify-content: center;
679 align-items: center;
680 z-index: 10001;
681 `;
682
683 // Создаем модальное окно
684 const modal = document.createElement('div');
685 modal.style.cssText = `
686 background: white;
687 border-radius: 12px;
688 padding: 24px;
689 min-width: 500px;
690 max-width: 600px;
691 max-height: 80vh;
692 overflow-y: auto;
693 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
694 `;
695
696 // Заголовок
697 const title = document.createElement('h2');
698 title.textContent = 'Стратегии удержания места';
699 title.style.cssText = `
700 margin: 0 0 20px 0;
701 font-size: 24px;
702 font-weight: bold;
703 color: #333;
704 `;
705 modal.appendChild(title);
706
707 // Описание
708 const description = document.createElement('p');
709 description.textContent = 'Здесь вы можете управлять стратегиями для обычных кампаний и кластеров, которые будут применяться при включенном чекбоксе "Удержание места".';
710 description.style.cssText = `
711 margin: 0 0 20px 0;
712 font-size: 14px;
713 color: #666;
714 `;
715 modal.appendChild(description);
716
717 // Секция для обычных стратегий
718 const normalSection = document.createElement('div');
719 normalSection.style.cssText = `
720 margin-bottom: 30px;
721 padding: 15px;
722 background: #f9f9f9;
723 border-radius: 8px;
724 `;
725
726 const normalTitle = document.createElement('h3');
727 normalTitle.textContent = '📋 Обычные кампании';
728 normalTitle.style.cssText = `
729 margin: 0 0 15px 0;
730 font-size: 18px;
731 font-weight: bold;
732 color: #333;
733 `;
734 normalSection.appendChild(normalTitle);
735
736 const normalListContainer = document.createElement('div');
737 normalListContainer.style.cssText = `
738 margin-bottom: 15px;
739 max-height: 200px;
740 overflow-y: auto;
741 `;
742
743 // Функция для обновления списка обычных стратегий
744 const updateNormalList = async () => {
745 const strategies = await getSavedStrategies();
746 const holdPositionStrategyId = await GM.getValue('hold_position_strategy_id', null);
747 normalListContainer.innerHTML = '';
748
749 if (strategies.length === 0) {
750 const emptyMessage = document.createElement('p');
751 emptyMessage.textContent = 'Нет сохраненных стратегий.';
752 emptyMessage.style.cssText = `
753 color: #666;
754 font-style: italic;
755 padding: 10px;
756 text-align: center;
757 `;
758 normalListContainer.appendChild(emptyMessage);
759 } else {
760 strategies.forEach((strategy) => {
761 const item = document.createElement('div');
762 const isHoldPosition = strategy.id === holdPositionStrategyId;
763 item.style.cssText = `
764 display: flex;
765 justify-content: space-between;
766 align-items: center;
767 padding: 10px;
768 margin-bottom: 8px;
769 background: ${isHoldPosition ? '#e8f5e9' : 'white'};
770 border-radius: 6px;
771 border: ${isHoldPosition ? '2px solid #4caf50' : '1px solid #ddd'};
772 `;
773
774 const nameSpan = document.createElement('span');
775 nameSpan.textContent = strategy.name + (isHoldPosition ? ' ✓' : '');
776 nameSpan.style.cssText = `
777 font-size: 14px;
778 color: #333;
779 flex: 1;
780 font-weight: ${isHoldPosition ? 'bold' : 'normal'};
781 `;
782
783 const selectBtn = document.createElement('button');
784 selectBtn.textContent = isHoldPosition ? '✓ Выбрана' : 'Выбрать';
785 selectBtn.disabled = isHoldPosition;
786 selectBtn.style.cssText = `
787 background: ${isHoldPosition ? '#4caf50' : '#2196f3'};
788 color: white;
789 border: none;
790 padding: 6px 12px;
791 border-radius: 6px;
792 cursor: ${isHoldPosition ? 'default' : 'pointer'};
793 font-size: 13px;
794 opacity: ${isHoldPosition ? '0.7' : '1'};
795 `;
796 if (!isHoldPosition) {
797 selectBtn.onmouseover = () => selectBtn.style.background = '#1976d2';
798 selectBtn.onmouseout = () => selectBtn.style.background = '#2196f3';
799 selectBtn.onclick = async () => {
800 await GM.setValue('hold_position_strategy_id', strategy.id);
801 console.log(`Установлена стратегия удержания места: ${strategy.name}`);
802 await updateNormalList();
803 };
804 }
805
806 item.appendChild(nameSpan);
807 item.appendChild(selectBtn);
808 normalListContainer.appendChild(item);
809 });
810 }
811 };
812
813 await updateNormalList();
814 normalSection.appendChild(normalListContainer);
815
816 const normalResetBtn = document.createElement('button');
817 normalResetBtn.textContent = '🔄 Сбросить на стандартную';
818 normalResetBtn.style.cssText = `
819 width: 100%;
820 background: #ff9800;
821 color: white;
822 border: none;
823 padding: 10px;
824 border-radius: 6px;
825 cursor: pointer;
826 font-size: 14px;
827 font-weight: 600;
828 `;
829 normalResetBtn.onmouseover = () => normalResetBtn.style.background = '#f57c00';
830 normalResetBtn.onmouseout = () => normalResetBtn.style.background = '#ff9800';
831 normalResetBtn.onclick = async () => {
832 await GM.deleteValue('hold_position_strategy_id');
833 console.log('Стратегия удержания места сброшена на стандартную');
834 await updateNormalList();
835 };
836 normalSection.appendChild(normalResetBtn);
837
838 modal.appendChild(normalSection);
839
840 // Секция для кластерных стратегий
841 const clusterSection = document.createElement('div');
842 clusterSection.style.cssText = `
843 margin-bottom: 20px;
844 padding: 15px;
845 background: #f9f9f9;
846 border-radius: 8px;
847 `;
848
849 const clusterTitle = document.createElement('h3');
850 clusterTitle.textContent = '🎯 Кластеры';
851 clusterTitle.style.cssText = `
852 margin: 0 0 15px 0;
853 font-size: 18px;
854 font-weight: bold;
855 color: #333;
856 `;
857 clusterSection.appendChild(clusterTitle);
858
859 const clusterListContainer = document.createElement('div');
860 clusterListContainer.style.cssText = `
861 margin-bottom: 15px;
862 max-height: 200px;
863 overflow-y: auto;
864 `;
865
866 // Функция для обновления списка кластерных стратегий
867 const updateClusterList = async () => {
868 const strategies = await getSavedClusterStrategies();
869 const holdPositionStrategyId = await GM.getValue('hold_position_cluster_strategy_id', null);
870 clusterListContainer.innerHTML = '';
871
872 if (strategies.length === 0) {
873 const emptyMessage = document.createElement('p');
874 emptyMessage.textContent = 'Нет сохраненных кластерных стратегий.';
875 emptyMessage.style.cssText = `
876 color: #666;
877 font-style: italic;
878 padding: 10px;
879 text-align: center;
880 `;
881 clusterListContainer.appendChild(emptyMessage);
882 } else {
883 strategies.forEach((strategy) => {
884 const item = document.createElement('div');
885 const isHoldPosition = strategy.id === holdPositionStrategyId;
886 item.style.cssText = `
887 display: flex;
888 justify-content: space-between;
889 align-items: center;
890 padding: 10px;
891 margin-bottom: 8px;
892 background: ${isHoldPosition ? '#e8f5e9' : 'white'};
893 border-radius: 6px;
894 border: ${isHoldPosition ? '2px solid #4caf50' : '1px solid #ddd'};
895 `;
896
897 const nameSpan = document.createElement('span');
898 nameSpan.textContent = strategy.name + (isHoldPosition ? ' ✓' : '');
899 nameSpan.style.cssText = `
900 font-size: 14px;
901 color: #333;
902 flex: 1;
903 font-weight: ${isHoldPosition ? 'bold' : 'normal'};
904 `;
905
906 const selectBtn = document.createElement('button');
907 selectBtn.textContent = isHoldPosition ? '✓ Выбрана' : 'Выбрать';
908 selectBtn.disabled = isHoldPosition;
909 selectBtn.style.cssText = `
910 background: ${isHoldPosition ? '#4caf50' : '#2196f3'};
911 color: white;
912 border: none;
913 padding: 6px 12px;
914 border-radius: 6px;
915 cursor: ${isHoldPosition ? 'default' : 'pointer'};
916 font-size: 13px;
917 opacity: ${isHoldPosition ? '0.7' : '1'};
918 `;
919 if (!isHoldPosition) {
920 selectBtn.onmouseover = () => selectBtn.style.background = '#1976d2';
921 selectBtn.onmouseout = () => selectBtn.style.background = '#2196f3';
922 selectBtn.onclick = async () => {
923 await GM.setValue('hold_position_cluster_strategy_id', strategy.id);
924 console.log(`Установлена кластерная стратегия удержания места: ${strategy.name}`);
925 await updateClusterList();
926 };
927 }
928
929 item.appendChild(nameSpan);
930 item.appendChild(selectBtn);
931 clusterListContainer.appendChild(item);
932 });
933 }
934 };
935
936 await updateClusterList();
937 clusterSection.appendChild(clusterListContainer);
938
939 const clusterResetBtn = document.createElement('button');
940 clusterResetBtn.textContent = '🔄 Сбросить на стандартную';
941 clusterResetBtn.style.cssText = `
942 width: 100%;
943 background: #ff9800;
944 color: white;
945 border: none;
946 padding: 10px;
947 border-radius: 6px;
948 cursor: pointer;
949 font-size: 14px;
950 font-weight: 600;
951 `;
952 clusterResetBtn.onmouseover = () => clusterResetBtn.style.background = '#f57c00';
953 clusterResetBtn.onmouseout = () => clusterResetBtn.style.background = '#ff9800';
954 clusterResetBtn.onclick = async () => {
955 await GM.deleteValue('hold_position_cluster_strategy_id');
956 console.log('Кластерная стратегия удержания места сброшена на стандартную');
957 await updateClusterList();
958 };
959 clusterSection.appendChild(clusterResetBtn);
960
961 modal.appendChild(clusterSection);
962
963 // Кнопка закрытия
964 const closeButton = document.createElement('button');
965 closeButton.textContent = 'Закрыть';
966 closeButton.style.cssText = `
967 width: 100%;
968 background: #666;
969 color: white;
970 border: none;
971 padding: 12px;
972 border-radius: 8px;
973 cursor: pointer;
974 font-size: 16px;
975 `;
976 closeButton.onmouseover = () => closeButton.style.background = '#555';
977 closeButton.onmouseout = () => closeButton.style.background = '#666';
978 closeButton.onclick = () => {
979 document.body.removeChild(overlay);
980 resolve();
981 };
982 modal.appendChild(closeButton);
983
984 overlay.appendChild(modal);
985 document.body.appendChild(overlay);
986
987 // Закрытие по клику на оверлей
988 overlay.onclick = (e) => {
989 if (e.target === overlay) {
990 document.body.removeChild(overlay);
991 resolve();
992 }
993 };
994 });
995 }
996
997 // Функция для создания UI
998 function createUI() {
999 console.log('Создание UI панели');
1000
1001 // Проверяем, есть ли сохраненный ДРР для массовой обработки
1002 GM.getValue('bulkProcessingDRR', null).then(savedDRR => {
1003 if (savedDRR !== null) {
1004 console.log(`Найден сохраненный ДРР для массовой обработки: ${savedDRR}`);
1005 // Автоматически запускаем стратегию с сохраненным ДРР
1006 setTimeout(async () => {
1007 const input = document.getElementById('desired-percentage');
1008 if (input) {
1009 input.value = savedDRR;
1010
1011 // Восстанавливаем состояние чекбоксов из сохраненных настроек
1012 const savedApplyToClusters = await GM.getValue('bulkApplyToClusters', true);
1013 const savedHoldPosition = await GM.getValue('bulkHoldPosition', true);
1014
1015 const applyToClustersCheckbox = document.getElementById('apply-to-clusters');
1016 const holdPositionCheckbox = document.getElementById('hold-position');
1017
1018 if (applyToClustersCheckbox) {
1019 applyToClustersCheckbox.checked = savedApplyToClusters;
1020 console.log(`Восстановлено состояние "Применить к кластерам": ${savedApplyToClusters}`);
1021 }
1022
1023 if (holdPositionCheckbox) {
1024 holdPositionCheckbox.checked = savedHoldPosition;
1025 console.log(`Восстановлено состояние "Удержание места": ${savedHoldPosition}`);
1026 }
1027
1028 // Запускаем стратегию автоматически
1029 runStrategy();
1030 }
1031 }, 2000);
1032 }
1033 });
1034
1035 const panel = document.createElement('div');
1036 panel.id = 'auto-strategy-panel';
1037 panel.style.cssText = `
1038 position: fixed;
1039 top: 20px;
1040 right: 20px;
1041 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
1042 padding: 15px;
1043 border-radius: 12px;
1044 box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
1045 z-index: 10000;
1046 min-width: 280px;
1047 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1048 color: white;
1049 transition: all 0.3s ease;
1050 cursor: move;
1051 `;
1052
1053 panel.innerHTML = `
1054 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; cursor: pointer;" id="panel-header">
1055 <h3 style="margin: 0; font-size: 16px; font-weight: 600;">🚀 Авто-стратегия</h3>
1056 <span id="toggle-icon" style="font-size: 18px;">▼</span>
1057 </div>
1058
1059 <div id="panel-content" style="display: none;">
1060 <div style="margin-bottom: 15px;">
1061 <label style="display: block; margin-bottom: 8px; font-size: 13px; font-weight: 500;">
1062 Желаемый % рекламных расходов:
1063 </label>
1064 <input
1065 type="number"
1066 id="desired-percentage"
1067 value="30"
1068 min="1"
1069 max="100"
1070 step="0.1"
1071 style="
1072 width: 100%;
1073 padding: 10px;
1074 border: none;
1075 border-radius: 8px;
1076 font-size: 14px;
1077 box-sizing: border-box;
1078 background: rgba(255, 255, 255, 0.95);
1079 color: #333;
1080 cursor: text;
1081 "
1082 />
1083 </div>
1084
1085 <div style="margin-bottom: 15px;">
1086 <label style="display: flex; align-items: center; cursor: pointer; font-size: 13px; font-weight: 500;">
1087 <input
1088 type="checkbox"
1089 id="apply-to-clusters"
1090 checked
1091 style="
1092 margin-right: 8px;
1093 width: 18px;
1094 height: 18px;
1095 cursor: pointer;
1096 "
1097 />
1098 Применить стратегию к кластерам
1099 </label>
1100 </div>
1101
1102 <div style="margin-bottom: 15px;">
1103 <label style="display: flex; align-items: center; cursor: pointer; font-size: 13px; font-weight: 500;">
1104 <input
1105 type="checkbox"
1106 id="hold-position"
1107 checked
1108 style="
1109 margin-right: 8px;
1110 width: 18px;
1111 height: 18px;
1112 cursor: pointer;
1113 "
1114 />
1115 Удержание места
1116 </label>
1117 </div>
1118
1119 <button
1120 id="run-strategy-btn"
1121 style="
1122 width: 100%;
1123 padding: 12px;
1124 background: white;
1125 color: #667eea;
1126 border: none;
1127 border-radius: 8px;
1128 font-size: 14px;
1129 font-weight: 600;
1130 cursor: pointer;
1131 transition: all 0.3s ease;
1132 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
1133 margin-bottom: 10px;
1134 "
1135 onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 6px 16px rgba(0, 0, 0, 0.2)';"
1136 onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='0 4px 12px rgba(0, 0, 0, 0.15)';"
1137 >
1138 Запустить
1139 </button>
1140
1141 <button
1142 id="manage-strategies-btn"
1143 style="
1144 width: 100%;
1145 padding: 10px;
1146 background: rgba(255, 255, 255, 0.2);
1147 color: white;
1148 border: 1px solid rgba(255, 255, 255, 0.3);
1149 border-radius: 8px;
1150 font-size: 13px;
1151 font-weight: 600;
1152 cursor: pointer;
1153 transition: all 0.3s ease;
1154 margin-bottom: 8px;
1155 "
1156 onmouseover="this.style.background='rgba(255, 255, 255, 0.3)';"
1157 onmouseout="this.style.background='rgba(255, 255, 255, 0.2)';"
1158 >
1159 ⚙️ Управление стратегиями
1160 </button>
1161
1162 <button
1163 id="manage-cluster-strategies-btn"
1164 style="
1165 width: 100%;
1166 padding: 10px;
1167 background: rgba(255, 255, 255, 0.2);
1168 color: white;
1169 border: 1px solid rgba(255, 255, 255, 0.3);
1170 border-radius: 8px;
1171 font-size: 13px;
1172 font-weight: 600;
1173 cursor: pointer;
1174 transition: all 0.3s ease;
1175 margin-bottom: 8px;
1176 "
1177 onmouseover="this.style.background='rgba(255, 255, 255, 0.3)';"
1178 onmouseout="this.style.background='rgba(255, 255, 255, 0.2)';"
1179 >
1180 🎯 Стратегии для кластеров
1181 </button>
1182
1183 <button
1184 id="manage-hold-position-strategies-btn"
1185 style="
1186 width: 100%;
1187 padding: 10px;
1188 background: rgba(255, 255, 255, 0.2);
1189 color: white;
1190 border: 1px solid rgba(255, 255, 255, 0.3);
1191 border-radius: 8px;
1192 font-size: 13px;
1193 font-weight: 600;
1194 cursor: pointer;
1195 transition: all 0.3s ease;
1196 "
1197 onmouseover="this.style.background='rgba(255, 255, 255, 0.3)';"
1198 onmouseout="this.style.background='rgba(255, 255, 255, 0.2)';"
1199 >
1200 📍 Стратегии удержания места
1201 </button>
1202
1203 <div id="status-message" style="
1204 margin-top: 15px;
1205 padding: 10px;
1206 border-radius: 8px;
1207 font-size: 12px;
1208 background: rgba(255, 255, 255, 0.2);
1209 display: none;
1210 "></div>
1211 </div>
1212 `;
1213
1214 document.body.appendChild(panel);
1215 console.log('UI панель создана');
1216
1217 // Добавляем возможность перетаскивания
1218 makeDraggable(panel);
1219
1220 // Добавляем обработчик клика на заголовок для разворачивания/сворачивания
1221 document.getElementById('panel-header').addEventListener('click', () => {
1222 const content = document.getElementById('panel-content');
1223 const icon = document.getElementById('toggle-icon');
1224 if (content.style.display === 'none') {
1225 content.style.display = 'block';
1226 icon.textContent = '▲';
1227 } else {
1228 content.style.display = 'none';
1229 icon.textContent = '▼';
1230 }
1231 });
1232
1233 // Добавляем обработчик клика на кнопку
1234 document.getElementById('run-strategy-btn').addEventListener('click', runStrategy);
1235
1236 // Добавляем обработчик для кнопки управления стратегиями
1237 document.getElementById('manage-strategies-btn').addEventListener('click', async () => {
1238 await createStrategyManagementModal();
1239 });
1240
1241 // Добавляем обработчик для кнопки управления кластерными стратегиями
1242 document.getElementById('manage-cluster-strategies-btn').addEventListener('click', async () => {
1243 await createClusterStrategyManagementModal();
1244 });
1245
1246 // Добавляем обработчик для кнопки управления стратегиями удержания места
1247 document.getElementById('manage-hold-position-strategies-btn').addEventListener('click', async () => {
1248 await createHoldPositionStrategyManagementModal();
1249 });
1250 }
1251
1252 // Функция для создания UI панели массового изменения
1253 function createBulkUI() {
1254 console.log('Создание UI панели массового изменения');
1255
1256 const panel = document.createElement('div');
1257 panel.id = 'bulk-strategy-panel';
1258 panel.style.cssText = `
1259 position: fixed;
1260 top: 20px;
1261 right: 20px;
1262 background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
1263 padding: 15px;
1264 border-radius: 12px;
1265 box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
1266 z-index: 10000;
1267 min-width: 300px;
1268 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1269 color: white;
1270 transition: all 0.3s ease;
1271 cursor: move;
1272 `;
1273
1274 panel.innerHTML = `
1275 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; cursor: pointer;" id="bulk-panel-header">
1276 <h3 style="margin: 0; font-size: 16px; font-weight: 600;">📦 Массовое изменение</h3>
1277 <span id="bulk-toggle-icon" style="font-size: 18px;">▼</span>
1278 </div>
1279
1280 <div id="bulk-panel-content" style="display: none;">
1281 <div style="margin-bottom: 15px;">
1282 <label style="display: block; margin-bottom: 8px; font-size: 13px; font-weight: 500;">
1283 Желаемый % рекламных расходов (ДРР):
1284 </label>
1285 <input
1286 type="number"
1287 id="bulk-desired-percentage"
1288 value="30"
1289 min="1"
1290 max="100"
1291 step="0.1"
1292 style="
1293 width: 100%;
1294 padding: 10px;
1295 border: none;
1296 border-radius: 8px;
1297 font-size: 14px;
1298 box-sizing: border-box;
1299 background: rgba(255, 255, 255, 0.95);
1300 color: #333;
1301 cursor: text;
1302 "
1303 />
1304 </div>
1305
1306 <div id="bulk-campaigns-info" style="
1307 margin-bottom: 15px;
1308 padding: 10px;
1309 border-radius: 8px;
1310 font-size: 12px;
1311 background: rgba(255, 255, 255, 0.2);
1312 ">
1313 Найдено кампаний: <span id="campaigns-count">0</span>
1314 </div>
1315
1316 <button
1317 id="run-bulk-strategy-btn"
1318 style="
1319 width: 100%;
1320 padding: 12px;
1321 background: white;
1322 color: #f5576c;
1323 border: none;
1324 border-radius: 8px;
1325 font-size: 14px;
1326 font-weight: 600;
1327 cursor: pointer;
1328 transition: all 0.3s ease;
1329 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
1330 margin-bottom: 10px;
1331 "
1332 onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 6px 16px rgba(0, 0, 0, 0.2)';"
1333 onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='0 4px 12px rgba(0, 0, 0, 0.15)';"
1334 >
1335 Запустить
1336 </button>
1337
1338 <button
1339 id="bulk-manage-strategies-btn"
1340 style="
1341 width: 100%;
1342 padding: 10px;
1343 background: rgba(255, 255, 255, 0.2);
1344 color: white;
1345 border: 1px solid rgba(255, 255, 255, 0.3);
1346 border-radius: 8px;
1347 font-size: 13px;
1348 font-weight: 600;
1349 cursor: pointer;
1350 transition: all 0.3s ease;
1351 margin-bottom: 10px;
1352 "
1353 onmouseover="this.style.background='rgba(255, 255, 255, 0.3)';"
1354 onmouseout="this.style.background='rgba(255, 255, 255, 0.2)';"
1355 >
1356 ⚙️ Управление стратегиями
1357 </button>
1358
1359 <button
1360 id="bulk-manage-cluster-strategies-btn"
1361 style="
1362 width: 100%;
1363 padding: 10px;
1364 background: rgba(255, 255, 255, 0.2);
1365 color: white;
1366 border: 1px solid rgba(255, 255, 255, 0.3);
1367 border-radius: 8px;
1368 font-size: 13px;
1369 font-weight: 600;
1370 cursor: pointer;
1371 transition: all 0.3s ease;
1372 margin-bottom: 10px;
1373 "
1374 onmouseover="this.style.background='rgba(255, 255, 255, 0.3)';"
1375 onmouseout="this.style.background='rgba(255, 255, 255, 0.2)';"
1376 >
1377 🎯 Стратегии для кластеров
1378 </button>
1379
1380 <div style="margin-bottom: 15px;">
1381 <label style="display: flex; align-items: center; cursor: pointer; font-size: 13px; font-weight: 500;">
1382 <input
1383 type="checkbox"
1384 id="bulk-apply-to-clusters"
1385 checked
1386 style="
1387 margin-right: 8px;
1388 width: 18px;
1389 height: 18px;
1390 cursor: pointer;
1391 "
1392 />
1393 Применить стратегию к кластерам
1394 </label>
1395 </div>
1396
1397 <div style="margin-bottom: 15px;">
1398 <label style="display: flex; align-items: center; cursor: pointer; font-size: 13px; font-weight: 500;">
1399 <input
1400 type="checkbox"
1401 id="bulk-hold-position"
1402 checked
1403 style="
1404 margin-right: 8px;
1405 width: 18px;
1406 height: 18px;
1407 cursor: pointer;
1408 "
1409 />
1410 Удержание места
1411 </label>
1412 </div>
1413
1414 <div id="bulk-control-buttons" style="
1415 margin-top: 10px;
1416 display: none;
1417 gap: 10px;
1418 ">
1419 <button
1420 id="pause-bulk-btn"
1421 style="
1422 flex: 1;
1423 padding: 10px;
1424 background: rgba(255, 255, 255, 0.9);
1425 color: #f5576c;
1426 border: none;
1427 border-radius: 8px;
1428 font-size: 13px;
1429 font-weight: 600;
1430 cursor: pointer;
1431 transition: all 0.3s ease;
1432 "
1433 >
1434 ⏏️ Пауза
1435 </button>
1436 <button
1437 id="stop-bulk-btn"
1438 style="
1439 flex: 1;
1440 padding: 10px;
1441 background: rgba(255, 59, 48, 0.9);
1442 color: white;
1443 border: none;
1444 border-radius: 8px;
1445 font-size: 13px;
1446 font-weight: 600;
1447 cursor: pointer;
1448 transition: all 0.3s ease;
1449 "
1450 >
1451 ⏹️ Стоп
1452 </button>
1453 </div>
1454
1455 <div id="bulk-status-message" style="
1456 margin-top: 15px;
1457 padding: 10px;
1458 border-radius: 8px;
1459 font-size: 12px;
1460 background: rgba(255, 255, 255, 0.2);
1461 display: none;
1462 "></div>
1463
1464 <div id="bulk-progress" style="
1465 margin-top: 15px;
1466 display: none;
1467 ">
1468 <div style="margin-bottom: 5px; font-size: 12px;">
1469 Прогресс: <span id="bulk-progress-text">0/0</span>
1470 </div>
1471 <div style="
1472 width: 100%;
1473 height: 8px;
1474 background: rgba(255, 255, 255, 0.3);
1475 border-radius: 4px;
1476 overflow: hidden;
1477 ">
1478 <div id="bulk-progress-bar" style="
1479 width: 0%;
1480 height: 100%;
1481 background: white;
1482 transition: width 0.3s ease;
1483 "></div>
1484 </div>
1485 </div>
1486 </div>
1487 `;
1488
1489 document.body.appendChild(panel);
1490 console.log('UI панель массового изменения создана');
1491
1492 // Добавляем возможность перетаскивания
1493 makeDraggable(panel);
1494
1495 // Подсчитываем количество кампаний
1496 updateCampaignsCount();
1497
1498 // Добавляем обработчик клика на заголовок для разворачивания/сворачивания
1499 document.getElementById('bulk-panel-header').addEventListener('click', () => {
1500 const content = document.getElementById('bulk-panel-content');
1501 const icon = document.getElementById('bulk-toggle-icon');
1502 if (content.style.display === 'none') {
1503 content.style.display = 'block';
1504 icon.textContent = '▲';
1505 } else {
1506 content.style.display = 'none';
1507 icon.textContent = '▼';
1508 }
1509 });
1510
1511 // Добавляем обработчик клика на кнопку
1512 document.getElementById('run-bulk-strategy-btn').addEventListener('click', runBulkStrategy);
1513
1514 // Добавляем обработчик для кнопки управления стратегиями
1515 document.getElementById('bulk-manage-strategies-btn').addEventListener('click', async () => {
1516 await createStrategyManagementModal();
1517 });
1518
1519 // Добавляем обработчик для кнопки управления кластерными стратегиями
1520 document.getElementById('bulk-manage-cluster-strategies-btn').addEventListener('click', async () => {
1521 await createClusterStrategyManagementModal();
1522 });
1523
1524 // Добавляем обработчики для кнопок управления
1525 document.getElementById('pause-bulk-btn').addEventListener('click', pauseBulkProcessing);
1526 document.getElementById('stop-bulk-btn').addEventListener('click', stopBulkProcessing);
1527 }
1528
1529 // Функция для создания перетаскиваемого элемента
1530 function makeDraggable(element) {
1531 let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
1532 let isDragging = false;
1533
1534 element.onmousedown = dragMouseDown;
1535
1536 function dragMouseDown(e) {
1537 // Не перетаскиваем, если кликнули на input, button или другие интерактивные элементы
1538 if (e.target.tagName === 'INPUT' || e.target.tagName === 'BUTTON' || e.target.tagName === 'TEXTAREA') {
1539 return;
1540 }
1541
1542 e.preventDefault();
1543 isDragging = true;
1544 pos3 = e.clientX;
1545 pos4 = e.clientY;
1546 document.onmouseup = closeDragElement;
1547 document.onmousemove = elementDrag;
1548 }
1549
1550 function elementDrag(e) {
1551 if (!isDragging) return;
1552 pos1 = pos3 - e.clientX;
1553 pos2 = pos4 - e.clientY;
1554 pos3 = e.clientX;
1555 pos4 = e.clientY;
1556 element.style.top = (element.offsetTop - pos2) + 'px';
1557 element.style.left = (element.offsetLeft - pos1) + 'px';
1558 element.style.right = 'auto';
1559 element.style.bottom = 'auto';
1560 }
1561
1562 function closeDragElement() {
1563 isDragging = false;
1564 document.onmouseup = null;
1565 document.onmousemove = null;
1566 }
1567 }
1568
1569 // Функция для обновления количества кампаний
1570 function updateCampaignsCount() {
1571 const campaignLinks = document.querySelectorAll('a[href*="/campaigns/auto-campaigns/"][href*="/campaign"]');
1572 const count = campaignLinks.length;
1573 const countElement = document.getElementById('campaigns-count');
1574 if (countElement) {
1575 countElement.textContent = count;
1576 }
1577 console.log(`Найдено кампаний: ${count}`);
1578 }
1579
1580 // Функция для показа статуса массовой обработки
1581 function showBulkStatus(message, isError = false) {
1582 const statusDiv = document.getElementById('bulk-status-message');
1583 if (statusDiv) {
1584 statusDiv.textContent = message;
1585 statusDiv.style.display = 'block';
1586 statusDiv.style.background = isError ? 'rgba(255, 59, 48, 0.9)' : 'rgba(52, 199, 89, 0.9)';
1587 console.log(`Статус массовой обработки: ${message}`);
1588 }
1589 }
1590
1591 // Функция для обновления прогресса
1592 function updateBulkProgress(current, total) {
1593 const progressDiv = document.getElementById('bulk-progress');
1594 const progressText = document.getElementById('bulk-progress-text');
1595 const progressBar = document.getElementById('bulk-progress-bar');
1596
1597 if (progressDiv && progressText && progressBar) {
1598 progressDiv.style.display = 'block';
1599 progressText.textContent = `${current}/${total}`;
1600 const percentage = (current / total) * 100;
1601 progressBar.style.width = `${percentage}%`;
1602 }
1603 }
1604
1605 // Функция для прокрутки страницы вниз для загрузки всех кампаний
1606 async function scrollToLoadAllCampaigns() {
1607 console.log('Начинаем загрузку всех кампаний через прокрутку...');
1608
1609 // Находим контейнер с прокруткой
1610 const tableContainer = document.querySelector('.container.MuiBox-root.css-9hf803');
1611
1612 if (!tableContainer) {
1613 console.error('Контейнер таблицы не найден');
1614 return [];
1615 }
1616
1617 console.log('Контейнер найден, начинаем прокрутку...');
1618 console.log(`Высота контейнера: ${tableContainer.scrollHeight}px`);
1619
1620 // Собираем уникальные ссылки во время прокрутки
1621 const uniqueLinks = new Set();
1622
1623 // Прокручиваем контейнер постепенно, чтобы загрузить все кампании
1624 let previousLinksCount = 0;
1625 let stableCount = 0;
1626 const maxAttempts = 200; // Максимум попыток
1627 let attempts = 0;
1628 const scrollStep = 500; // Прокручиваем по 500px за раз
1629
1630 while (attempts < maxAttempts) {
1631 // Собираем ссылки на текущем шаге
1632 const currentLinks = document.querySelectorAll('a[href*="/campaigns/auto-campaigns/"][href*="/campaign"]');
1633 currentLinks.forEach(link => {
1634 uniqueLinks.add(link.href);
1635 });
1636
1637 const currentCount = uniqueLinks.size;
1638 console.log(`Загружено кампаний: ${currentCount}, прокрутка: ${tableContainer.scrollTop}/${tableContainer.scrollHeight}`);
1639
1640 // Прокручиваем контейнер постепенно
1641 tableContainer.scrollTop += scrollStep;
1642
1643 // Ждем загрузки новых элементов
1644 await wait(500);
1645
1646 // Если количество не изменилось
1647 if (currentCount === previousLinksCount) {
1648 stableCount++;
1649 // Если количество стабильно 5 раз подряд - значит все загружено
1650 if (stableCount >= 5) {
1651 console.log('Все кампании загружены');
1652 break;
1653 }
1654 } else {
1655 stableCount = 0;
1656 previousLinksCount = currentCount;
1657 }
1658
1659 // Если достигли конца контейнера
1660 if (tableContainer.scrollTop + tableContainer.clientHeight >= tableContainer.scrollHeight - 10) {
1661 console.log('Достигнут конец контейнера');
1662 // Ждем еще немного для загрузки последних элементов
1663 await wait(1000);
1664
1665 // Собираем последние ссылки
1666 const finalLinks = document.querySelectorAll('a[href*="/campaigns/auto-campaigns/"][href*="/campaign"]');
1667 finalLinks.forEach(link => {
1668 uniqueLinks.add(link.href);
1669 });
1670
1671 // Проверяем еще раз количество
1672 if (uniqueLinks.size === previousLinksCount) {
1673 break;
1674 }
1675 previousLinksCount = uniqueLinks.size;
1676 }
1677
1678 attempts++;
1679 }
1680
1681 // Преобразуем Set в массив URL (НЕ прокручиваем обратно!)
1682 const links = Array.from(uniqueLinks);
1683
1684 console.log(`Найдено кампаний: ${links.length}`);
1685 console.log(`Всего попыток прокрутки: ${attempts}`);
1686 return links;
1687 }
1688
1689 // Функция для массовой обработки кампаний
1690 async function runBulkStrategy() {
1691 if (isBulkProcessing) {
1692 showBulkStatus('Обработка уже выполняется', true);
1693 return;
1694 }
1695
1696 try {
1697 isBulkProcessing = true;
1698 console.log('Начало массовой обработки кампаний');
1699
1700 bulkDesiredPercentage = parseFloat(document.getElementById('bulk-desired-percentage').value);
1701 if (!bulkDesiredPercentage || bulkDesiredPercentage <= 0) {
1702 showBulkStatus('Ошибка: введите корректный процент', true);
1703 isBulkProcessing = false;
1704 return;
1705 }
1706 console.log(`Желаемый процент: ${bulkDesiredPercentage}%`);
1707
1708 // Сохраняем ДРР для автоматической обработки
1709 await GM.setValue('bulkProcessingDRR', bulkDesiredPercentage);
1710 console.log(`ДРР ${bulkDesiredPercentage} сохранен для массовой обработки`);
1711
1712 // Сохраняем состояние чекбоксов
1713 const applyToClustersCheckbox = document.getElementById('bulk-apply-to-clusters');
1714 const holdPositionCheckbox = document.getElementById('bulk-hold-position');
1715
1716 if (applyToClustersCheckbox) {
1717 await GM.setValue('bulkApplyToClusters', applyToClustersCheckbox.checked);
1718 console.log(`Сохранено состояние "Применить к кластерам": ${applyToClustersCheckbox.checked}`);
1719 }
1720
1721 if (holdPositionCheckbox) {
1722 await GM.setValue('bulkHoldPosition', holdPositionCheckbox.checked);
1723 console.log(`Сохранено состояние "Удержание места": ${holdPositionCheckbox.checked}`);
1724 }
1725
1726 // Прокручиваем страницу для загрузки всех кампаний
1727 const campaignLinks = await scrollToLoadAllCampaigns();
1728
1729 if (campaignLinks.length === 0) {
1730 showBulkStatus('Ошибка: кампании не найдены', true);
1731 isBulkProcessing = false;
1732 await GM.deleteValue('bulkProcessingDRR');
1733 return;
1734 }
1735
1736 console.log(`Найдено кампаний для обработки: ${campaignLinks.length}`);
1737 showBulkStatus(`Начинаем обработку ${campaignLinks.length} кампаний...`);
1738 updateBulkProgress(0, campaignLinks.length);
1739
1740 // Сохраняем список кампаний (массив URL), текущий индекс и общее количество
1741 await GM.setValue('bulkCampaigns', JSON.stringify(campaignLinks));
1742 await GM.setValue('bulkCurrentIndex', 0);
1743 await GM.setValue('bulkTotalCampaigns', campaignLinks.length);
1744
1745 // Показываем кнопки управления
1746 const controlButtons = document.getElementById('bulk-control-buttons');
1747 if (controlButtons) {
1748 controlButtons.style.display = 'flex';
1749 }
1750
1751 // Открываем первую кампанию в новой вкладке через window.open
1752 if (campaignLinks.length > 0) {
1753 console.log(`Открытие кампании 1/${campaignLinks.length}: ${campaignLinks[0]}`);
1754 window.open(campaignLinks[0], '_blank');
1755 }
1756
1757 } catch (error) {
1758 console.error('Ошибка при массовой обработке:', error);
1759 showBulkStatus(`Ошибка: ${error.message}`, true);
1760 isBulkProcessing = false;
1761 await GM.deleteValue('bulkProcessingDRR');
1762 }
1763 }
1764
1765 // Функция для паузы массовой обработки
1766 async function pauseBulkProcessing() {
1767 bulkPaused = !bulkPaused;
1768 const pauseBtn = document.getElementById('pause-bulk-btn');
1769
1770 if (bulkPaused) {
1771 await GM.setValue('bulkPaused', true);
1772 pauseBtn.textContent = '▶️ Продолжить';
1773 showBulkStatus('⏸️ Обработка приостановлена');
1774 console.log('Массовая обработка приостановлена');
1775 } else {
1776 await GM.deleteValue('bulkPaused');
1777 pauseBtn.textContent = '⏸️ Пауза';
1778 showBulkStatus('▶️ Обработка возобновлена');
1779 console.log('Массовая обработка возобновлена');
1780
1781 // Продолжаем обработку - открываем следующую кампанию
1782 const campaignsJson = await GM.getValue('bulkCampaigns', null);
1783 const currentIndex = await GM.getValue('bulkCurrentIndex', 0);
1784 const totalCampaigns = await GM.getValue('bulkTotalCampaigns', 0);
1785
1786 if (campaignsJson) {
1787 const campaigns = JSON.parse(campaignsJson);
1788 const nextIndex = currentIndex + 1;
1789
1790 console.log(`Обработано кампаний: ${nextIndex} из ${totalCampaigns}`);
1791
1792 if (nextIndex < campaigns.length) {
1793 // Сохраняем новый индекс
1794 await GM.setValue('bulkCurrentIndex', nextIndex);
1795
1796 // Открываем следующую кампанию в новой вкладке через window.open
1797 console.log(`Открываем кампанию ${nextIndex + 1}: ${campaigns[nextIndex]}`);
1798 const newTab = window.open(campaigns[nextIndex], '_blank');
1799
1800 if (newTab) {
1801 // Закрываем текущую вкладку
1802 await wait(1000);
1803 window.close();
1804 } else {
1805 // Если блокировщик всплывающих окон, используем редирект
1806 console.log('Блокировщик всплывающих окон, используем редирект');
1807 window.location.href = campaigns[nextIndex];
1808 }
1809 }
1810 }
1811 }
1812 }
1813
1814 // Функция для остановки массовой обработки
1815 async function stopBulkProcessing() {
1816 await GM.deleteValue('bulkProcessingDRR');
1817 await GM.deleteValue('bulkCampaigns');
1818 await GM.deleteValue('bulkCurrentIndex');
1819 await GM.deleteValue('bulkTotalCampaigns');
1820
1821 showBulkStatus('⏹️ Обработка остановлена', true);
1822 console.log('Массовая обработка остановлена');
1823
1824 // Скрываем кнопки управления
1825 const controlButtons = document.getElementById('bulk-control-buttons');
1826 if (controlButtons) {
1827 controlButtons.style.display = 'none';
1828 }
1829
1830 isBulkProcessing = false;
1831 bulkPaused = false;
1832 }
1833
1834 // Функция для показа статуса
1835 function showStatus(message, isError = false) {
1836 const statusDiv = document.getElementById('status-message');
1837 if (statusDiv) {
1838 statusDiv.textContent = message;
1839 statusDiv.style.display = 'block';
1840 statusDiv.style.background = isError ? 'rgba(255, 59, 48, 0.9)' : 'rgba(52, 199, 89, 0.9)';
1841 console.log(`Статус: ${message}`);
1842 }
1843 }
1844
1845 // Функция для ожидания
1846 function wait(ms) {
1847 return new Promise(resolve => setTimeout(resolve, ms));
1848 }
1849
1850 // Функция для парсинга числа из строки
1851 function parseNumber(str) {
1852 if (!str) return 0;
1853 // Убираем все символы кроме цифр, точек и запятых
1854 const cleaned = str.replace(/[^\d.,]/g, '').replace(/\s/g, '');
1855 // Заменяем запятую на точку
1856 const normalized = cleaned.replace(',', '.');
1857 return parseFloat(normalized) || 0;
1858 }
1859
1860 // Функция для парсинга процента
1861 function parsePercentage(str) {
1862 if (!str) return 0;
1863 const cleaned = str.replace('%', '').replace(',', '.').trim();
1864 return parseFloat(cleaned) || 0;
1865 }
1866
1867 // Основная функция запуска стратегии
1868 async function runStrategy() {
1869 try {
1870 console.log('Начало выполнения стратегии');
1871 showStatus('Запуск процесса...');
1872
1873 const desiredPercentage = parseFloat(document.getElementById('desired-percentage').value);
1874 if (!desiredPercentage || desiredPercentage <= 0) {
1875 showStatus('Ошибка: введите корректный процент', true);
1876 await handleStrategyError();
1877 return;
1878 }
1879 console.log(`Желаемый процент: ${desiredPercentage}%`);
1880
1881 // Проверяем чекбоксы из обеих панелей
1882 const holdPositionCheckbox = document.getElementById('hold-position');
1883 const bulkHoldPositionCheckbox = document.getElementById('bulk-hold-position');
1884 const shouldApplyHoldPosition = holdPositionCheckbox ? holdPositionCheckbox.checked :
1885 (bulkHoldPositionCheckbox ? bulkHoldPositionCheckbox.checked : false);
1886
1887 // Шаг 1: Кликаем на статистику
1888 showStatus('Открытие статистики...');
1889 const statsButton = document.querySelector('.css-amj7dw');
1890 if (!statsButton) {
1891 showStatus('Ошибка: кнопка статистики не найдена', true);
1892 await handleStrategyError();
1893 return;
1894 }
1895 statsButton.click();
1896 console.log('Клик на статистику выполнен');
1897 await wait(2000);
1898
1899 // Шаг 1.5: Выбираем режим "Товары"
1900 showStatus('Выбор режима "Товары"...');
1901 const modeLabels = Array.from(document.querySelectorAll('label'));
1902 let modeInput = null;
1903
1904 for (const label of modeLabels) {
1905 if (label.textContent.trim() === 'Режим') {
1906 const inputId = label.getAttribute('for');
1907 if (inputId) {
1908 modeInput = document.getElementById(inputId);
1909 console.log(`Найдено поле Режим с id: ${inputId}`);
1910 break;
1911 }
1912 }
1913 }
1914
1915 if (modeInput) {
1916 modeInput.value = 'Товары';
1917 const inputEvent = new Event('input', { bubbles: true });
1918 const changeEvent = new Event('change', { bubbles: true });
1919 modeInput.dispatchEvent(inputEvent);
1920 modeInput.dispatchEvent(changeEvent);
1921
1922 console.log('Установлено значение: Товары');
1923 await wait(1000);
1924
1925 const options = document.querySelectorAll('[role="option"]');
1926 console.log(`Найдено опций: ${options.length}`);
1927
1928 if (options.length > 0) {
1929 const tovarOption = Array.from(options).find(opt => opt.textContent.includes('Товары'));
1930 if (tovarOption) {
1931 tovarOption.click();
1932 console.log('Выбран режим "Товары"');
1933 await wait(1000);
1934 }
1935 }
1936 }
1937
1938 // Шаг 2: Извлекаем данные из статистики
1939 showStatus('Извлечение данных...');
1940 await wait(500);
1941
1942 const stats = Array.from(document.querySelectorAll('.MuiTypography-caption.css-1et52kr'));
1943
1944 let sumOrders = 0;
1945 let ordersCount = 0;
1946 let cartToOrderPercent = 0;
1947
1948 stats.forEach(el => {
1949 const text = el.textContent.trim();
1950 const valueElement = el.closest('.MuiBox-root')?.querySelector('.MuiTypography-h3 .MuiTypography-body1');
1951 const value = valueElement ? valueElement.textContent.trim() : '';
1952
1953 console.log(`Найден показатель: ${text} = ${value}`);
1954
1955 if (text === 'Сумма заказов') {
1956 sumOrders = parseNumber(value);
1957 console.log(`Сумма заказов: ${sumOrders}`);
1958 } else if (text === 'Заказов') {
1959 ordersCount = parseNumber(value);
1960 console.log(`Заказов: ${ordersCount}`);
1961 } else if (text === 'Корзина → Заказ') {
1962 cartToOrderPercent = parsePercentage(value);
1963 console.log(`Корзина → Заказ: ${cartToOrderPercent}%`);
1964 }
1965 });
1966
1967 if (sumOrders === 0 || ordersCount === 0 || cartToOrderPercent === 0) {
1968 showStatus('Ошибка: не удалось получить данные статистики', true);
1969 console.error('Недостаточно данных:', { sumOrders, ordersCount, cartToOrderPercent });
1970 await handleStrategyError();
1971 return;
1972 }
1973
1974 // Шаг 3: Вычисляем стоимость корзины
1975 const cartCost = (sumOrders / ordersCount) * (desiredPercentage / 100) * (cartToOrderPercent / 100);
1976 const cartCostRounded = Math.round(cartCost * 100) / 100;
1977 console.log(`Расчет: (${sumOrders} / ${ordersCount}) * (${desiredPercentage} / 100) * (${cartToOrderPercent} / 100) = ${cartCostRounded}`);
1978 showStatus(`Рассчитано: ${cartCostRounded} ₽`);
1979
1980 // Закрываем статистику
1981 statsButton.click();
1982 await wait(500);
1983
1984 // Шаг 4: Кликаем на "Вставить стратегию"
1985 showStatus('Вставка стратегии...');
1986 const insertButtons = document.querySelectorAll('.css-5kbhos');
1987 let insertButton = null;
1988
1989 for (const btn of insertButtons) {
1990 if (btn.textContent.includes('Вставить стратегию')) {
1991 insertButton = btn;
1992 break;
1993 }
1994 }
1995
1996 if (!insertButton && insertButtons.length >= 2) {
1997 insertButton = insertButtons[1];
1998 } else if (!insertButton && insertButtons.length === 1) {
1999 insertButton = insertButtons[0];
2000 }
2001
2002 if (!insertButton) {
2003 showStatus('Ошибка: кнопка "Вставить стратегию" не найдена', true);
2004 await handleStrategyError();
2005 return;
2006 }
2007
2008 // Получаем текущую выбранную стратегию
2009 let currentStrategyCode;
2010 if (shouldApplyHoldPosition) {
2011 currentStrategyCode = await getHoldPositionStrategy();
2012 console.log('Используем стратегию удержания места, длина:', currentStrategyCode.length);
2013 } else {
2014 currentStrategyCode = await getCurrentStrategy();
2015 console.log('Используем обычную стратегию, длина:', currentStrategyCode.length);
2016 }
2017
2018 // Копируем код стратегии в буфер обмена
2019 console.log('Копируем стратегию в буфер обмена...');
2020
2021 try {
2022 await navigator.clipboard.writeText(currentStrategyCode);
2023 console.log('✓ Стратегия скопирована через navigator.clipboard');
2024 } catch {
2025 console.log('navigator.clipboard не сработал, пробуем GM.setClipboard...');
2026 try {
2027 await GM.setClipboard(currentStrategyCode);
2028 console.log('✓ Стратегия скопирована через GM.setClipboard');
2029 } catch (e2) {
2030 console.error('Ошибка копирования через GM.setClipboard:', e2.message);
2031 }
2032 }
2033
2034 await wait(500);
2035
2036 insertButton.click();
2037 console.log('Клик на "Вставить стратегию" выполнен');
2038 await wait(500);
2039
2040 console.log('✓ Стратегия вставлена из буфера обмена');
2041 await wait(500);
2042
2043 // Шаг 5: Находим поле "Желаемое значение" и вставляем результат
2044 showStatus('Заполнение поля...');
2045
2046 const inputLabels = Array.from(document.querySelectorAll('.MuiInputLabel-root'));
2047 let targetInput = null;
2048
2049 for (const label of inputLabels) {
2050 const labelText = label.textContent;
2051 if (labelText.includes('Желаемое значение За корзину')) {
2052 const inputId = label.getAttribute('for');
2053 if (inputId) {
2054 targetInput = document.getElementById(inputId);
2055 console.log(`Найдено поле: ${labelText}, id: ${inputId}`);
2056 break;
2057 }
2058 }
2059 }
2060
2061 if (!targetInput) {
2062 const inputs = document.querySelectorAll('input[type="number"]');
2063 for (const input of inputs) {
2064 const name = input.getAttribute('name') || '';
2065 if (name.includes('rules') && name.includes('value')) {
2066 targetInput = input;
2067 console.log(`Найдено поле по имени: ${name}`);
2068 break;
2069 }
2070 }
2071 }
2072
2073 if (!targetInput) {
2074 showStatus('Ошибка: поле для ввода не найдено', true);
2075 console.error('Не удалось найти поле для ввода значения');
2076 await handleStrategyError();
2077 return;
2078 }
2079
2080 targetInput.focus();
2081 targetInput.value = cartCostRounded.toString();
2082
2083 const inputEvent = new Event('input', { bubbles: true });
2084 const changeEvent = new Event('change', { bubbles: true });
2085 targetInput.dispatchEvent(inputEvent);
2086 targetInput.dispatchEvent(changeEvent);
2087
2088 console.log(`Значение ${cartCostRounded} установлено в поле ${targetInput.id}`);
2089 await wait(500);
2090
2091 // Шаг 6: Нажимаем "Сохранить"
2092 showStatus('Сохранение...');
2093 const saveButtons = document.querySelectorAll('button');
2094 let saveButton = null;
2095
2096 for (const btn of saveButtons) {
2097 if (btn.textContent.trim() === 'Сохранить') {
2098 saveButton = btn;
2099 break;
2100 }
2101 }
2102
2103 if (!saveButton) {
2104 showStatus('Ошибка: кнопка "Сохранить" не найдена', true);
2105 await handleStrategyError();
2106 return;
2107 }
2108
2109 saveButton.click();
2110 console.log('Клик на "Сохранить" выполнен');
2111 await wait(500);
2112
2113 showStatus(`✅ Готово! Стоимость корзины: ${cartCostRounded} ₽`);
2114 console.log('Стратегия успешно установлена');
2115
2116 // Шаг 7: Работа с кластерами
2117 // Проверяем чекбоксы из обеих панелей
2118 const applyToClusters = document.getElementById('apply-to-clusters');
2119 const bulkApplyToClusters = document.getElementById('bulk-apply-to-clusters');
2120 const shouldApplyToClusters = applyToClusters ? applyToClusters.checked :
2121 (bulkApplyToClusters ? bulkApplyToClusters.checked : true);
2122
2123 if (!shouldApplyToClusters) {
2124 console.log('Применение стратегии к кластерам отключено');
2125 showStatus('✅ Готово! Кластеры пропущены');
2126
2127 // Проверяем, идет ли массовая обработка
2128 const savedDRR = await GM.getValue('bulkProcessingDRR', null);
2129 if (savedDRR !== null) {
2130 console.log('Массовая обработка: переход к следующей кампании');
2131 await wait(2000);
2132
2133 const campaignsJson = await GM.getValue('bulkCampaigns', null);
2134 const currentIndex = await GM.getValue('bulkCurrentIndex', 0);
2135 const totalCampaigns = await GM.getValue('bulkTotalCampaigns', 0);
2136
2137 if (campaignsJson) {
2138 const campaigns = JSON.parse(campaignsJson);
2139 const nextIndex = currentIndex + 1;
2140
2141 console.log(`Обработано кампаний: ${nextIndex} из ${totalCampaigns}`);
2142
2143 if (nextIndex < campaigns.length) {
2144 await GM.setValue('bulkCurrentIndex', nextIndex);
2145 console.log(`Переходим к кампании ${nextIndex + 1}: ${campaigns[nextIndex]}`);
2146 window.location.href = campaigns[nextIndex];
2147 } else {
2148 console.log('Все кампании обработаны');
2149 await GM.deleteValue('bulkProcessingDRR');
2150 await GM.deleteValue('bulkCampaigns');
2151 await GM.deleteValue('bulkCurrentIndex');
2152 await GM.deleteValue('bulkTotalCampaigns');
2153
2154 showStatus('✅ Все кампании обработаны!');
2155 alert('✅ Все кампании обработаны!');
2156 }
2157 }
2158 }
2159 return;
2160 }
2161
2162 console.log('Применение стратегии к кластерам включено');
2163
2164 // Переходим во вкладку "Кластеры"
2165 const tabs = Array.from(document.querySelectorAll('button[role="tab"]'));
2166 const clustersTab = tabs.find(tab => tab.textContent.trim() === 'Кластеры');
2167 if (!clustersTab) {
2168 console.error('Вкладка "Кластеры" не найдена');
2169 showStatus('⚠️ Вкладка "Кластеры" не найдена', true);
2170 return;
2171 }
2172
2173 clustersTab.click();
2174 console.log('Клик на "Кластеры" выполнен');
2175 await wait(1000);
2176
2177 // Ждем загрузки кластеров
2178 let loadingAttempts = 0;
2179 while (loadingAttempts < 10) {
2180 const skeleton = document.querySelector('.MuiSkeleton-root');
2181 if (!skeleton) {
2182 console.log('Кластеры загружены');
2183 break;
2184 }
2185 console.log(`Ожидание загрузки кластеров, попытка ${loadingAttempts + 1}`);
2186 await wait(500);
2187 loadingAttempts++;
2188 }
2189
2190 // СНАЧАЛА применяем обычную стратегию ко ВСЕМ кластерам
2191 showStatus('Применение обычной стратегии ко всем кластерам...');
2192 console.log('Шаг 1: Применяем обычную стратегию ко всем кластерам');
2193
2194 // Выделяем отфильтрованные кластеры
2195 showStatus('Выделение отфильтрованных кластеров...');
2196
2197 // Сначала кликаем на первый чекбокс в списке кластеров
2198 const firstClusterCheckbox = document.querySelector('tbody input[type="checkbox"]');
2199 if (firstClusterCheckbox) {
2200 firstClusterCheckbox.click();
2201 console.log('Клик на первый чекбокс кластера выполнен');
2202 await wait(500);
2203 }
2204
2205 // Теперь кликаем на общий чекбокс для выделения всех
2206 let selectAllCheckbox2 = document.querySelector('.css-1ytbthu .css-vlug8u');
2207 if (!selectAllCheckbox2) {
2208 selectAllCheckbox2 = document.querySelector('thead .css-vlug8u');
2209 }
2210 if (!selectAllCheckbox2) {
2211 const checkboxes2 = document.querySelectorAll('input[type="checkbox"]');
2212 if (checkboxes2.length > 0) {
2213 selectAllCheckbox2 = checkboxes2[0];
2214 }
2215 }
2216
2217 if (!selectAllCheckbox2) {
2218 console.error('Checkbox для отфильтрованных кластеров не найден');
2219 return;
2220 }
2221
2222 selectAllCheckbox2.click();
2223 console.log('Клик на checkbox отфильтрованных кластеров выполнен');
2224 await wait(1000);
2225
2226 // Открываем меню "Действия"
2227 showStatus('Открытие меню действий...');
2228 console.log('Ищем кнопку "Действия"...');
2229 const actionsButton = document.querySelector('.css-1rll63h');
2230 if (!actionsButton) {
2231 console.error('Кнопка "Действия" не найдена');
2232 showStatus('⚠️ Кнопка "Действия" не найдена', true);
2233 return;
2234 }
2235
2236 console.log('Кнопка "Действия" найдена, кликаем...');
2237 actionsButton.click();
2238 console.log('Клик на "Действия" выполнен');
2239 await wait(300);
2240
2241 // Кликаем на "Управление"
2242 showStatus('Открытие управления...');
2243 console.log('Ищем кнопку "Управление"...');
2244 const managementButtons = document.querySelectorAll('.css-hq58ok');
2245 console.log(`Найдено кнопок управления: ${managementButtons.length}`);
2246 if (managementButtons.length < 2) {
2247 console.error('Кнопка "Управление" не найдена');
2248 showStatus('⚠️ Кнопка "Управление" не найдена', true);
2249 return;
2250 }
2251
2252 console.log('Кликаем на вторую кнопку "Управление"...');
2253 managementButtons[1].click();
2254 console.log('Клик на "Управление" выполнен');
2255 await wait(300);
2256
2257 // Кликаем на "Стратегия"
2258 showStatus('Открытие стратегии кластеров...');
2259 console.log('Ищем вкладку "Стратегия"...');
2260 const strategyTabs = document.querySelectorAll('.css-582wun');
2261 console.log(`Найдено вкладок стратегии: ${strategyTabs.length}`);
2262 if (strategyTabs.length < 3) {
2263 console.error('Вкладка "Стратегия" не найдена');
2264 showStatus('⚠️ Вкладка "Стратегия" не найдена', true);
2265 return;
2266 }
2267
2268 console.log('Кликаем на третью вкладку "Стратегия"...');
2269 strategyTabs[2].click();
2270 console.log('Клик на "Стратегия" выполнен');
2271 await wait(300);
2272
2273 // Вставляем ОБЫЧНУЮ стратегию для всех кластеров
2274 showStatus('Вставка обычной стратегии для всех кластеров...');
2275 const clusterInsertButtons = document.querySelectorAll('.css-5kbhos');
2276 let clusterInsertButton = null;
2277
2278 for (const btn of clusterInsertButtons) {
2279 if (btn.textContent.includes('Вставить стратегию')) {
2280 clusterInsertButton = btn;
2281 break;
2282 }
2283 }
2284
2285 if (!clusterInsertButton && clusterInsertButtons.length > 0) {
2286 clusterInsertButton = clusterInsertButtons[0];
2287 }
2288
2289 if (!clusterInsertButton) {
2290 console.error('Кнопка "Вставить стратегию" для кластеров не найдена');
2291 showStatus('⚠️ Кнопка вставки стратегии не найдена', true);
2292 return;
2293 }
2294
2295 // Получаем ОБЫЧНУЮ кластерную стратегию
2296 const currentClusterStrategyCode = await getCurrentClusterStrategy();
2297 console.log('Используем обычную кластерную стратегию, длина:', currentClusterStrategyCode.length);
2298
2299 // Копируем стратегию в буфер обмена
2300 try {
2301 await navigator.clipboard.writeText(currentClusterStrategyCode);
2302 console.log('✓ Обычная кластерная стратегия скопирована');
2303 } catch {
2304 await GM.setClipboard(currentClusterStrategyCode);
2305 console.log('✓ Обычная кластерная стратегия скопирована через GM');
2306 }
2307
2308 await wait(500);
2309
2310 clusterInsertButton.click();
2311 console.log('Клик на "Вставить стратегию" для всех кластеров выполнен');
2312 await wait(1000);
2313
2314 // Находим поле для ввода ставки
2315 const clusterInputLabels = Array.from(document.querySelectorAll('.MuiInputLabel-root'));
2316 let clusterTargetInput = null;
2317
2318 for (const label of clusterInputLabels) {
2319 const labelText = label.textContent.trim();
2320 if (labelText.includes('Желаемое значение За корзину')) {
2321 const inputId = label.getAttribute('for');
2322 if (inputId) {
2323 clusterTargetInput = document.getElementById(inputId);
2324 console.log(`Найдено поле для кластеров: ${labelText}`);
2325 break;
2326 }
2327 }
2328 }
2329
2330 if (!clusterTargetInput) {
2331 const clusterInputs = document.querySelectorAll('input[type="number"]');
2332 for (const input of clusterInputs) {
2333 const name = input.getAttribute('name') || '';
2334 if (name.includes('CostPerAddedToCart') || name.includes('value')) {
2335 clusterTargetInput = input;
2336 console.log(`Найдено поле по имени: ${name}`);
2337 break;
2338 }
2339 }
2340 }
2341
2342 if (clusterTargetInput) {
2343 clusterTargetInput.focus();
2344 clusterTargetInput.value = cartCostRounded.toString();
2345 clusterTargetInput.dispatchEvent(new Event('input', { bubbles: true }));
2346 clusterTargetInput.dispatchEvent(new Event('change', { bubbles: true }));
2347 console.log(`Значение ${cartCostRounded} установлено для всех кластеров`);
2348 await wait(500);
2349 }
2350
2351 // Нажимаем "Применить"
2352 showStatus('Применение обычной стратегии ко всем кластерам...');
2353 const clusterStrategyApplyButtons = document.querySelectorAll('.css-eqlbov');
2354 if (clusterStrategyApplyButtons.length < 2) {
2355 console.error('Кнопка "Применить" не найдена');
2356 showStatus('⚠️ Кнопка "Применить" не найдена', true);
2357 return;
2358 }
2359
2360 clusterStrategyApplyButtons[1].click();
2361 console.log('Клик на "Применить" обычную стратегию выполнен');
2362 await wait(1000);
2363
2364 // Закрываем модальное окно
2365 const closeModalButtons = document.querySelectorAll('button[aria-label="Close"]');
2366 if (closeModalButtons.length > 0) {
2367 closeModalButtons[closeModalButtons.length - 1].click();
2368 console.log('Модальное окно закрыто');
2369 await wait(1000);
2370 }
2371
2372 // Снимаем выделение со всех кластеров
2373 console.log('Снимаем выделение со всех кластеров');
2374 let deselectAllCheckbox = document.querySelector('.css-1ytbthu .css-vlug8u');
2375 if (!deselectAllCheckbox) {
2376 deselectAllCheckbox = document.querySelector('thead .css-vlug8u');
2377 }
2378 if (!deselectAllCheckbox) {
2379 const checkboxes = document.querySelectorAll('input[type="checkbox"]');
2380 if (checkboxes.length > 0) {
2381 deselectAllCheckbox = checkboxes[0];
2382 }
2383 }
2384
2385 if (deselectAllCheckbox) {
2386 deselectAllCheckbox.click();
2387 console.log('Клик на checkbox для снятия выделения выполнен');
2388 await wait(1000);
2389 }
2390
2391 // ТЕПЕРЬ, если включен чекбокс "Удержание места", применяем фильтры
2392 if (shouldApplyHoldPosition) {
2393 console.log('Шаг 2: Применяем фильтры и стратегию удержания места');
2394 await applyHoldPositionToFilteredClusters(cartCostRounded);
2395 }
2396
2397 // Работаем с автофильтрами
2398 await applyAutofilters();
2399
2400 showStatus(`✅ Полностью готово! Стоимость корзины: ${cartCostRounded} ₽`);
2401
2402 // Проверяем, идет ли массовая обработка
2403 const savedDRR = await GM.getValue('bulkProcessingDRR', null);
2404 if (savedDRR !== null) {
2405 console.log('Массовая обработка: переход к следующей кампании');
2406 await wait(2000);
2407
2408 const campaignsJson = await GM.getValue('bulkCampaigns', null);
2409 const currentIndex = await GM.getValue('bulkCurrentIndex', 0);
2410 const totalCampaigns = await GM.getValue('bulkTotalCampaigns', 0);
2411
2412 if (campaignsJson) {
2413 const campaigns = JSON.parse(campaignsJson);
2414 const nextIndex = currentIndex + 1;
2415
2416 console.log(`Обработано кампаний: ${nextIndex} из ${totalCampaigns}`);
2417
2418 if (nextIndex < campaigns.length) {
2419 await GM.setValue('bulkCurrentIndex', nextIndex);
2420 console.log(`Переходим к кампании ${nextIndex + 1}: ${campaigns[nextIndex]}`);
2421 // Просто перенаправляем текущую вкладку на следующую кампанию
2422 window.location.href = campaigns[nextIndex];
2423 } else {
2424 console.log('Все кампании обработаны');
2425 await GM.deleteValue('bulkProcessingDRR');
2426 await GM.deleteValue('bulkCampaigns');
2427 await GM.deleteValue('bulkCurrentIndex');
2428 await GM.deleteValue('bulkTotalCampaigns');
2429
2430 showStatus('✅ Все кампании обработаны!');
2431 alert('✅ Все кампании обработаны!');
2432 }
2433 }
2434 }
2435
2436 } catch (error) {
2437 console.error('Ошибка при выполнении стратегии:', error);
2438 showStatus(`Ошибка: ${error.message}`, true);
2439 await handleStrategyError();
2440 }
2441 }
2442
2443 // Функция для применения стратегии удержания места к отфильтрованным кластерам
2444 async function applyHoldPositionToFilteredClusters(cartCostRounded) {
2445 showStatus('Настройка фильтра "Удержание места"...');
2446 await wait(1500);
2447
2448 // Выбираем дату "За 7 дней"
2449 showStatus('Выбор даты "За 7 дней"...');
2450 const jt7z00Elements = document.querySelectorAll('.css-jt7z00');
2451
2452 if (jt7z00Elements.length >= 6) {
2453 const clusterIntervalInput = jt7z00Elements[5];
2454 Object.defineProperty(clusterIntervalInput, 'readOnly', { value: false, writable: true });
2455 clusterIntervalInput.focus();
2456 clusterIntervalInput.dispatchEvent(new Event('focusin', { bubbles: true }));
2457 clusterIntervalInput.click();
2458
2459 await wait(2000);
2460
2461 const dateButtons = document.querySelectorAll('.css-ghy8jd');
2462 if (dateButtons.length >= 2) {
2463 dateButtons[1].click();
2464 console.log('Клик на "За 7 дней" выполнен');
2465 await wait(1000);
2466
2467 const applyDateButtons = document.querySelectorAll('.css-7wa720');
2468 if (applyDateButtons.length > 0) {
2469 applyDateButtons[0].click();
2470 console.log('Клик на "Применить" дату выполнен');
2471 await wait(1500);
2472 }
2473 }
2474 }
2475
2476 // Открываем фильтры
2477 const filtersButton = document.querySelector('.css-a54mx2');
2478 if (!filtersButton) {
2479 console.error('Кнопка "Фильтры" не найдена');
2480 return;
2481 }
2482
2483 filtersButton.click();
2484 console.log('Клик на "Фильтры" выполнен');
2485 await wait(1500);
2486
2487 // Добавляем фильтр
2488 const addFilterButtons = document.querySelectorAll('.css-8h18y2');
2489 if (addFilterButtons.length < 2) {
2490 console.error('Кнопка "Добавить фильтр" не найдена');
2491 return;
2492 }
2493
2494 addFilterButtons[1].click();
2495 console.log('Клик на "Добавить фильтр" выполнен');
2496 await wait(1500);
2497
2498 // Выбираем "Среднее место в выдаче"
2499 const filterOptions = document.querySelectorAll('.css-1ytbthu');
2500 if (filterOptions.length < 6) {
2501 console.error('Опция "Среднее место в выдаче" не найдена');
2502 return;
2503 }
2504
2505 filterOptions[5].click();
2506 console.log('Клик на "Среднее место в выдаче" выполнен');
2507 await wait(1500);
2508
2509 // Очищаем фильтр
2510 const allInputsWithVse = document.querySelectorAll('input[value="Все"]');
2511 let avgPositionInput = allInputsWithVse.length >= 2 ? allInputsWithVse[1] : allInputsWithVse[0];
2512
2513 if (!avgPositionInput) {
2514 console.error('Input "Среднее место в выдаче" не найден');
2515 return;
2516 }
2517
2518 const inputContainer = avgPositionInput.closest('.MuiInputBase-root');
2519 const clearButton = inputContainer?.querySelector('button');
2520
2521 if (!clearButton) {
2522 console.error('Кнопка очистки не найдена');
2523 return;
2524 }
2525
2526 clearButton.click();
2527 console.log('Клик на кнопку очистки выполнен');
2528 await wait(1500);
2529
2530 // Выбираем "Между"
2531 const hiddenInputsForComparison = document.querySelectorAll('input.MuiSelect-nativeInput');
2532 let comparisonContainer = null;
2533
2534 for (const inp of hiddenInputsForComparison) {
2535 const container = inp.closest('.MuiFormControl-root');
2536 const label = container?.querySelector('label');
2537 if (label && label.textContent.includes('Сравнение')) {
2538 comparisonContainer = container;
2539 break;
2540 }
2541 }
2542
2543 if (!comparisonContainer) {
2544 console.error('Контейнер "Сравнение" не найден');
2545 return;
2546 }
2547
2548 const arrowButton = comparisonContainer.querySelector('button');
2549 if (!arrowButton) {
2550 console.error('Кнопка со стрелкой не найдена');
2551 return;
2552 }
2553
2554 arrowButton.click();
2555 console.log('Клик на кнопку со стрелкой выполнен');
2556 await wait(1000);
2557
2558 // Выбираем "Между"
2559 const options = document.querySelectorAll('[role="option"]');
2560 const betweenOption = Array.from(options).find(opt => opt.textContent.trim() === 'Между');
2561
2562 if (!betweenOption) {
2563 console.error('Опция "Между" не найдена');
2564 return;
2565 }
2566
2567 betweenOption.click();
2568 console.log('Клик на "Между" выполнен');
2569 await wait(1500);
2570
2571 // Устанавливаем значения "от" и "до"
2572 const allLabels = document.querySelectorAll('label');
2573 let fromInput = null;
2574 let toInput = null;
2575
2576 for (const label of allLabels) {
2577 const text = label.textContent.trim();
2578 if (text === 'Значение от') {
2579 const inputId = label.getAttribute('for');
2580 if (inputId) {
2581 fromInput = document.getElementById(inputId);
2582 }
2583 } else if (text === 'Значение до') {
2584 const inputId = label.getAttribute('for');
2585 if (inputId) {
2586 toInput = document.getElementById(inputId);
2587 }
2588 }
2589 }
2590
2591 if (!fromInput || !toInput) {
2592 console.error('Поля "от" и "до" не найдены');
2593 return;
2594 }
2595
2596 // Устанавливаем "от" = 1
2597 fromInput.focus();
2598 fromInput.value = '1';
2599 fromInput.dispatchEvent(new Event('input', { bubbles: true }));
2600 fromInput.dispatchEvent(new Event('change', { bubbles: true }));
2601 console.log('Установлено значение "от" = 1');
2602 await wait(500);
2603
2604 // Устанавливаем "до" = 4
2605 toInput.focus();
2606 toInput.value = '4';
2607 toInput.dispatchEvent(new Event('input', { bubbles: true }));
2608 toInput.dispatchEvent(new Event('change', { bubbles: true }));
2609 console.log('Установлено значение "до" = 4');
2610 await wait(1000);
2611
2612 // Выделяем отфильтрованные кластеры
2613 showStatus('Выделение отфильтрованных кластеров...');
2614
2615 // Сначала кликаем на первый чекбокс в списке кластеров
2616 const firstClusterCheckbox = document.querySelector('tbody input[type="checkbox"]');
2617 if (firstClusterCheckbox) {
2618 firstClusterCheckbox.click();
2619 console.log('Клик на первый чекбокс кластера выполнен');
2620 await wait(500);
2621 }
2622
2623 // Теперь кликаем на общий чекбокс для выделения всех
2624 let selectAllCheckbox2 = document.querySelector('.css-1ytbthu .css-vlug8u');
2625 if (!selectAllCheckbox2) {
2626 selectAllCheckbox2 = document.querySelector('thead .css-vlug8u');
2627 }
2628 if (!selectAllCheckbox2) {
2629 const checkboxes2 = document.querySelectorAll('input[type="checkbox"]');
2630 if (checkboxes2.length > 0) {
2631 selectAllCheckbox2 = checkboxes2[0];
2632 }
2633 }
2634
2635 if (!selectAllCheckbox2) {
2636 console.error('Checkbox для отфильтрованных кластеров не найден');
2637 return;
2638 }
2639
2640 selectAllCheckbox2.click();
2641 console.log('Клик на checkbox отфильтрованных кластеров выполнен');
2642 await wait(1000);
2643
2644 // Открываем меню "Действия"
2645 showStatus('Открытие меню действий для отфильтрованных кластеров...');
2646 const actionsButton2 = document.querySelector('.css-1rll63h');
2647 if (!actionsButton2) {
2648 console.error('Кнопка "Действия" не найдена');
2649 return;
2650 }
2651
2652 actionsButton2.click();
2653 await wait(300);
2654
2655 // Кликаем на "Управление"
2656 const managementButtons2 = document.querySelectorAll('.css-hq58ok');
2657 if (managementButtons2.length < 2) {
2658 console.error('Кнопка "Управление" не найдена');
2659 return;
2660 }
2661
2662 managementButtons2[1].click();
2663 await wait(300);
2664
2665 // Кликаем на "Стратегия"
2666 const strategyTabs2 = document.querySelectorAll('.css-582wun');
2667 if (strategyTabs2.length < 3) {
2668 console.error('Вкладка "Стратегия" не найдена');
2669 return;
2670 }
2671
2672 strategyTabs2[2].click();
2673 await wait(300);
2674
2675 // Вставляем стратегию УДЕРЖАНИЯ МЕСТА
2676 showStatus('Вставка стратегии удержания места...');
2677 const clusterInsertButtons2 = document.querySelectorAll('.css-5kbhos');
2678 let clusterInsertButton2 = null;
2679
2680 for (const btn of clusterInsertButtons2) {
2681 if (btn.textContent.includes('Вставить стратегию')) {
2682 clusterInsertButton2 = btn;
2683 break;
2684 }
2685 }
2686
2687 if (!clusterInsertButton2 && clusterInsertButtons2.length > 0) {
2688 clusterInsertButton2 = clusterInsertButtons2[0];
2689 }
2690
2691 if (!clusterInsertButton2) {
2692 console.error('Кнопка "Вставить стратегию" для отфильтрованных кластеров не найдена');
2693 return;
2694 }
2695
2696 // Получаем стратегию УДЕРЖАНИЯ МЕСТА
2697 const holdPositionClusterStrategyCode = await getHoldPositionClusterStrategy();
2698 console.log('Используем стратегию удержания места, длина:', holdPositionClusterStrategyCode.length);
2699
2700 try {
2701 await navigator.clipboard.writeText(holdPositionClusterStrategyCode);
2702 console.log('✓ Стратегия удержания места скопирована');
2703 } catch {
2704 await GM.setClipboard(holdPositionClusterStrategyCode);
2705 console.log('✓ Стратегия удержания места скопирована через GM');
2706 }
2707
2708 await wait(500);
2709
2710 clusterInsertButton2.click();
2711 console.log('Клик на "Вставить стратегию" удержания места выполнен');
2712 await wait(1000);
2713
2714 // Находим поле для ввода ставки
2715 const clusterInputLabels2 = Array.from(document.querySelectorAll('.MuiInputLabel-root'));
2716 let clusterTargetInput2 = null;
2717
2718 for (const label of clusterInputLabels2) {
2719 const labelText = label.textContent.trim();
2720 if (labelText.includes('Желаемое значение За корзину')) {
2721 const inputId = label.getAttribute('for');
2722 if (inputId) {
2723 clusterTargetInput2 = document.getElementById(inputId);
2724 break;
2725 }
2726 }
2727 }
2728
2729 if (!clusterTargetInput2) {
2730 const clusterInputs2 = document.querySelectorAll('input[type="number"]');
2731 for (const input of clusterInputs2) {
2732 const name = input.getAttribute('name') || '';
2733 if (name.includes('CostPerAddedToCart') || name.includes('value')) {
2734 clusterTargetInput2 = input;
2735 break;
2736 }
2737 }
2738 }
2739
2740 if (clusterTargetInput2) {
2741 clusterTargetInput2.focus();
2742 clusterTargetInput2.value = cartCostRounded.toString();
2743 clusterTargetInput2.dispatchEvent(new Event('input', { bubbles: true }));
2744 clusterTargetInput2.dispatchEvent(new Event('change', { bubbles: true }));
2745 console.log(`Значение ${cartCostRounded} установлено для отфильтрованных кластеров`);
2746 await wait(500);
2747 }
2748
2749 // Нажимаем "Применить"
2750 showStatus('Применение стратегии удержания места...');
2751 const clusterStrategyApplyButtons2 = document.querySelectorAll('.css-eqlbov');
2752 if (clusterStrategyApplyButtons2.length >= 2) {
2753 clusterStrategyApplyButtons2[1].click();
2754 console.log('Клик на "Применить" стратегию удержания места выполнен');
2755 await wait(1000);
2756
2757 // Закрываем модальное окно
2758 const closeModalButtons2 = document.querySelectorAll('button[aria-label="Close"]');
2759 if (closeModalButtons2.length > 0) {
2760 closeModalButtons2[closeModalButtons2.length - 1].click();
2761 console.log('Модальное окно закрыто');
2762 await wait(1000);
2763 }
2764 }
2765 }
2766
2767 // Функция для применения автофильтров
2768 async function applyAutofilters() {
2769 showStatus('Открытие автофильтров...');
2770
2771 const autofilterAccordion = document.getElementById('bidder-constructor');
2772 if (!autofilterAccordion) {
2773 console.error('Аккордеон "Автофильтры" не найден');
2774 return;
2775 }
2776
2777 const accordionButton = autofilterAccordion.querySelector('button[aria-expanded]');
2778 const isExpanded = accordionButton && accordionButton.getAttribute('aria-expanded') === 'true';
2779
2780 if (!isExpanded && accordionButton) {
2781 accordionButton.click();
2782 console.log('Аккордеон "Автофильтры" раскрыт');
2783 await wait(1000);
2784 }
2785
2786 // Кликаем на "Шаблоны"
2787 showStatus('Открытие шаблонов...');
2788 const templatesButtons = autofilterAccordion.querySelectorAll('button');
2789 let templateButton = null;
2790
2791 for (const btn of templatesButtons) {
2792 if (btn.textContent.trim() === 'Шаблоны') {
2793 templateButton = btn;
2794 break;
2795 }
2796 }
2797
2798 if (!templateButton) {
2799 console.error('Кнопка "Шаблоны" не найдена');
2800 return;
2801 }
2802
2803 templateButton.click();
2804 console.log('Кнопка "Шаблоны" выбрана');
2805 await wait(500);
2806
2807 // Выбираем "Расширение"
2808 showStatus('Выбор шаблона "Расширение"...');
2809 const allElements = document.querySelectorAll('div, button, span, p');
2810 let extensionTemplate = null;
2811
2812 for (const el of allElements) {
2813 const text = el.textContent.trim();
2814 if (text === 'Расширение') {
2815 extensionTemplate = el;
2816 break;
2817 }
2818 }
2819
2820 if (!extensionTemplate) {
2821 console.error('Шаблон "Расширение" не найден');
2822 return;
2823 }
2824
2825 extensionTemplate.click();
2826 console.log('Шаблон "Расширение" выбран');
2827 await wait(500);
2828
2829 // Сохраняем
2830 showStatus('Сохранение автофильтров...');
2831 const saveButtonsAfterTemplate = document.querySelectorAll('button');
2832 let saveAutofilterButton = null;
2833
2834 for (const btn of saveButtonsAfterTemplate) {
2835 if (btn.textContent.trim() === 'Сохранить') {
2836 saveAutofilterButton = btn;
2837 break;
2838 }
2839 }
2840
2841 if (!saveAutofilterButton) {
2842 console.error('Кнопка "Сохранить" не найдена');
2843 return;
2844 }
2845
2846 saveAutofilterButton.click();
2847 console.log('Клик на "Сохранить" автофильтры выполнен');
2848 await wait(1000);
2849
2850 // Применяем автофильтры вручную
2851 showStatus('Применение автофильтров вручную...');
2852 const applyButtonsAfterSave = document.querySelectorAll('button');
2853 let applyAutofilterButton = null;
2854
2855 for (const btn of applyButtonsAfterSave) {
2856 if (btn.textContent.trim() === 'Применить автофильтры вручную') {
2857 applyAutofilterButton = btn;
2858 break;
2859 }
2860 }
2861
2862 if (!applyAutofilterButton) {
2863 console.error('Кнопка "Применить автофильтры вручную" не найдена');
2864 return;
2865 }
2866
2867 applyAutofilterButton.click();
2868 console.log('Клик на "Применить автофильтры вручную" выполнен');
2869 await wait(1000);
2870 }
2871
2872 // Функция для обработки ошибок и перехода к следующей кампании
2873 async function handleStrategyError() {
2874 const savedDRR = await GM.getValue('bulkProcessingDRR', null);
2875 if (savedDRR !== null) {
2876 console.log('Обнаружена ошибка при массовой обработке, ждем 10 секунд');
2877 showStatus('⚠️ Ошибка! Переход к следующей кампании через 10 секунд...', true);
2878
2879 for (let i = 10; i > 0; i--) {
2880 showStatus(`⚠️ Ошибка! Переход к следующей через ${i} сек...`, true);
2881 await wait(1000);
2882 }
2883
2884 const campaignsJson = await GM.getValue('bulkCampaigns', null);
2885 const currentIndex = await GM.getValue('bulkCurrentIndex', 0);
2886 const totalCampaigns = await GM.getValue('bulkTotalCampaigns', 0);
2887
2888 if (campaignsJson) {
2889 const campaigns = JSON.parse(campaignsJson);
2890 const nextIndex = currentIndex + 1;
2891
2892 console.log(`Обработано кампаний: ${nextIndex} из ${totalCampaigns}`);
2893
2894 if (nextIndex < campaigns.length) {
2895 await GM.setValue('bulkCurrentIndex', nextIndex);
2896 console.log(`Переходим к кампании ${nextIndex + 1}: ${campaigns[nextIndex]}`);
2897 // Просто перенаправляем текущую вкладку на следующую кампанию
2898 window.location.href = campaigns[nextIndex];
2899 } else {
2900 console.log('Все кампании обработаны');
2901 await GM.deleteValue('bulkProcessingDRR');
2902 await GM.deleteValue('bulkCampaigns');
2903 await GM.deleteValue('bulkCurrentIndex');
2904 await GM.deleteValue('bulkTotalCampaigns');
2905
2906 showStatus('✅ Все кампании обработаны!');
2907 alert('✅ Все кампании обработаны!');
2908 }
2909 }
2910 }
2911 }
2912
2913 // Инициализация
2914 function init() {
2915 console.log('Инициализация расширения');
2916
2917 if (window.location.href.includes('/advert/campaigns')) {
2918 console.log('Страница списка кампаний обнаружена');
2919 setTimeout(() => {
2920 if (document.body) {
2921 createBulkUI();
2922 } else {
2923 console.error('Body не найден');
2924 }
2925 }, 1000);
2926 } else if (window.location.href.includes('/campaigns/auto-campaigns/') && window.location.href.includes('/campaign')) {
2927 console.log('Страница кампании обнаружена');
2928 setTimeout(() => {
2929 if (document.body) {
2930 createUI();
2931 } else {
2932 console.error('Body не найден');
2933 }
2934 }, 1000);
2935 } else {
2936 console.log('Не на странице кампании, UI не создается');
2937 }
2938 }
2939
2940 // Запускаем инициализацию
2941 init();
2942
2943})();