Автоматическая установка стратегии рекламной кампании на основе статистики
Size
144.3 KB
Version
1.8.139
Created
Mar 18, 2026
Updated
29 days ago
1// ==UserScript==
2// @name MP Manager WB Cluster Auto Strategy Setter
3// @description Автоматическая установка стратегии рекламной кампании на основе статистики
4// @version 1.8.139
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 = 'eyJjYW1wYWlnblR5cGUiOiJTZWFyY2hDYXRhbG9nIiwidmVyc2lvbiI6MiwiZGF0YSI6eyJzdGF0ZSI6IkVuYWJsZWQiLCJtb2RlIjp7InR5cGUiOiJQb3NpdGlvbiIsInBhdXNlVHlwZSI6IlJlc3VtZSJ9LCJzdHJhdGVneSI6eyJ0eXBlIjoiQ2FtcGFpZ25TdGF0cyIsIm1vZGUiOiJQbGFjZSIsImJpZE1vZGUiOiJDb3N0UGVyQ2xpY2siLCJjbHVzdGVyU3RhdHMiOnsibWF4QmlkIjo4MDAwLCJtaW5CaWQiOjQwMCwiaW50ZXJ2YWwiOiJUaHJlZURheXMiLCJydWxlcyI6W3sidHlwZSI6IkF2ZXJhZ2VQb3NpdGlvbiIsIm1vZGUiOiJWYWx1ZSIsInZhbHVlIjoyfSx7InR5cGUiOiJDb3N0UGVyQWRkZWRUb0NhcnQiLCJtb2RlIjoiQXZlcmFnZVZhbHVlIn1dfSwicGxhY2VTdHJhdGVneSI6eyJ0eXBlIjoiS2V5d29yZHMiLCJrZXl3b3JkcyI6eyJrZXl3b3JkcyI6W119fSwiaXNDbHVzdGVyIjpmYWxzZX19';
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 startProgressMonitoring();
1530 }
1531
1532 // Функция для мониторинга прогресса массовой обработки
1533 async function startProgressMonitoring() {
1534 setInterval(async () => {
1535 const currentIndex = await GM.getValue('bulkCurrentIndex', 0);
1536 const totalCampaigns = await GM.getValue('bulkTotalCampaigns', 0);
1537
1538 if (totalCampaigns > 0) {
1539 updateBulkProgress(currentIndex, totalCampaigns);
1540
1541 const progressDiv = document.getElementById('bulk-progress');
1542 if (progressDiv && progressDiv.style.display === 'none') {
1543 progressDiv.style.display = 'block';
1544 }
1545
1546 const controlButtons = document.getElementById('bulk-control-buttons');
1547 if (controlButtons && controlButtons.style.display === 'none' && currentIndex < totalCampaigns) {
1548 controlButtons.style.display = 'flex';
1549 }
1550 }
1551 }, 1000); // Обновляем каждую секунду
1552 }
1553
1554 // Функция для создания перетаскиваемого элемента
1555 function makeDraggable(element) {
1556 let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
1557 let isDragging = false;
1558
1559 element.onmousedown = dragMouseDown;
1560
1561 function dragMouseDown(e) {
1562 // Не перетаскиваем, если кликнули на input, button или другие интерактивные элементы
1563 if (e.target.tagName === 'INPUT' || e.target.tagName === 'BUTTON' || e.target.tagName === 'TEXTAREA') {
1564 return;
1565 }
1566
1567 e.preventDefault();
1568 isDragging = true;
1569 pos3 = e.clientX;
1570 pos4 = e.clientY;
1571 document.onmouseup = closeDragElement;
1572 document.onmousemove = elementDrag;
1573 }
1574
1575 function elementDrag(e) {
1576 if (!isDragging) return;
1577 pos1 = pos3 - e.clientX;
1578 pos2 = pos4 - e.clientY;
1579 pos3 = e.clientX;
1580 pos4 = e.clientY;
1581 element.style.top = (element.offsetTop - pos2) + 'px';
1582 element.style.left = (element.offsetLeft - pos1) + 'px';
1583 element.style.right = 'auto';
1584 element.style.bottom = 'auto';
1585 }
1586
1587 function closeDragElement() {
1588 isDragging = false;
1589 document.onmouseup = null;
1590 document.onmousemove = null;
1591 }
1592 }
1593
1594 // Функция для обновления количества кампаний
1595 function updateCampaignsCount() {
1596 const campaignLinks = document.querySelectorAll('a[href*="/campaigns/auto-campaigns/"][href*="/campaign"]');
1597 const count = campaignLinks.length;
1598 const countElement = document.getElementById('campaigns-count');
1599 if (countElement) {
1600 countElement.textContent = count;
1601 }
1602 console.log(`Найдено кампаний: ${count}`);
1603 }
1604
1605 // Функция для показа статуса массовой обработки
1606 function showBulkStatus(message, isError = false) {
1607 const statusDiv = document.getElementById('bulk-status-message');
1608 if (statusDiv) {
1609 statusDiv.textContent = message;
1610 statusDiv.style.display = 'block';
1611 statusDiv.style.background = isError ? 'rgba(255, 59, 48, 0.9)' : 'rgba(52, 199, 89, 0.9)';
1612 console.log(`Статус массовой обработки: ${message}`);
1613 }
1614 }
1615
1616 // Функция для обновления прогресса
1617 function updateBulkProgress(current, total) {
1618 const progressDiv = document.getElementById('bulk-progress');
1619 const progressText = document.getElementById('bulk-progress-text');
1620 const progressBar = document.getElementById('bulk-progress-bar');
1621
1622 if (progressDiv && progressText && progressBar) {
1623 progressDiv.style.display = 'block';
1624 progressText.textContent = `${current}/${total}`;
1625 const percentage = (current / total) * 100;
1626 progressBar.style.width = `${percentage}%`;
1627 }
1628 }
1629
1630 // Функция для прокрутки страницы вниз для загрузки всех кампаний
1631 async function scrollToLoadAllCampaigns() {
1632 console.log('Начинаем загрузку всех кампаний через прокрутку...');
1633
1634 // Находим контейнер с прокруткой
1635 const tableContainer = document.querySelector('.container.MuiBox-root.css-9hf803');
1636
1637 if (!tableContainer) {
1638 console.error('Контейнер таблицы не найден');
1639 return [];
1640 }
1641
1642 console.log('Контейнер найден, начинаем прокрутку...');
1643 console.log(`Высота контейнера: ${tableContainer.scrollHeight}px`);
1644
1645 // Собираем уникальные ссылки во время прокрутки
1646 const uniqueLinks = new Set();
1647
1648 // Прокручиваем контейнер постепенно, чтобы загрузить все кампании
1649 let previousLinksCount = 0;
1650 let stableCount = 0;
1651 const maxAttempts = 200; // Максимум попыток
1652 let attempts = 0;
1653 const scrollStep = 500; // Прокручиваем по 500px за раз
1654
1655 while (attempts < maxAttempts) {
1656 // Собираем ссылки на текущем шаге
1657 const currentLinks = document.querySelectorAll('a[href*="/campaigns/auto-campaigns/"][href*="/campaign"]');
1658 currentLinks.forEach(link => {
1659 uniqueLinks.add(link.href);
1660 });
1661
1662 const currentCount = uniqueLinks.size;
1663 console.log(`Загружено кампаний: ${currentCount}, прокрутка: ${tableContainer.scrollTop}/${tableContainer.scrollHeight}`);
1664
1665 // Прокручиваем контейнер постепенно
1666 tableContainer.scrollTop += scrollStep;
1667
1668 // Ждем загрузки новых элементов
1669 await wait(500);
1670
1671 // Если количество не изменилось
1672 if (currentCount === previousLinksCount) {
1673 stableCount++;
1674 // Если количество стабильно 5 раз подряд - значит все загружено
1675 if (stableCount >= 5) {
1676 console.log('Все кампании загружены');
1677 break;
1678 }
1679 } else {
1680 stableCount = 0;
1681 previousLinksCount = currentCount;
1682 }
1683
1684 // Если достигли конца контейнера
1685 if (tableContainer.scrollTop + tableContainer.clientHeight >= tableContainer.scrollHeight - 10) {
1686 console.log('Достигнут конец контейнера');
1687 // Ждем еще немного для загрузки последних элементов
1688 await wait(1000);
1689
1690 // Собираем последние ссылки
1691 const finalLinks = document.querySelectorAll('a[href*="/campaigns/auto-campaigns/"][href*="/campaign"]');
1692 finalLinks.forEach(link => {
1693 uniqueLinks.add(link.href);
1694 });
1695
1696 // Проверяем еще раз количество
1697 if (uniqueLinks.size === previousLinksCount) {
1698 break;
1699 }
1700 previousLinksCount = uniqueLinks.size;
1701 }
1702
1703 attempts++;
1704 }
1705
1706 // Преобразуем Set в массив URL (НЕ прокручиваем обратно!)
1707 const links = Array.from(uniqueLinks);
1708
1709 console.log(`Найдено кампаний: ${links.length}`);
1710 console.log(`Всего попыток прокрутки: ${attempts}`);
1711 return links;
1712 }
1713
1714 // Функция для массовой обработки кампаний
1715 async function runBulkStrategy() {
1716 if (isBulkProcessing) {
1717 showBulkStatus('Обработка уже выполняется', true);
1718 return;
1719 }
1720
1721 try {
1722 isBulkProcessing = true;
1723 console.log('Начало массовой обработки кампаний');
1724
1725 bulkDesiredPercentage = parseFloat(document.getElementById('bulk-desired-percentage').value);
1726 if (!bulkDesiredPercentage || bulkDesiredPercentage <= 0) {
1727 showBulkStatus('Ошибка: введите корректный процент', true);
1728 isBulkProcessing = false;
1729 return;
1730 }
1731 console.log(`Желаемый процент: ${bulkDesiredPercentage}%`);
1732
1733 // Сохраняем ДРР для автоматической обработки
1734 await GM.setValue('bulkProcessingDRR', bulkDesiredPercentage);
1735 console.log(`ДРР ${bulkDesiredPercentage} сохранен для массовой обработки`);
1736
1737 // Сохраняем состояние чекбоксов
1738 const applyToClustersCheckbox = document.getElementById('bulk-apply-to-clusters');
1739 const holdPositionCheckbox = document.getElementById('bulk-hold-position');
1740
1741 if (applyToClustersCheckbox) {
1742 await GM.setValue('bulkApplyToClusters', applyToClustersCheckbox.checked);
1743 console.log(`Сохранено состояние "Применить к кластерам": ${applyToClustersCheckbox.checked}`);
1744 }
1745
1746 if (holdPositionCheckbox) {
1747 await GM.setValue('bulkHoldPosition', holdPositionCheckbox.checked);
1748 console.log(`Сохранено состояние "Удержание места": ${holdPositionCheckbox.checked}`);
1749 }
1750
1751 // Прокручиваем страницу для загрузки всех кампаний
1752 const campaignLinks = await scrollToLoadAllCampaigns();
1753
1754 if (campaignLinks.length === 0) {
1755 showBulkStatus('Ошибка: кампании не найдены', true);
1756 isBulkProcessing = false;
1757 await GM.deleteValue('bulkProcessingDRR');
1758 return;
1759 }
1760
1761 console.log(`Найдено кампаний для обработки: ${campaignLinks.length}`);
1762 showBulkStatus(`Начинаем обработку ${campaignLinks.length} кампаний...`);
1763 updateBulkProgress(0, campaignLinks.length);
1764
1765 // Сохраняем список кампаний (массив URL), текущий индекс и общее количество
1766 await GM.setValue('bulkCampaigns', JSON.stringify(campaignLinks));
1767 await GM.setValue('bulkCurrentIndex', 0);
1768 await GM.setValue('bulkTotalCampaigns', campaignLinks.length);
1769
1770 // Показываем кнопки управления
1771 const controlButtons = document.getElementById('bulk-control-buttons');
1772 if (controlButtons) {
1773 controlButtons.style.display = 'flex';
1774 }
1775
1776 // Открываем первую кампанию в новой вкладке через window.open
1777 if (campaignLinks.length > 0) {
1778 console.log(`Открытие кампании 1/${campaignLinks.length}: ${campaignLinks[0]}`);
1779 window.open(campaignLinks[0], '_blank');
1780 }
1781
1782 } catch (error) {
1783 console.error('Ошибка при массовой обработке:', error);
1784 showBulkStatus(`Ошибка: ${error.message}`, true);
1785 isBulkProcessing = false;
1786 await GM.deleteValue('bulkProcessingDRR');
1787 }
1788 }
1789
1790 // Функция для паузы массовой обработки
1791 async function pauseBulkProcessing() {
1792 bulkPaused = !bulkPaused;
1793 const pauseBtn = document.getElementById('pause-bulk-btn');
1794
1795 if (bulkPaused) {
1796 await GM.setValue('bulkPaused', true);
1797 pauseBtn.textContent = '▶️ Продолжить';
1798 showBulkStatus('⏸️ Обработка приостановлена');
1799 console.log('Массовая обработка приостановлена');
1800 } else {
1801 await GM.deleteValue('bulkPaused');
1802 pauseBtn.textContent = '⏸️ Пауза';
1803 showBulkStatus('▶️ Обработка возобновлена');
1804 console.log('Массовая обработка возобновлена');
1805
1806 // Продолжаем обработку - открываем следующую кампанию
1807 const campaignsJson = await GM.getValue('bulkCampaigns', null);
1808 const currentIndex = await GM.getValue('bulkCurrentIndex', 0);
1809 const totalCampaigns = await GM.getValue('bulkTotalCampaigns', 0);
1810
1811 if (campaignsJson) {
1812 const campaigns = JSON.parse(campaignsJson);
1813 const nextIndex = currentIndex + 1;
1814
1815 console.log(`Обработано кампаний: ${nextIndex} из ${totalCampaigns}`);
1816
1817 if (nextIndex < campaigns.length) {
1818 // Сохраняем новый индекс
1819 await GM.setValue('bulkCurrentIndex', nextIndex);
1820
1821 // Открываем следующую кампанию в новой вкладке через window.open
1822 console.log(`Открываем кампанию ${nextIndex + 1}: ${campaigns[nextIndex]}`);
1823 const newTab = window.open(campaigns[nextIndex], '_blank');
1824
1825 if (newTab) {
1826 // Закрываем текущую вкладку
1827 await wait(1000);
1828 window.close();
1829 } else {
1830 // Если блокировщик всплывающих окон, используем редирект
1831 console.log('Блокировщик всплывающих окон, используем редирект');
1832 window.location.href = campaigns[nextIndex];
1833 }
1834 }
1835 }
1836 }
1837 }
1838
1839 // Функция для остановки массовой обработки
1840 async function stopBulkProcessing() {
1841 await GM.deleteValue('bulkProcessingDRR');
1842 await GM.deleteValue('bulkCampaigns');
1843 await GM.deleteValue('bulkCurrentIndex');
1844 await GM.deleteValue('bulkTotalCampaigns');
1845
1846 showBulkStatus('⏹️ Обработка остановлена', true);
1847 console.log('Массовая обработка остановлена');
1848
1849 // Скрываем кнопки управления
1850 const controlButtons = document.getElementById('bulk-control-buttons');
1851 if (controlButtons) {
1852 controlButtons.style.display = 'none';
1853 }
1854
1855 isBulkProcessing = false;
1856 bulkPaused = false;
1857 }
1858
1859 // Функция для показа статуса
1860 function showStatus(message, isError = false) {
1861 const statusDiv = document.getElementById('status-message');
1862 if (statusDiv) {
1863 statusDiv.textContent = message;
1864 statusDiv.style.display = 'block';
1865 statusDiv.style.background = isError ? 'rgba(255, 59, 48, 0.9)' : 'rgba(52, 199, 89, 0.9)';
1866 console.log(`Статус: ${message}`);
1867 }
1868 }
1869
1870 // Функция для ожидания
1871 function wait(ms) {
1872 return new Promise(resolve => setTimeout(resolve, ms));
1873 }
1874
1875 // Функция для парсинга числа из строки
1876 function parseNumber(str) {
1877 if (!str) return 0;
1878 // Убираем все символы кроме цифр, точек и запятых
1879 const cleaned = str.replace(/[^\d.,]/g, '').replace(/\s/g, '');
1880 // Заменяем запятую на точку
1881 const normalized = cleaned.replace(',', '.');
1882 return parseFloat(normalized) || 0;
1883 }
1884
1885 // Функция для парсинга процента
1886 function parsePercentage(str) {
1887 if (!str) return 0;
1888 const cleaned = str.replace('%', '').replace(',', '.').trim();
1889 return parseFloat(cleaned) || 0;
1890 }
1891
1892 // Основная функция запуска стратегии
1893 async function runStrategy() {
1894 try {
1895 console.log('Начало выполнения стратегии');
1896 showStatus('Запуск процесса...');
1897
1898 const desiredPercentage = parseFloat(document.getElementById('desired-percentage').value);
1899 if (!desiredPercentage || desiredPercentage <= 0) {
1900 showStatus('Ошибка: введите корректный процент', true);
1901 await handleStrategyError();
1902 return;
1903 }
1904 console.log(`Желаемый процент: ${desiredPercentage}%`);
1905
1906 // Проверяем чекбоксы из обеих панелей
1907 const holdPositionCheckbox = document.getElementById('hold-position');
1908 const bulkHoldPositionCheckbox = document.getElementById('bulk-hold-position');
1909 const shouldApplyHoldPosition = holdPositionCheckbox ? holdPositionCheckbox.checked :
1910 (bulkHoldPositionCheckbox ? bulkHoldPositionCheckbox.checked : false);
1911
1912 // Шаг 1: Кликаем на статистику
1913 showStatus('Открытие статистики...');
1914 const statsButton = document.querySelector('.css-amj7dw');
1915 if (!statsButton) {
1916 showStatus('Ошибка: кнопка статистики не найдена', true);
1917 await handleStrategyError();
1918 return;
1919 }
1920 statsButton.click();
1921 console.log('Клик на статистику выполнен');
1922 await wait(2000);
1923
1924 // Шаг 1.5: Выбираем режим "Товары"
1925 showStatus('Выбор режима "Товары"...');
1926 const modeLabels = Array.from(document.querySelectorAll('label'));
1927 let modeInput = null;
1928
1929 for (const label of modeLabels) {
1930 if (label.textContent.trim() === 'Режим') {
1931 const inputId = label.getAttribute('for');
1932 if (inputId) {
1933 modeInput = document.getElementById(inputId);
1934 console.log(`Найдено поле Режим с id: ${inputId}`);
1935 break;
1936 }
1937 }
1938 }
1939
1940 if (modeInput) {
1941 modeInput.value = 'Товары';
1942 const inputEvent = new Event('input', { bubbles: true });
1943 const changeEvent = new Event('change', { bubbles: true });
1944 modeInput.dispatchEvent(inputEvent);
1945 modeInput.dispatchEvent(changeEvent);
1946
1947 console.log('Установлено значение: Товары');
1948 await wait(1000);
1949
1950 const options = document.querySelectorAll('[role="option"]');
1951 console.log(`Найдено опций: ${options.length}`);
1952
1953 if (options.length > 0) {
1954 const tovarOption = Array.from(options).find(opt => opt.textContent.includes('Товары'));
1955 if (tovarOption) {
1956 tovarOption.click();
1957 console.log('Выбран режим "Товары"');
1958 await wait(1000);
1959 }
1960 }
1961 }
1962
1963 // Шаг 2: Извлекаем данные из статистики
1964 showStatus('Извлечение данных...');
1965 await wait(500);
1966
1967 const stats = Array.from(document.querySelectorAll('.MuiTypography-caption.css-1et52kr'));
1968
1969 let sumOrders = 0;
1970 let ordersCount = 0;
1971 let cartToOrderPercent = 0;
1972
1973 stats.forEach(el => {
1974 const text = el.textContent.trim();
1975 const valueElement = el.closest('.MuiBox-root')?.querySelector('.MuiTypography-h3 .MuiTypography-body1');
1976 const value = valueElement ? valueElement.textContent.trim() : '';
1977
1978 console.log(`Найден показатель: ${text} = ${value}`);
1979
1980 if (text === 'Сумма заказов') {
1981 sumOrders = parseNumber(value);
1982 console.log(`Сумма заказов: ${sumOrders}`);
1983 } else if (text === 'Заказов') {
1984 ordersCount = parseNumber(value);
1985 console.log(`Заказов: ${ordersCount}`);
1986 } else if (text === 'Корзина → Заказ') {
1987 cartToOrderPercent = parsePercentage(value);
1988 console.log(`Корзина → Заказ: ${cartToOrderPercent}%`);
1989 }
1990 });
1991
1992 if (sumOrders === 0 || ordersCount === 0 || cartToOrderPercent === 0) {
1993 showStatus('Ошибка: не удалось получить данные статистики', true);
1994 console.error('Недостаточно данных:', { sumOrders, ordersCount, cartToOrderPercent });
1995 await handleStrategyError();
1996 return;
1997 }
1998
1999 // Шаг 3: Вычисляем стоимость корзины
2000 const cartCost = (sumOrders / ordersCount) * (desiredPercentage / 100) * (cartToOrderPercent / 100);
2001 const cartCostRounded = Math.round(cartCost * 100) / 100;
2002 console.log(`Расчет: (${sumOrders} / ${ordersCount}) * (${desiredPercentage} / 100) * (${cartToOrderPercent} / 100) = ${cartCostRounded}`);
2003 showStatus(`Рассчитано: ${cartCostRounded} ₽`);
2004
2005 // Закрываем статистику
2006 statsButton.click();
2007 await wait(500);
2008
2009 // Шаг 4: Кликаем на "Вставить стратегию"
2010 showStatus('Вставка стратегии...');
2011
2012 // Ищем ВСЕ кнопки и фильтруем по тексту
2013 const allButtonsForStrategy = document.querySelectorAll('button');
2014 const mainInsertStrategyButtons = [];
2015
2016 for (const btn of allButtonsForStrategy) {
2017 if (btn.textContent.includes('Вставить стратегию')) {
2018 mainInsertStrategyButtons.push(btn);
2019 }
2020 }
2021
2022 console.log(`Найдено кнопок "Вставить стратегию": ${mainInsertStrategyButtons.length}`);
2023
2024 // Берем первую кнопку для основной стратегии
2025 let insertButton = null;
2026 if (mainInsertStrategyButtons.length >= 1) {
2027 insertButton = mainInsertStrategyButtons[0];
2028 console.log('Используем первую кнопку "Вставить стратегию" для основной стратегии');
2029 }
2030
2031 if (!insertButton) {
2032 console.error('Кнопка "Вставить стратегию" не найдена');
2033 showStatus('⚠️ Кнопка вставки стратегии не найдена', true);
2034 await handleStrategyError();
2035 return;
2036 }
2037
2038 // Получаем текущую выбранную стратегию
2039 let currentStrategyCode;
2040 if (shouldApplyHoldPosition) {
2041 currentStrategyCode = await getHoldPositionStrategy();
2042 console.log('Используем стратегию удержания места, длина:', currentStrategyCode.length);
2043 } else {
2044 currentStrategyCode = await getCurrentStrategy();
2045 console.log('Используем обычную стратегию, длина:', currentStrategyCode.length);
2046 }
2047
2048 // Копируем код стратегии в буфер обмена
2049 console.log('Копируем стратегию в буфер обмена...');
2050
2051 try {
2052 await navigator.clipboard.writeText(currentStrategyCode);
2053 console.log('✓ Стратегия скопирована через navigator.clipboard');
2054 } catch {
2055 console.log('navigator.clipboard не сработал, пробуем GM.setClipboard...');
2056 try {
2057 await GM.setClipboard(currentStrategyCode);
2058 console.log('✓ Стратегия скопирована через GM.setClipboard');
2059 } catch (e2) {
2060 console.error('Ошибка копирования через GM.setClipboard:', e2.message);
2061 }
2062 }
2063
2064 await wait(500);
2065
2066 insertButton.click();
2067 console.log('Клик на "Вставить стратегию" выполнен');
2068 await wait(500);
2069
2070 console.log('✓ Стратегия вставлена из буфера обмена');
2071 await wait(500);
2072
2073 // Шаг 5: Находим поле "Желаемое значение" и вставляем результат
2074 showStatus('Заполнение поля...');
2075
2076 const inputLabels = Array.from(document.querySelectorAll('.MuiInputLabel-root'));
2077 let targetInput = null;
2078
2079 for (const label of inputLabels) {
2080 const labelText = label.textContent;
2081 if (labelText.includes('Желаемое значение За корзину')) {
2082 const inputId = label.getAttribute('for');
2083 if (inputId) {
2084 targetInput = document.getElementById(inputId);
2085 console.log(`Найдено поле: ${labelText}, id: ${inputId}`);
2086 break;
2087 }
2088 }
2089 }
2090
2091 if (!targetInput) {
2092 const inputs = document.querySelectorAll('input[type="number"]');
2093 for (const input of inputs) {
2094 const name = input.getAttribute('name') || '';
2095 if (name.includes('rules') && name.includes('value')) {
2096 targetInput = input;
2097 break;
2098 }
2099 }
2100 }
2101
2102 if (!targetInput) {
2103 showStatus('Ошибка: поле для ввода не найдено', true);
2104 console.error('Не удалось найти поле для ввода значения');
2105 await handleStrategyError();
2106 return;
2107 }
2108
2109 targetInput.focus();
2110 targetInput.value = cartCostRounded.toString();
2111
2112 const inputEvent = new Event('input', { bubbles: true });
2113 const changeEvent = new Event('change', { bubbles: true });
2114 targetInput.dispatchEvent(inputEvent);
2115 targetInput.dispatchEvent(changeEvent);
2116
2117 console.log(`Значение ${cartCostRounded} установлено в поле ${targetInput.id}`);
2118 await wait(500);
2119
2120 // Шаг 6: Нажимаем "Сохранить"
2121 showStatus('Сохранение...');
2122 const saveButtons = document.querySelectorAll('button');
2123 let saveButton = null;
2124
2125 for (const btn of saveButtons) {
2126 if (btn.textContent.trim() === 'Сохранить') {
2127 saveButton = btn;
2128 break;
2129 }
2130 }
2131
2132 if (!saveButton) {
2133 showStatus('Ошибка: кнопка "Сохранить" не найдена', true);
2134 await handleStrategyError();
2135 return;
2136 }
2137
2138 saveButton.click();
2139 console.log('Клик на "Сохранить" выполнен');
2140 await wait(500);
2141
2142 showStatus(`✅ Готово! Стоимость корзины: ${cartCostRounded} ₽`);
2143 console.log('Стратегия успешно установлена');
2144
2145 // Шаг 7: Работа с кластерами
2146 // Проверяем чекбоксы из обеих панелей
2147 const applyToClusters = document.getElementById('apply-to-clusters');
2148 const bulkApplyToClusters = document.getElementById('bulk-apply-to-clusters');
2149 const shouldApplyToClusters = applyToClusters ? applyToClusters.checked :
2150 (bulkApplyToClusters ? bulkApplyToClusters.checked : true);
2151
2152 if (!shouldApplyToClusters) {
2153 console.log('Применение стратегии к кластерам отключено');
2154 showStatus('✅ Готово! Кластеры пропущены');
2155
2156 // Проверяем, идет ли массовая обработка
2157 const savedDRR = await GM.getValue('bulkProcessingDRR', null);
2158 if (savedDRR !== null) {
2159 console.log('Массовая обработка: переход к следующей кампании');
2160
2161 // Обновляем счетчик ПЕРЕД переходом
2162 const currentIndex = await GM.getValue('bulkCurrentIndex', 0);
2163 const nextIndex = currentIndex + 1;
2164 await GM.setValue('bulkCurrentIndex', nextIndex);
2165 console.log(`✅ Кампания ${nextIndex} обработана успешно`);
2166
2167 await wait(2000);
2168
2169 const campaignsJson = await GM.getValue('bulkCampaigns', null);
2170 const totalCampaigns = await GM.getValue('bulkTotalCampaigns', 0);
2171
2172 if (campaignsJson) {
2173 const campaigns = JSON.parse(campaignsJson);
2174
2175 console.log(`Обработано кампаний: ${nextIndex} из ${totalCampaigns}`);
2176
2177 if (nextIndex < campaigns.length) {
2178 console.log(`Переходим к кампании ${nextIndex + 1}: ${campaigns[nextIndex]}`);
2179 // Просто перенаправляем текущую вкладку на следующую кампанию
2180 window.location.href = campaigns[nextIndex];
2181 } else {
2182 console.log('Все кампании обработаны');
2183 await GM.deleteValue('bulkProcessingDRR');
2184 await GM.deleteValue('bulkCampaigns');
2185 await GM.deleteValue('bulkCurrentIndex');
2186 await GM.deleteValue('bulkTotalCampaigns');
2187
2188 showStatus('✅ Все кампании обработаны!');
2189 alert('✅ Все кампании обработаны!');
2190 }
2191 }
2192 }
2193 return;
2194 }
2195
2196 console.log('Применение стратегии к кластерам включено');
2197
2198 // Переходим во вкладку "Кластеры"
2199 const tabs = Array.from(document.querySelectorAll('button[role="tab"]'));
2200 const clustersTab = tabs.find(tab => tab.textContent.trim() === 'Кластеры');
2201 if (!clustersTab) {
2202 console.error('Вкладка "Кластеры" не найдена');
2203 showStatus('⚠️ Вкладка "Кластеры" не найдена', true);
2204 return;
2205 }
2206
2207 clustersTab.click();
2208 console.log('Клик на "Кластеры" выполнен');
2209 await wait(1000);
2210
2211 // Ждем загрузки кластеров
2212 let loadingAttempts = 0;
2213 while (loadingAttempts < 10) {
2214 const skeleton = document.querySelector('.MuiSkeleton-root');
2215 if (!skeleton) {
2216 console.log('Кластеры загружены');
2217 break;
2218 }
2219 console.log(`Ожидание загрузки кластеров, попытка ${loadingAttempts + 1}`);
2220 await wait(500);
2221 loadingAttempts++;
2222 }
2223
2224 // СНАЧАЛА применяем обычную стратегию ко ВСЕМ кластерам
2225 showStatus('Применение обычной стратегии ко всем кластерам...');
2226 console.log('Шаг 1: Применяем обычную стратегию ко всем кластерам');
2227
2228 // Выделяем отфильтрованные кластеры
2229 showStatus('Выделение отфильтрованных кластеров...');
2230
2231 // Сначала кликаем на первый чекбокс в списке кластеров
2232 const firstClusterCheckbox = document.querySelector('tbody input[type="checkbox"]');
2233 if (firstClusterCheckbox) {
2234 firstClusterCheckbox.click();
2235 console.log('Клик на первый чекбокс кластера выполнен');
2236 await wait(500);
2237 }
2238
2239 // Теперь кликаем на общий чекбокс для выделения всех
2240 let selectAllCheckbox2 = document.querySelector('.css-1ytbthu .css-vlug8u');
2241 if (!selectAllCheckbox2) {
2242 selectAllCheckbox2 = document.querySelector('thead .css-vlug8u');
2243 }
2244 if (!selectAllCheckbox2) {
2245 const checkboxes2 = document.querySelectorAll('input[type="checkbox"]');
2246 if (checkboxes2.length > 0) {
2247 selectAllCheckbox2 = checkboxes2[0];
2248 }
2249 }
2250
2251 if (!selectAllCheckbox2) {
2252 console.error('Checkbox для отфильтрованных кластеров не найден');
2253 return;
2254 }
2255
2256 selectAllCheckbox2.click();
2257 console.log('Клик на checkbox отфильтрованных кластеров выполнен');
2258 await wait(1000);
2259
2260 // Открываем меню "Действия"
2261 showStatus('Открытие меню действий...');
2262 console.log('Ищем кнопку "Действия"...');
2263 const actionsButton = document.querySelector('.css-1rll63h');
2264 if (!actionsButton) {
2265 console.error('Кнопка "Действия" не найдена');
2266 showStatus('⚠️ Кнопка "Действия" не найдена', true);
2267 return;
2268 }
2269
2270 console.log('Кнопка "Действия" найдена, кликаем...');
2271 actionsButton.click();
2272 console.log('Клик на "Действия" выполнен');
2273 await wait(300);
2274
2275 // Кликаем на "Управление"
2276 showStatus('Открытие управления...');
2277 console.log('Ищем кнопку "Управление"...');
2278 const managementButtons = document.querySelectorAll('.css-hq58ok');
2279 console.log(`Найдено кнопок управления: ${managementButtons.length}`);
2280 if (managementButtons.length < 2) {
2281 console.error('Кнопка "Управление" не найдена');
2282 showStatus('⚠️ Кнопка "Управление" не найдена', true);
2283 return;
2284 }
2285
2286 console.log('Кликаем на вторую кнопку "Управление"...');
2287 managementButtons[1].click();
2288 console.log('Клик на "Управление" выполнен');
2289 await wait(300);
2290
2291 // Кликаем на "Стратегия"
2292 showStatus('Открытие стратегии кластеров...');
2293 console.log('Ищем вкладку "Стратегия"...');
2294 const strategyTabs = document.querySelectorAll('.css-582wun');
2295 console.log(`Найдено вкладок стратегии: ${strategyTabs.length}`);
2296 if (strategyTabs.length < 3) {
2297 console.error('Вкладка "Стратегия" не найдена');
2298 showStatus('⚠️ Вкладка "Стратегия" не найдена', true);
2299 return;
2300 }
2301
2302 console.log('Кликаем на третью вкладку "Стратегия"...');
2303 strategyTabs[2].click();
2304 console.log('Клик на "Стратегия" выполнен');
2305 await wait(300);
2306
2307 // Вставляем ОБЫЧНУЮ стратегию для всех кластеров
2308 showStatus('Вставка обычной стратегии для всех кластеров...');
2309
2310 // Ищем ВСЕ кнопки и фильтруем по тексту
2311 const allButtonsForCluster = document.querySelectorAll('button');
2312 const insertStrategyButtons = [];
2313
2314 for (const btn of allButtonsForCluster) {
2315 if (btn.textContent.includes('Вставить стратегию')) {
2316 insertStrategyButtons.push(btn);
2317 }
2318 }
2319
2320 console.log(`Найдено кнопок "Вставить стратегию" для кластеров: ${insertStrategyButtons.length}`);
2321
2322 // Берем вторую кнопку (индекс 1)
2323 let clusterInsertButton = null;
2324 if (insertStrategyButtons.length >= 2) {
2325 clusterInsertButton = insertStrategyButtons[1];
2326 console.log('Используем вторую кнопку "Вставить стратегию" для кластеров');
2327 } else if (insertStrategyButtons.length === 1) {
2328 clusterInsertButton = insertStrategyButtons[0];
2329 console.log('Найдена только одна кнопка, используем её');
2330 }
2331
2332 if (!clusterInsertButton) {
2333 console.error('Кнопка "Вставить стратегию" для кластеров не найдена');
2334 showStatus('⚠️ Кнопка вставки стратегии не найдена', true);
2335 return;
2336 }
2337
2338 // Получаем ОБЫЧНУЮ кластерную стратегию
2339 const currentClusterStrategyCode = await getCurrentClusterStrategy();
2340 console.log('Используем обычную кластерную стратегию, длина:', currentClusterStrategyCode.length);
2341
2342 // Копируем стратегию в буфер обмена
2343 try {
2344 await navigator.clipboard.writeText(currentClusterStrategyCode);
2345 console.log('✓ Обычная кластерная стратегия скопирована');
2346 } catch {
2347 await GM.setClipboard(currentClusterStrategyCode);
2348 console.log('✓ Обычная кластерная стратегия скопирована через GM');
2349 }
2350
2351 await wait(500);
2352
2353 clusterInsertButton.click();
2354 console.log('Клик на "Вставить стратегию" для всех кластеров выполнен');
2355 await wait(1000);
2356
2357 // Находим поле для ввода ставки
2358 const clusterInputLabels = Array.from(document.querySelectorAll('.MuiInputLabel-root'));
2359 let clusterTargetInput = null;
2360
2361 // Ищем второй input с меткой "Желаемое значение За корзину"
2362 let foundInputs = [];
2363 for (const label of clusterInputLabels) {
2364 const labelText = label.textContent.trim();
2365 if (labelText.includes('Желаемое значение За корзину')) {
2366 const inputId = label.getAttribute('for');
2367 if (inputId) {
2368 const input = document.getElementById(inputId);
2369 if (input) {
2370 foundInputs.push(input);
2371 console.log(`Найдено поле для кластеров: ${labelText}, id: ${inputId}`);
2372 }
2373 }
2374 }
2375 }
2376
2377 // Берем второй найденный input (индекс 1)
2378 if (foundInputs.length >= 2) {
2379 clusterTargetInput = foundInputs[1];
2380 console.log(`Используем второй input для кластеров: ${clusterTargetInput.id}`);
2381 } else if (foundInputs.length === 1) {
2382 clusterTargetInput = foundInputs[0];
2383 console.log(`Найден только один input, используем его: ${clusterTargetInput.id}`);
2384 }
2385
2386 if (clusterTargetInput) {
2387 clusterTargetInput.focus();
2388 clusterTargetInput.value = cartCostRounded.toString();
2389 clusterTargetInput.dispatchEvent(new Event('input', { bubbles: true }));
2390 clusterTargetInput.dispatchEvent(new Event('change', { bubbles: true }));
2391 console.log(`Значение ${cartCostRounded} установлено для всех кластеров в поле ${clusterTargetInput.id}`);
2392 await wait(500);
2393 }
2394
2395 // Нажимаем "Применить"
2396 showStatus('Применение обычной стратегии ко всем кластерам...');
2397 const clusterStrategyApplyButtons = document.querySelectorAll('.css-eqlbov');
2398 if (clusterStrategyApplyButtons.length >= 2) {
2399 clusterStrategyApplyButtons[1].click();
2400 console.log('Клик на "Применить" обычную стратегию выполнен');
2401 await wait(1000);
2402
2403 // Закрываем модальное окно
2404 const closeModalButtons = document.querySelectorAll('button[aria-label="Close"]');
2405 if (closeModalButtons.length > 0) {
2406 closeModalButtons[closeModalButtons.length - 1].click();
2407 console.log('Модальное окно закрыто');
2408 await wait(1000); // Ждем после закрытия модального окна, выделение снимется автоматически
2409 }
2410 }
2411
2412 // ЗАТЕМ, если включен чекбокс "Удержание места", применяем фильтры
2413 if (shouldApplyHoldPosition) {
2414 console.log('Шаг 2: Применяем фильтры и стратегию удержания места');
2415 await applyHoldPositionToFilteredClusters(cartCostRounded);
2416 }
2417
2418 // Работаем с автофильтрами
2419 await applyAutofilters();
2420
2421 showStatus(`✅ Полностью готово! Стоимость корзины: ${cartCostRounded} ₽`);
2422
2423 // Проверяем, идет ли массовая обработка
2424 const savedDRR = await GM.getValue('bulkProcessingDRR', null);
2425 if (savedDRR !== null) {
2426 console.log('Массовая обработка: переход к следующей кампании');
2427
2428 // Обновляем счетчик ПЕРЕД переходом
2429 const currentIndex = await GM.getValue('bulkCurrentIndex', 0);
2430 const nextIndex = currentIndex + 1;
2431 await GM.setValue('bulkCurrentIndex', nextIndex);
2432 console.log(`✅ Кампания ${nextIndex} обработана успешно`);
2433
2434 await wait(2000);
2435
2436 const campaignsJson = await GM.getValue('bulkCampaigns', null);
2437 const totalCampaigns = await GM.getValue('bulkTotalCampaigns', 0);
2438
2439 if (campaignsJson) {
2440 const campaigns = JSON.parse(campaignsJson);
2441
2442 console.log(`Обработано кампаний: ${nextIndex} из ${totalCampaigns}`);
2443
2444 if (nextIndex < campaigns.length) {
2445 console.log(`Переходим к кампании ${nextIndex + 1}: ${campaigns[nextIndex]}`);
2446 // Просто перенаправляем текущую вкладку на следующую кампанию
2447 window.location.href = campaigns[nextIndex];
2448 } else {
2449 console.log('Все кампании обработаны');
2450 await GM.deleteValue('bulkProcessingDRR');
2451 await GM.deleteValue('bulkCampaigns');
2452 await GM.deleteValue('bulkCurrentIndex');
2453 await GM.deleteValue('bulkTotalCampaigns');
2454
2455 showStatus('✅ Все кампании обработаны!');
2456 alert('✅ Все кампании обработаны!');
2457 }
2458 }
2459 }
2460
2461 } catch (error) {
2462 console.error('Ошибка при выполнении стратегии:', error);
2463 showStatus(`Ошибка: ${error.message}`, true);
2464 await handleStrategyError();
2465 }
2466 }
2467
2468 // Функция для применения стратегии удержания места к отфильтрованным кластерам
2469 async function applyHoldPositionToFilteredClusters(cartCostRounded) {
2470 showStatus('Настройка фильтра "Удержание места"...');
2471 await wait(1500);
2472
2473 // Выбираем дату "За 7 дней"
2474 showStatus('Выбор даты "За 7 дней"...');
2475 const jt7z00Elements = document.querySelectorAll('.css-jt7z00');
2476
2477 if (jt7z00Elements.length >= 6) {
2478 const clusterIntervalInput = jt7z00Elements[5];
2479 Object.defineProperty(clusterIntervalInput, 'readOnly', { value: false, writable: true });
2480 clusterIntervalInput.focus();
2481 clusterIntervalInput.dispatchEvent(new Event('focusin', { bubbles: true }));
2482 clusterIntervalInput.click();
2483
2484 await wait(2000);
2485
2486 const dateButtons = document.querySelectorAll('.css-ghy8jd');
2487 if (dateButtons.length >= 2) {
2488 dateButtons[1].click();
2489 console.log('Клик на "За 7 дней" выполнен');
2490 await wait(1000);
2491
2492 const applyDateButtons = document.querySelectorAll('.css-7wa720');
2493 if (applyDateButtons.length > 0) {
2494 applyDateButtons[0].click();
2495 console.log('Клик на "Применить" дату выполнен');
2496 await wait(1500);
2497 }
2498 }
2499 }
2500
2501 // Открываем фильтры
2502 const filtersButton = document.querySelector('.css-a54mx2');
2503 if (!filtersButton) {
2504 console.error('Кнопка "Фильтры" не найдена');
2505 return;
2506 }
2507
2508 filtersButton.click();
2509 console.log('Клик на "Фильтры" выполнен');
2510 await wait(1500);
2511
2512 // Добавляем фильтр
2513 const addFilterButtons = document.querySelectorAll('.css-8h18y2');
2514 if (addFilterButtons.length < 2) {
2515 console.error('Кнопка "Добавить фильтр" не найдена');
2516 return;
2517 }
2518
2519 addFilterButtons[1].click();
2520 console.log('Клик на "Добавить фильтр" выполнен');
2521 await wait(1500);
2522
2523 // Выбираем "Среднее место в выдаче"
2524 const filterOptions = document.querySelectorAll('.css-1ytbthu');
2525 if (filterOptions.length < 8) {
2526 console.error('Опция "Среднее место в выдаче" не найдена');
2527 return;
2528 }
2529
2530 filterOptions[7].click();
2531 console.log('Клик на "Среднее место в выдаче" выполнен');
2532 await wait(1500);
2533
2534 // Очищаем фильтр
2535 const allInputsWithVse = document.querySelectorAll('input[value="Все"]');
2536 let avgPositionInput = allInputsWithVse.length >= 2 ? allInputsWithVse[1] : allInputsWithVse[0];
2537
2538 if (!avgPositionInput) {
2539 console.error('Input "Среднее место в выдаче" не найден');
2540 return;
2541 }
2542
2543 const inputContainer = avgPositionInput.closest('.MuiInputBase-root');
2544 const clearButton = inputContainer?.querySelector('button');
2545
2546 if (!clearButton) {
2547 console.error('Кнопка очистки не найдена');
2548 return;
2549 }
2550
2551 clearButton.click();
2552 console.log('Клик на кнопку очистки выполнен');
2553 await wait(1500);
2554
2555 // Выбираем "Между"
2556 const hiddenInputsForComparison = document.querySelectorAll('input.MuiSelect-nativeInput');
2557 let comparisonContainer = null;
2558
2559 for (const inp of hiddenInputsForComparison) {
2560 const container = inp.closest('.MuiFormControl-root');
2561 const label = container?.querySelector('label');
2562 if (label && label.textContent.includes('Сравнение')) {
2563 comparisonContainer = container;
2564 break;
2565 }
2566 }
2567
2568 if (!comparisonContainer) {
2569 console.error('Контейнер "Сравнение" не найден');
2570 return;
2571 }
2572
2573 const arrowButton = comparisonContainer.querySelector('button');
2574 if (!arrowButton) {
2575 console.error('Кнопка со стрелкой не найдена');
2576 return;
2577 }
2578
2579 arrowButton.click();
2580 console.log('Клик на кнопку со стрелкой выполнен');
2581 await wait(1000);
2582
2583 // Выбираем "Между"
2584 const options = document.querySelectorAll('[role="option"]');
2585 const betweenOption = Array.from(options).find(opt => opt.textContent.trim() === 'Между');
2586
2587 if (!betweenOption) {
2588 console.error('Опция "Между" не найдена');
2589 return;
2590 }
2591
2592 betweenOption.click();
2593 console.log('Клик на "Между" выполнен');
2594 await wait(1500);
2595
2596 // Устанавливаем значения "от" и "до"
2597 const allLabels = document.querySelectorAll('label');
2598 let fromInput = null;
2599 let toInput = null;
2600
2601 for (const label of allLabels) {
2602 const text = label.textContent.trim();
2603 if (text === 'Значение от') {
2604 const inputId = label.getAttribute('for');
2605 if (inputId) {
2606 fromInput = document.getElementById(inputId);
2607 }
2608 } else if (text === 'Значение до') {
2609 const inputId = label.getAttribute('for');
2610 if (inputId) {
2611 toInput = document.getElementById(inputId);
2612 }
2613 }
2614 }
2615
2616 if (!fromInput || !toInput) {
2617 console.error('Поля "от" и "до" не найдены');
2618 return;
2619 }
2620
2621 // Устанавливаем "от" = 1
2622 fromInput.focus();
2623 fromInput.value = '1';
2624 fromInput.dispatchEvent(new Event('input', { bubbles: true }));
2625 fromInput.dispatchEvent(new Event('change', { bubbles: true }));
2626 console.log('Установлено значение "от" = 1');
2627 await wait(500);
2628
2629 // Устанавливаем "до" = 4
2630 toInput.focus();
2631 toInput.value = '4';
2632 toInput.dispatchEvent(new Event('input', { bubbles: true }));
2633 toInput.dispatchEvent(new Event('change', { bubbles: true }));
2634 console.log('Установлено значение "до" = 4');
2635 await wait(1000);
2636
2637 // Выделяем отфильтрованные кластеры
2638 showStatus('Выделение отфильтрованных кластеров...');
2639
2640 // Сначала кликаем на первый чекбокс в списке кластеров
2641 const firstClusterCheckbox = document.querySelector('tbody input[type="checkbox"]');
2642 if (firstClusterCheckbox) {
2643 firstClusterCheckbox.click();
2644 console.log('Клик на первый чекбокс кластера выполнен');
2645 await wait(500);
2646 }
2647
2648 // Теперь кликаем на общий чекбокс для выделения всех
2649 let selectAllCheckbox2 = document.querySelector('.css-1ytbthu .css-vlug8u');
2650 if (!selectAllCheckbox2) {
2651 selectAllCheckbox2 = document.querySelector('thead .css-vlug8u');
2652 }
2653 if (!selectAllCheckbox2) {
2654 const checkboxes2 = document.querySelectorAll('input[type="checkbox"]');
2655 if (checkboxes2.length > 0) {
2656 selectAllCheckbox2 = checkboxes2[0];
2657 }
2658 }
2659
2660 if (!selectAllCheckbox2) {
2661 console.error('Checkbox для отфильтрованных кластеров не найден');
2662 return;
2663 }
2664
2665 selectAllCheckbox2.click();
2666 console.log('Клик на checkbox отфильтрованных кластеров выполнен');
2667 await wait(1000);
2668
2669 // Открываем меню "Действия"
2670 showStatus('Открытие меню действий для отфильтрованных кластеров...');
2671 const actionsButton2 = document.querySelector('.css-1rll63h');
2672 if (!actionsButton2) {
2673 console.error('Кнопка "Действия" не найдена');
2674 return;
2675 }
2676
2677 actionsButton2.click();
2678 await wait(300);
2679
2680 // Кликаем на "Управление"
2681 const managementButtons2 = document.querySelectorAll('.css-hq58ok');
2682 if (managementButtons2.length < 2) {
2683 console.error('Кнопка "Управление" не найдена');
2684 return;
2685 }
2686
2687 managementButtons2[1].click();
2688 await wait(300);
2689
2690 // Кликаем на "Стратегия"
2691 const strategyTabs2 = document.querySelectorAll('.css-582wun');
2692 if (strategyTabs2.length < 3) {
2693 console.error('Вкладка "Стратегия" не найдена');
2694 return;
2695 }
2696
2697 strategyTabs2[2].click();
2698 await wait(300);
2699
2700 // Вставляем стратегию УДЕРЖАНИЯ МЕСТА
2701 showStatus('Вставка стратегии удержания места...');
2702
2703 // Ищем ВСЕ кнопки и фильтруем по тексту
2704 const allButtonsForHoldPosition = document.querySelectorAll('button');
2705 const insertStrategyButtons2 = [];
2706
2707 for (const btn of allButtonsForHoldPosition) {
2708 if (btn.textContent.includes('Вставить стратегию')) {
2709 insertStrategyButtons2.push(btn);
2710 }
2711 }
2712
2713 console.log(`Найдено кнопок "Вставить стратегию" для удержания места: ${insertStrategyButtons2.length}`);
2714
2715 // Берем вторую кнопку (индекс 1)
2716 let clusterInsertButton2 = null;
2717 if (insertStrategyButtons2.length >= 2) {
2718 clusterInsertButton2 = insertStrategyButtons2[1];
2719 console.log('Используем вторую кнопку "Вставить стратегию" для отфильтрованных кластеров');
2720 } else if (insertStrategyButtons2.length === 1) {
2721 clusterInsertButton2 = insertStrategyButtons2[0];
2722 console.log('Найдена только одна кнопка, используем её');
2723 }
2724
2725 if (!clusterInsertButton2) {
2726 console.error('Кнопка "Вставить стратегию" для отфильтрованных кластеров не найдена');
2727 return;
2728 }
2729
2730 // Получаем стратегию УДЕРЖАНИЯ МЕСТА
2731 const holdPositionClusterStrategyCode = await getHoldPositionClusterStrategy();
2732 console.log('Используем стратегию удержания места, длина:', holdPositionClusterStrategyCode.length);
2733
2734 try {
2735 await navigator.clipboard.writeText(holdPositionClusterStrategyCode);
2736 console.log('✓ Стратегия удержания места скопирована');
2737 } catch {
2738 await GM.setClipboard(holdPositionClusterStrategyCode);
2739 console.log('✓ Стратегия удержания места скопирована через GM');
2740 }
2741
2742 await wait(500);
2743
2744 clusterInsertButton2.click();
2745 console.log('Клик на "Вставить стратегию" удержания места выполнен');
2746 await wait(1000);
2747
2748 // Находим поле для ввода ставки
2749 const clusterInputLabels2 = Array.from(document.querySelectorAll('.MuiInputLabel-root'));
2750 let clusterTargetInput2 = null;
2751
2752 // Ищем второй input с меткой "Желаемое значение За корзину"
2753 let foundInputs2 = [];
2754 for (const label of clusterInputLabels2) {
2755 const labelText = label.textContent.trim();
2756 if (labelText.includes('Желаемое значение За корзину')) {
2757 const inputId = label.getAttribute('for');
2758 if (inputId) {
2759 const input = document.getElementById(inputId);
2760 if (input) {
2761 foundInputs2.push(input);
2762 console.log(`Найдено поле для отфильтрованных кластеров: ${labelText}, id: ${inputId}`);
2763 }
2764 }
2765 }
2766 }
2767
2768 // Берем второй найденный input (индекс 1)
2769 if (foundInputs2.length >= 2) {
2770 clusterTargetInput2 = foundInputs2[1];
2771 console.log(`Используем второй input для отфильтрованных кластеров: ${clusterTargetInput2.id}`);
2772 } else if (foundInputs2.length === 1) {
2773 clusterTargetInput2 = foundInputs2[0];
2774 console.log(`Найден только один input, используем его: ${clusterTargetInput2.id}`);
2775 }
2776
2777 if (!clusterTargetInput2) {
2778 const clusterInputs2 = document.querySelectorAll('input[type="number"]');
2779 for (const input of clusterInputs2) {
2780 const name = input.getAttribute('name') || '';
2781 if (name.includes('CostPerAddedToCart') || name.includes('value')) {
2782 clusterTargetInput2 = input;
2783 break;
2784 }
2785 }
2786 }
2787
2788 if (clusterTargetInput2) {
2789 clusterTargetInput2.focus();
2790 clusterTargetInput2.value = cartCostRounded.toString();
2791 clusterTargetInput2.dispatchEvent(new Event('input', { bubbles: true }));
2792 clusterTargetInput2.dispatchEvent(new Event('change', { bubbles: true }));
2793 console.log(`Значение ${cartCostRounded} установлено для отфильтрованных кластеров в поле ${clusterTargetInput2.id}`);
2794 await wait(500);
2795 }
2796
2797 // Нажимаем "Применить"
2798 showStatus('Применение стратегии удержания места...');
2799 const clusterStrategyApplyButtons2 = document.querySelectorAll('.css-eqlbov');
2800 if (clusterStrategyApplyButtons2.length >= 2) {
2801 clusterStrategyApplyButtons2[1].click();
2802 console.log('Клик на "Применить" стратегию удержания места выполнен');
2803 await wait(1000);
2804
2805 // Нажимаем "Сохранить"
2806 showStatus('Сохранение стратегии удержания места...');
2807 const saveButton = document.querySelector('.css-tn31lt');
2808 if (saveButton && saveButton.textContent.trim() === 'Сохранить') {
2809 saveButton.click();
2810 console.log('Клик на "Сохранить" стратегию удержания места выполнен');
2811 await wait(1000);
2812 } else {
2813 console.error('Кнопка "Сохранить" для стратегии удержания места не найдена');
2814 }
2815
2816 // Закрываем модальное окно
2817 const closeModalButtons2 = document.querySelectorAll('button[aria-label="Close"]');
2818 if (closeModalButtons2.length > 0) {
2819 closeModalButtons2[closeModalButtons2.length - 1].click();
2820 console.log('Модальное окно закрыто');
2821 await wait(1000);
2822 }
2823 }
2824 }
2825
2826 // Функция для применения автофильтров
2827 async function applyAutofilters() {
2828 showStatus('Открытие автофильтров...');
2829
2830 const autofilterAccordion = document.getElementById('bidder-constructor');
2831 if (!autofilterAccordion) {
2832 console.error('Аккордеон "Автофильтры" не найден');
2833 return;
2834 }
2835
2836 const accordionButton = autofilterAccordion.querySelector('button[aria-expanded]');
2837 const isExpanded = accordionButton && accordionButton.getAttribute('aria-expanded') === 'true';
2838
2839 if (!isExpanded && accordionButton) {
2840 accordionButton.click();
2841 console.log('Аккордеон "Автофильтры" раскрыт');
2842 await wait(1000);
2843 }
2844
2845 // Кликаем на "Шаблоны"
2846 showStatus('Открытие шаблонов...');
2847 const templatesButtons = autofilterAccordion.querySelectorAll('button');
2848 let templateButton = null;
2849
2850 for (const btn of templatesButtons) {
2851 if (btn.textContent.trim() === 'Шаблоны') {
2852 templateButton = btn;
2853 break;
2854 }
2855 }
2856
2857 if (!templateButton) {
2858 console.error('Кнопка "Шаблоны" не найдена');
2859 return;
2860 }
2861
2862 templateButton.click();
2863 console.log('Кнопка "Шаблоны" выбрана');
2864 await wait(500);
2865
2866 // Выбираем "Расширение"
2867 showStatus('Выбор шаблона "Расширение"...');
2868 const allElements = document.querySelectorAll('div, button, span, p');
2869 let extensionTemplate = null;
2870
2871 for (const el of allElements) {
2872 const text = el.textContent.trim();
2873 if (text === 'Расширение') {
2874 extensionTemplate = el;
2875 break;
2876 }
2877 }
2878
2879 if (!extensionTemplate) {
2880 console.error('Шаблон "Расширение" не найден');
2881 return;
2882 }
2883
2884 extensionTemplate.click();
2885 console.log('Шаблон "Расширение" выбран');
2886 await wait(500);
2887
2888 // Сохраняем
2889 showStatus('Сохранение автофильтров...');
2890 const saveButtonsAfterTemplate = document.querySelectorAll('button');
2891 let saveAutofilterButton = null;
2892
2893 for (const btn of saveButtonsAfterTemplate) {
2894 if (btn.textContent.trim() === 'Сохранить') {
2895 saveAutofilterButton = btn;
2896 break;
2897 }
2898 }
2899
2900 if (!saveAutofilterButton) {
2901 console.error('Кнопка "Сохранить" не найдена');
2902 return;
2903 }
2904
2905 saveAutofilterButton.click();
2906 console.log('Клик на "Сохранить" автофильтры выполнен');
2907 await wait(1000);
2908
2909 // Применяем автофильтры вручную
2910 showStatus('Применение автофильтров вручную...');
2911 const applyButtonsAfterSave = document.querySelectorAll('button');
2912 let applyAutofilterButton = null;
2913
2914 for (const btn of applyButtonsAfterSave) {
2915 if (btn.textContent.trim() === 'Применить автофильтры вручную') {
2916 applyAutofilterButton = btn;
2917 break;
2918 }
2919 }
2920
2921 if (!applyAutofilterButton) {
2922 console.error('Кнопка "Применить автофильтры вручную" не найдена');
2923 return;
2924 }
2925
2926 applyAutofilterButton.click();
2927 console.log('Клик на "Применить автофильтры вручную" выполнен');
2928 await wait(1000);
2929 }
2930
2931 // Функция для обработки ошибок и перехода к следующей кампании
2932 async function handleStrategyError() {
2933 const savedDRR = await GM.getValue('bulkProcessingDRR', null);
2934 if (savedDRR !== null) {
2935 console.log('Обнаружена ошибка при массовой обработке, ждем 10 секунд');
2936 showStatus('⚠️ Ошибка! Переход к следующей кампании через 10 секунд...', true);
2937
2938 for (let i = 10; i > 0; i--) {
2939 showStatus(`⚠️ Ошибка! Переход к следующей через ${i} сек...`, true);
2940 await wait(1000);
2941 }
2942
2943 const campaignsJson = await GM.getValue('bulkCampaigns', null);
2944 const currentIndex = await GM.getValue('bulkCurrentIndex', 0);
2945 const totalCampaigns = await GM.getValue('bulkTotalCampaigns', 0);
2946
2947 if (campaignsJson) {
2948 const campaigns = JSON.parse(campaignsJson);
2949 const nextIndex = currentIndex + 1;
2950
2951 console.log(`Обработано кампаний: ${nextIndex} из ${totalCampaigns}`);
2952
2953 if (nextIndex < campaigns.length) {
2954 await GM.setValue('bulkCurrentIndex', nextIndex);
2955 console.log(`Переходим к кампании ${nextIndex + 1}: ${campaigns[nextIndex]}`);
2956 // Просто перенаправляем текущую вкладку на следующую кампанию
2957 window.location.href = campaigns[nextIndex];
2958 } else {
2959 console.log('Все кампании обработаны');
2960 await GM.deleteValue('bulkProcessingDRR');
2961 await GM.deleteValue('bulkCampaigns');
2962 await GM.deleteValue('bulkCurrentIndex');
2963 await GM.deleteValue('bulkTotalCampaigns');
2964
2965 showStatus('✅ Все кампании обработаны!');
2966 alert('✅ Все кампании обработаны!');
2967 }
2968 }
2969 }
2970 }
2971
2972 // Инициализация
2973 function init() {
2974 console.log('Инициализация расширения');
2975
2976 if (window.location.href.includes('/advert/campaigns')) {
2977 console.log('Страница списка кампаний обнаружена');
2978 setTimeout(() => {
2979 if (document.body) {
2980 createBulkUI();
2981 } else {
2982 console.error('Body не найден');
2983 }
2984 }, 1000);
2985 } else if (window.location.href.includes('/campaigns/auto-campaigns/') && window.location.href.includes('/campaign')) {
2986 console.log('Страница кампании обнаружена');
2987 setTimeout(() => {
2988 if (document.body) {
2989 createUI();
2990 } else {
2991 console.error('Body не найден');
2992 }
2993 }, 1000);
2994 } else {
2995 console.log('Не на странице кампании, UI не создается');
2996 }
2997 }
2998
2999 // Запускаем инициализацию
3000 init();
3001
3002})();