Автоматическая установка стратегии рекламной кампании на основе статистики
Size
144.0 KB
Version
1.8.145
Created
Mar 27, 2026
Updated
20 days ago
1// ==UserScript==
2// @name MP Manager WB Cluster Auto Strategy Setter
3// @description Автоматическая установка стратегии рекламной кампании на основе статистики
4// @version 1.8.145
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 selectAllCheckbox2 = document.querySelector('.css-9nib29 input.PrivateSwitchBase-input.css-j8yymo');
2233
2234 if (!selectAllCheckbox2) {
2235 console.error('Checkbox для выделения всех кластеров не найден');
2236 return;
2237 }
2238
2239 selectAllCheckbox2.click();
2240 console.log('Клик на checkbox для выделения всех кластеров выполнен');
2241 await wait(1000);
2242
2243 // Открываем меню "Действия"
2244 showStatus('Открытие меню действий...');
2245 console.log('Ищем кнопку "Действия"...');
2246 const actionsButton = document.querySelector('.css-1rll63h');
2247 if (!actionsButton) {
2248 console.error('Кнопка "Действия" не найдена');
2249 showStatus('⚠️ Кнопка "Действия" не найдена', true);
2250 return;
2251 }
2252
2253 console.log('Кнопка "Действия" найдена, кликаем...');
2254 actionsButton.click();
2255 console.log('Клик на "Действия" выполнен');
2256 await wait(300);
2257
2258 // Кликаем на "Управление"
2259 showStatus('Открытие управления...');
2260 console.log('Ищем кнопку "Управление"...');
2261 const managementButtons = document.querySelectorAll('.css-86fwf5');
2262 console.log(`Найдено кнопок управления: ${managementButtons.length}`);
2263
2264 let managementButton = null;
2265 for (const btn of managementButtons) {
2266 if (btn.textContent.includes('Управление')) {
2267 managementButton = btn;
2268 break;
2269 }
2270 }
2271
2272 if (!managementButton) {
2273 console.error('Кнопка "Управление" не найдена');
2274 showStatus('⚠️ Кнопка "Управление" не найдена', true);
2275 return;
2276 }
2277
2278 console.log('Кликаем на кнопку "Управление"...');
2279 managementButton.click();
2280 console.log('Клик на "Управление" выполнен');
2281 await wait(300);
2282
2283 // Кликаем на "Стратегия"
2284 showStatus('Открытие стратегии кластеров...');
2285 console.log('Ищем вкладку "Стратегия"...');
2286 const strategyTabs = document.querySelectorAll('.css-euoluq');
2287 console.log(`Найдено вкладок стратегии: ${strategyTabs.length}`);
2288 if (strategyTabs.length < 3) {
2289 console.error('Вкладка "Стратегия" не найдена');
2290 showStatus('⚠️ Вкладка "Стратегия" не найдена', true);
2291 return;
2292 }
2293
2294 console.log('Кликаем на третью вкладку "Стратегия"...');
2295 strategyTabs[2].click();
2296 console.log('Клик на "Стратегия" выполнен');
2297 await wait(300);
2298
2299 // Вставляем ОБЫЧНУЮ стратегию для всех кластеров
2300 showStatus('Вставка обычной стратегии для всех кластеров...');
2301
2302 // Ищем ВСЕ кнопки и фильтруем по тексту
2303 const allButtonsForCluster = document.querySelectorAll('button');
2304 const insertStrategyButtons = [];
2305
2306 for (const btn of allButtonsForCluster) {
2307 if (btn.textContent.includes('Вставить стратегию')) {
2308 insertStrategyButtons.push(btn);
2309 }
2310 }
2311
2312 console.log(`Найдено кнопок "Вставить стратегию" для кластеров: ${insertStrategyButtons.length}`);
2313
2314 // Берем вторую кнопку (индекс 1)
2315 let clusterInsertButton = null;
2316 if (insertStrategyButtons.length >= 2) {
2317 clusterInsertButton = insertStrategyButtons[1];
2318 console.log('Используем вторую кнопку "Вставить стратегию" для кластеров');
2319 } else if (insertStrategyButtons.length === 1) {
2320 clusterInsertButton = insertStrategyButtons[0];
2321 console.log('Найдена только одна кнопка, используем её');
2322 }
2323
2324 if (!clusterInsertButton) {
2325 console.error('Кнопка "Вставить стратегию" для кластеров не найдена');
2326 showStatus('⚠️ Кнопка вставки стратегии не найдена', true);
2327 return;
2328 }
2329
2330 // Получаем ОБЫЧНУЮ кластерную стратегию
2331 const currentClusterStrategyCode = await getCurrentClusterStrategy();
2332 console.log('Используем обычную кластерную стратегию, длина:', currentClusterStrategyCode.length);
2333
2334 // Копируем стратегию в буфер обмена
2335 try {
2336 await navigator.clipboard.writeText(currentClusterStrategyCode);
2337 console.log('✓ Обычная кластерная стратегия скопирована');
2338 } catch {
2339 await GM.setClipboard(currentClusterStrategyCode);
2340 console.log('✓ Обычная кластерная стратегия скопирована через GM');
2341 }
2342
2343 await wait(500);
2344
2345 clusterInsertButton.click();
2346 console.log('Клик на "Вставить стратегию" для всех кластеров выполнен');
2347 await wait(1000);
2348
2349 // Находим поле для ввода ставки
2350 const clusterInputLabels = Array.from(document.querySelectorAll('.MuiInputLabel-root'));
2351 let clusterTargetInput = null;
2352
2353 // Ищем второй input с меткой "Желаемое значение За корзину"
2354 let foundInputs = [];
2355 for (const label of clusterInputLabels) {
2356 const labelText = label.textContent.trim();
2357 if (labelText.includes('Желаемое значение За корзину')) {
2358 const inputId = label.getAttribute('for');
2359 if (inputId) {
2360 const input = document.getElementById(inputId);
2361 if (input) {
2362 foundInputs.push(input);
2363 console.log(`Найдено поле для кластеров: ${labelText}, id: ${inputId}`);
2364 }
2365 }
2366 }
2367 }
2368
2369 // Берем второй найденный input (индекс 1)
2370 if (foundInputs.length >= 2) {
2371 clusterTargetInput = foundInputs[1];
2372 console.log(`Используем второй input для кластеров: ${clusterTargetInput.id}`);
2373 } else if (foundInputs.length === 1) {
2374 clusterTargetInput = foundInputs[0];
2375 console.log(`Найден только один input, используем его: ${clusterTargetInput.id}`);
2376 }
2377
2378 if (clusterTargetInput) {
2379 clusterTargetInput.focus();
2380 clusterTargetInput.value = cartCostRounded.toString();
2381 clusterTargetInput.dispatchEvent(new Event('input', { bubbles: true }));
2382 clusterTargetInput.dispatchEvent(new Event('change', { bubbles: true }));
2383 console.log(`Значение ${cartCostRounded} установлено для всех кластеров в поле ${clusterTargetInput.id}`);
2384 await wait(500);
2385 }
2386
2387 // Нажимаем "Применить"
2388 showStatus('Применение обычной стратегии ко всем кластерам...');
2389 const clusterStrategyApplyButtons = document.querySelectorAll('.css-eqlbov');
2390 if (clusterStrategyApplyButtons.length >= 2) {
2391 clusterStrategyApplyButtons[1].click();
2392 console.log('Клик на "Применить" обычную стратегию выполнен');
2393 await wait(1000);
2394
2395 // Закрываем модальное окно
2396 const closeModalButtons = document.querySelectorAll('button[aria-label="Close"]');
2397 if (closeModalButtons.length > 0) {
2398 closeModalButtons[closeModalButtons.length - 1].click();
2399 console.log('Модальное окно закрыто');
2400 await wait(1000); // Ждем после закрытия модального окна, выделение снимется автоматически
2401 }
2402 }
2403
2404 // ЗАТЕМ, если включен чекбокс "Удержание места", применяем фильтры
2405 if (shouldApplyHoldPosition) {
2406 console.log('Шаг 2: Применяем фильтры и стратегию удержания места');
2407 await applyHoldPositionToFilteredClusters(cartCostRounded);
2408 }
2409
2410 // Работаем с автофильтрами
2411 await applyAutofilters();
2412
2413 showStatus(`✅ Полностью готово! Стоимость корзины: ${cartCostRounded} ₽`);
2414
2415 // Проверяем, идет ли массовая обработка
2416 const savedDRR = await GM.getValue('bulkProcessingDRR', null);
2417 if (savedDRR !== null) {
2418 console.log('Массовая обработка: переход к следующей кампании');
2419
2420 // Обновляем счетчик ПЕРЕД переходом
2421 const currentIndex = await GM.getValue('bulkCurrentIndex', 0);
2422 const nextIndex = currentIndex + 1;
2423 await GM.setValue('bulkCurrentIndex', nextIndex);
2424 console.log(`✅ Кампания ${nextIndex} обработана успешно`);
2425
2426 await wait(2000);
2427
2428 const campaignsJson = await GM.getValue('bulkCampaigns', null);
2429 const totalCampaigns = await GM.getValue('bulkTotalCampaigns', 0);
2430
2431 if (campaignsJson) {
2432 const campaigns = JSON.parse(campaignsJson);
2433
2434 console.log(`Обработано кампаний: ${nextIndex} из ${totalCampaigns}`);
2435
2436 if (nextIndex < campaigns.length) {
2437 console.log(`Переходим к кампании ${nextIndex + 1}: ${campaigns[nextIndex]}`);
2438 // Просто перенаправляем текущую вкладку на следующую кампанию
2439 window.location.href = campaigns[nextIndex];
2440 } else {
2441 console.log('Все кампании обработаны');
2442 await GM.deleteValue('bulkProcessingDRR');
2443 await GM.deleteValue('bulkCampaigns');
2444 await GM.deleteValue('bulkCurrentIndex');
2445 await GM.deleteValue('bulkTotalCampaigns');
2446
2447 showStatus('✅ Все кампании обработаны!');
2448 alert('✅ Все кампании обработаны!');
2449 }
2450 }
2451 }
2452
2453 } catch (error) {
2454 console.error('Ошибка при выполнении стратегии:', error);
2455 showStatus(`Ошибка: ${error.message}`, true);
2456 await handleStrategyError();
2457 }
2458 }
2459
2460 // Функция для применения стратегии удержания места к отфильтрованным кластерам
2461 async function applyHoldPositionToFilteredClusters(cartCostRounded) {
2462 showStatus('Настройка фильтра "Удержание места"...');
2463 await wait(1500);
2464
2465 // Выбираем дату "За 7 дней"
2466 showStatus('Выбор даты "За 7 дней"...');
2467 const jt7z00Elements = document.querySelectorAll('.css-jt7z00');
2468
2469 if (jt7z00Elements.length >= 6) {
2470 const clusterIntervalInput = jt7z00Elements[5];
2471 Object.defineProperty(clusterIntervalInput, 'readOnly', { value: false, writable: true });
2472 clusterIntervalInput.focus();
2473 clusterIntervalInput.dispatchEvent(new Event('focusin', { bubbles: true }));
2474 clusterIntervalInput.click();
2475
2476 await wait(2000);
2477
2478 const dateButtons = document.querySelectorAll('.css-ghy8jd');
2479 if (dateButtons.length >= 2) {
2480 dateButtons[1].click();
2481 console.log('Клик на "За 7 дней" выполнен');
2482 await wait(1000);
2483
2484 const applyDateButtons = document.querySelectorAll('.css-7wa720');
2485 if (applyDateButtons.length > 0) {
2486 applyDateButtons[0].click();
2487 console.log('Клик на "Применить" дату выполнен');
2488 await wait(1500);
2489 }
2490 }
2491 }
2492
2493 // Открываем фильтры
2494 const filtersButton = document.querySelector('.css-a54mx2');
2495 if (!filtersButton) {
2496 console.error('Кнопка "Фильтры" не найдена');
2497 return;
2498 }
2499
2500 filtersButton.click();
2501 console.log('Клик на "Фильтры" выполнен');
2502 await wait(1500);
2503
2504 // Добавляем фильтр
2505 const addFilterButtons = document.querySelectorAll('.css-8h18y2');
2506 if (addFilterButtons.length < 2) {
2507 console.error('Кнопка "Добавить фильтр" не найдена');
2508 return;
2509 }
2510
2511 addFilterButtons[1].click();
2512 console.log('Клик на "Добавить фильтр" выполнен');
2513 await wait(1500);
2514
2515 // Выбираем "Среднее место в выдаче"
2516 const filterOptions = document.querySelectorAll('.css-1ytbthu');
2517 if (filterOptions.length < 8) {
2518 console.error('Опция "Среднее место в выдаче" не найдена');
2519 return;
2520 }
2521
2522 filterOptions[8].click();
2523 console.log('Клик на "Среднее место в выдаче" выполнен');
2524 await wait(1500);
2525
2526 // Очищаем фильтр
2527 const allInputsWithVse = document.querySelectorAll('input[value="Все"]');
2528 let avgPositionInput = allInputsWithVse.length >= 2 ? allInputsWithVse[1] : allInputsWithVse[0];
2529
2530 if (!avgPositionInput) {
2531 console.error('Input "Среднее место в выдаче" не найден');
2532 return;
2533 }
2534
2535 const inputContainer = avgPositionInput.closest('.MuiInputBase-root');
2536 const clearButton = inputContainer?.querySelector('button');
2537
2538 if (!clearButton) {
2539 console.error('Кнопка очистки не найдена');
2540 return;
2541 }
2542
2543 clearButton.click();
2544 console.log('Клик на кнопку очистки выполнен');
2545 await wait(1500);
2546
2547 // Выбираем "Между"
2548 const hiddenInputsForComparison = document.querySelectorAll('input.MuiSelect-nativeInput');
2549 let comparisonContainer = null;
2550
2551 for (const inp of hiddenInputsForComparison) {
2552 const container = inp.closest('.MuiFormControl-root');
2553 const label = container?.querySelector('label');
2554 if (label && label.textContent.includes('Сравнение')) {
2555 comparisonContainer = container;
2556 break;
2557 }
2558 }
2559
2560 if (!comparisonContainer) {
2561 console.error('Контейнер "Сравнение" не найден');
2562 return;
2563 }
2564
2565 const arrowButton = comparisonContainer.querySelector('button');
2566 if (!arrowButton) {
2567 console.error('Кнопка со стрелкой не найдена');
2568 return;
2569 }
2570
2571 arrowButton.click();
2572 console.log('Клик на кнопку со стрелкой выполнен');
2573 await wait(1000);
2574
2575 // Выбираем "Между"
2576 const options = document.querySelectorAll('[role="option"]');
2577 const betweenOption = Array.from(options).find(opt => opt.textContent.trim() === 'Между');
2578
2579 if (!betweenOption) {
2580 console.error('Опция "Между" не найдена');
2581 return;
2582 }
2583
2584 betweenOption.click();
2585 console.log('Клик на "Между" выполнен');
2586 await wait(1500);
2587
2588 // Устанавливаем значения "от" и "до"
2589 const allLabels = document.querySelectorAll('label');
2590 let fromInput = null;
2591 let toInput = null;
2592
2593 for (const label of allLabels) {
2594 const text = label.textContent.trim();
2595 if (text === 'Значение от') {
2596 const inputId = label.getAttribute('for');
2597 if (inputId) {
2598 fromInput = document.getElementById(inputId);
2599 }
2600 } else if (text === 'Значение до') {
2601 const inputId = label.getAttribute('for');
2602 if (inputId) {
2603 toInput = document.getElementById(inputId);
2604 }
2605 }
2606 }
2607
2608 if (!fromInput || !toInput) {
2609 console.error('Поля "от" и "до" не найдены');
2610 return;
2611 }
2612
2613 // Устанавливаем "от" = 1
2614 fromInput.focus();
2615 fromInput.value = '1';
2616 fromInput.dispatchEvent(new Event('input', { bubbles: true }));
2617 fromInput.dispatchEvent(new Event('change', { bubbles: true }));
2618 console.log('Установлено значение "от" = 1');
2619 await wait(500);
2620
2621 // Устанавливаем "до" = 4
2622 toInput.focus();
2623 toInput.value = '4';
2624 toInput.dispatchEvent(new Event('input', { bubbles: true }));
2625 toInput.dispatchEvent(new Event('change', { bubbles: true }));
2626 console.log('Установлено значение "до" = 4');
2627 await wait(1000);
2628
2629 // Выделяем отфильтрованные кластеры
2630 showStatus('Выделение отфильтрованных кластеров...');
2631
2632 // Находим правильный чекбокс для выделения всех кластеров
2633 const selectAllCheckbox2 = document.querySelector('.css-9nib29 input.PrivateSwitchBase-input.css-j8yymo');
2634
2635 if (!selectAllCheckbox2) {
2636 console.error('Checkbox для выделения всех кластеров не найден');
2637 return;
2638 }
2639
2640 selectAllCheckbox2.click();
2641 console.log('Клик на checkbox для выделения всех кластеров выполнен');
2642 await wait(1000);
2643
2644 // Открываем меню "Действия"
2645 showStatus('Открытие меню действий для отфильтрованных кластеров...');
2646 const actionsButton2 = document.querySelector('.css-1rll63h');
2647 if (!actionsButton2) {
2648 console.error('Кнопка "Действия" не найдена');
2649 return;
2650 }
2651
2652 actionsButton2.click();
2653 await wait(300);
2654
2655 // Кликаем на "Управление"
2656 showStatus('Открытие управления...');
2657 console.log('Ищем кнопку "Управление"...');
2658 const managementButtons2 = document.querySelectorAll('.css-86fwf5');
2659 console.log(`Найдено кнопок управления: ${managementButtons2.length}`);
2660 if (managementButtons2.length < 2) {
2661 console.error('Кнопка "Управление" не найдена');
2662 showStatus('⚠️ Кнопка "Управление" не найдена', true);
2663 return;
2664 }
2665
2666 console.log('Кликаем на вторую кнопку "Управление"...');
2667 managementButtons2[1].click();
2668 console.log('Клик на "Управление" выполнен');
2669 await wait(300);
2670
2671 // Кликаем на "Стратегия"
2672 showStatus('Открытие стратегии кластеров...');
2673 console.log('Ищем вкладку "Стратегия"...');
2674 const strategyTabs2 = document.querySelectorAll('.css-euoluq');
2675 console.log(`Найдено вкладок стратегии: ${strategyTabs2.length}`);
2676 if (strategyTabs2.length < 3) {
2677 console.error('Вкладка "Стратегия" не найдена');
2678 showStatus('⚠️ Вкладка "Стратегия" не найдена', true);
2679 return;
2680 }
2681
2682 console.log('Кликаем на третью вкладку "Стратегия"...');
2683 strategyTabs2[2].click();
2684 console.log('Клик на "Стратегия" выполнен');
2685 await wait(300);
2686
2687 // Вставляем стратегию УДЕРЖАНИЯ МЕСТА
2688 showStatus('Вставка стратегии удержания места...');
2689
2690 // Ищем ВСЕ кнопки и фильтруем по тексту
2691 const allButtonsForHoldPosition = document.querySelectorAll('button');
2692 const insertStrategyButtons2 = [];
2693
2694 for (const btn of allButtonsForHoldPosition) {
2695 if (btn.textContent.includes('Вставить стратегию')) {
2696 insertStrategyButtons2.push(btn);
2697 }
2698 }
2699
2700 console.log(`Найдено кнопок "Вставить стратегию" для удержания места: ${insertStrategyButtons2.length}`);
2701
2702 // Берем вторую кнопку (индекс 1)
2703 let clusterInsertButton2 = null;
2704 if (insertStrategyButtons2.length >= 2) {
2705 clusterInsertButton2 = insertStrategyButtons2[1];
2706 console.log('Используем вторую кнопку "Вставить стратегию" для отфильтрованных кластеров');
2707 } else if (insertStrategyButtons2.length === 1) {
2708 clusterInsertButton2 = insertStrategyButtons2[0];
2709 console.log('Найдена только одна кнопка, используем её');
2710 }
2711
2712 if (!clusterInsertButton2) {
2713 console.error('Кнопка "Вставить стратегию" для отфильтрованных кластеров не найдена');
2714 return;
2715 }
2716
2717 // Получаем стратегию УДЕРЖАНИЯ МЕСТА
2718 const holdPositionClusterStrategyCode = await getHoldPositionClusterStrategy();
2719 console.log('Используем стратегию удержания места, длина:', holdPositionClusterStrategyCode.length);
2720
2721 try {
2722 await navigator.clipboard.writeText(holdPositionClusterStrategyCode);
2723 console.log('✓ Стратегия удержания места скопирована');
2724 } catch {
2725 await GM.setClipboard(holdPositionClusterStrategyCode);
2726 console.log('✓ Стратегия удержания места скопирована через GM');
2727 }
2728
2729 await wait(500);
2730
2731 clusterInsertButton2.click();
2732 console.log('Клик на "Вставить стратегию" удержания места выполнен');
2733 await wait(1000);
2734
2735 // Находим поле для ввода ставки
2736 const clusterInputLabels2 = Array.from(document.querySelectorAll('.MuiInputLabel-root'));
2737 let clusterTargetInput2 = null;
2738
2739 // Ищем второй input с меткой "Желаемое значение За корзину"
2740 let foundInputs2 = [];
2741 for (const label of clusterInputLabels2) {
2742 const labelText = label.textContent.trim();
2743 if (labelText.includes('Желаемое значение За корзину')) {
2744 const inputId = label.getAttribute('for');
2745 if (inputId) {
2746 const input = document.getElementById(inputId);
2747 if (input) {
2748 foundInputs2.push(input);
2749 console.log(`Найдено поле для отфильтрованных кластеров: ${labelText}, id: ${inputId}`);
2750 }
2751 }
2752 }
2753 }
2754
2755 // Берем второй найденный input (индекс 1)
2756 if (foundInputs2.length >= 2) {
2757 clusterTargetInput2 = foundInputs2[1];
2758 console.log(`Используем второй input для отфильтрованных кластеров: ${clusterTargetInput2.id}`);
2759 } else if (foundInputs2.length === 1) {
2760 clusterTargetInput2 = foundInputs2[0];
2761 console.log(`Найден только один input, используем его: ${clusterTargetInput2.id}`);
2762 }
2763
2764 if (!clusterTargetInput2) {
2765 const clusterInputs2 = document.querySelectorAll('input[type="number"]');
2766 for (const input of clusterInputs2) {
2767 const name = input.getAttribute('name') || '';
2768 if (name.includes('CostPerAddedToCart') || name.includes('value')) {
2769 clusterTargetInput2 = input;
2770 break;
2771 }
2772 }
2773 }
2774
2775 if (clusterTargetInput2) {
2776 clusterTargetInput2.focus();
2777 clusterTargetInput2.value = cartCostRounded.toString();
2778 clusterTargetInput2.dispatchEvent(new Event('input', { bubbles: true }));
2779 clusterTargetInput2.dispatchEvent(new Event('change', { bubbles: true }));
2780 console.log(`Значение ${cartCostRounded} установлено для отфильтрованных кластеров в поле ${clusterTargetInput2.id}`);
2781 await wait(500);
2782 }
2783
2784 // Нажимаем "Применить"
2785 showStatus('Применение стратегии удержания места...');
2786 const clusterStrategyApplyButtons2 = document.querySelectorAll('.css-eqlbov');
2787 if (clusterStrategyApplyButtons2.length >= 2) {
2788 clusterStrategyApplyButtons2[1].click();
2789 console.log('Клик на "Применить" стратегию удержания места выполнен');
2790 await wait(1000);
2791
2792 // Нажимаем "Сохранить"
2793 showStatus('Сохранение стратегии удержания места...');
2794 const saveButton = document.querySelector('.css-tn31lt');
2795 if (saveButton && saveButton.textContent.trim() === 'Сохранить') {
2796 saveButton.click();
2797 console.log('Клик на "Сохранить" стратегию удержания места выполнен');
2798 await wait(1000);
2799 } else {
2800 console.error('Кнопка "Сохранить" для стратегии удержания места не найдена');
2801 }
2802
2803 // Закрываем модальное окно
2804 const closeModalButtons2 = document.querySelectorAll('button[aria-label="Close"]');
2805 if (closeModalButtons2.length > 0) {
2806 closeModalButtons2[closeModalButtons2.length - 1].click();
2807 console.log('Модальное окно закрыто');
2808 await wait(1000);
2809 }
2810 }
2811 }
2812
2813 // Функция для применения автофильтров
2814 async function applyAutofilters() {
2815 showStatus('Открытие автофильтров...');
2816
2817 const autofilterAccordion = document.getElementById('bidder-constructor');
2818 if (!autofilterAccordion) {
2819 console.error('Аккордеон "Автофильтры" не найден');
2820 return;
2821 }
2822
2823 const accordionButton = autofilterAccordion.querySelector('button[aria-expanded]');
2824 const isExpanded = accordionButton && accordionButton.getAttribute('aria-expanded') === 'true';
2825
2826 if (!isExpanded && accordionButton) {
2827 accordionButton.click();
2828 console.log('Аккордеон "Автофильтры" раскрыт');
2829 await wait(1000);
2830 }
2831
2832 // Кликаем на "Шаблоны"
2833 showStatus('Открытие шаблонов...');
2834 const templatesButtons = autofilterAccordion.querySelectorAll('button');
2835 let templateButton = null;
2836
2837 for (const btn of templatesButtons) {
2838 if (btn.textContent.trim() === 'Шаблоны') {
2839 templateButton = btn;
2840 break;
2841 }
2842 }
2843
2844 if (!templateButton) {
2845 console.error('Кнопка "Шаблоны" не найдена');
2846 return;
2847 }
2848
2849 templateButton.click();
2850 console.log('Кнопка "Шаблоны" выбрана');
2851 await wait(500);
2852
2853 // Выбираем "Расширение"
2854 showStatus('Выбор шаблона "Расширение"...');
2855 const allElements = document.querySelectorAll('div, button, span, p');
2856 let extensionTemplate = null;
2857
2858 for (const el of allElements) {
2859 const text = el.textContent.trim();
2860 if (text === 'Расширение') {
2861 extensionTemplate = el;
2862 break;
2863 }
2864 }
2865
2866 if (!extensionTemplate) {
2867 console.error('Шаблон "Расширение" не найден');
2868 return;
2869 }
2870
2871 extensionTemplate.click();
2872 console.log('Шаблон "Расширение" выбран');
2873 await wait(500);
2874
2875 // Сохраняем
2876 showStatus('Сохранение автофильтров...');
2877 const saveButtonsAfterTemplate = document.querySelectorAll('button');
2878 let saveAutofilterButton = null;
2879
2880 for (const btn of saveButtonsAfterTemplate) {
2881 if (btn.textContent.trim() === 'Сохранить') {
2882 saveAutofilterButton = btn;
2883 break;
2884 }
2885 }
2886
2887 if (!saveAutofilterButton) {
2888 console.error('Кнопка "Сохранить" не найдена');
2889 return;
2890 }
2891
2892 saveAutofilterButton.click();
2893 console.log('Клик на "Сохранить" автофильтры выполнен');
2894 await wait(1000);
2895
2896 // Применяем автофильтры вручную
2897 showStatus('Применение автофильтров вручную...');
2898 const applyButtonsAfterSave = document.querySelectorAll('button');
2899 let applyAutofilterButton = null;
2900
2901 for (const btn of applyButtonsAfterSave) {
2902 if (btn.textContent.trim() === 'Применить автофильтры вручную') {
2903 applyAutofilterButton = btn;
2904 break;
2905 }
2906 }
2907
2908 if (!applyAutofilterButton) {
2909 console.error('Кнопка "Применить автофильтры вручную" не найдена');
2910 return;
2911 }
2912
2913 applyAutofilterButton.click();
2914 console.log('Клик на "Применить автофильтры вручную" выполнен');
2915 await wait(1000);
2916 }
2917
2918 // Функция для обработки ошибок и перехода к следующей кампании
2919 async function handleStrategyError() {
2920 const savedDRR = await GM.getValue('bulkProcessingDRR', null);
2921 if (savedDRR !== null) {
2922 console.log('Обнаружена ошибка при массовой обработке, ждем 10 секунд');
2923 showStatus('⚠️ Ошибка! Переход к следующей кампании через 10 секунд...', true);
2924
2925 for (let i = 10; i > 0; i--) {
2926 showStatus(`⚠️ Ошибка! Переход к следующей через ${i} сек...`, true);
2927 await wait(1000);
2928 }
2929
2930 const campaignsJson = await GM.getValue('bulkCampaigns', null);
2931 const currentIndex = await GM.getValue('bulkCurrentIndex', 0);
2932 const totalCampaigns = await GM.getValue('bulkTotalCampaigns', 0);
2933
2934 if (campaignsJson) {
2935 const campaigns = JSON.parse(campaignsJson);
2936 const nextIndex = currentIndex + 1;
2937
2938 console.log(`Обработано кампаний: ${nextIndex} из ${totalCampaigns}`);
2939
2940 if (nextIndex < campaigns.length) {
2941 await GM.setValue('bulkCurrentIndex', nextIndex);
2942 console.log(`Переходим к кампании ${nextIndex + 1}: ${campaigns[nextIndex]}`);
2943 // Просто перенаправляем текущую вкладку на следующую кампанию
2944 window.location.href = campaigns[nextIndex];
2945 } else {
2946 console.log('Все кампании обработаны');
2947 await GM.deleteValue('bulkProcessingDRR');
2948 await GM.deleteValue('bulkCampaigns');
2949 await GM.deleteValue('bulkCurrentIndex');
2950 await GM.deleteValue('bulkTotalCampaigns');
2951
2952 showStatus('✅ Все кампании обработаны!');
2953 alert('✅ Все кампании обработаны!');
2954 }
2955 }
2956 }
2957 }
2958
2959 // Инициализация
2960 function init() {
2961 console.log('Инициализация расширения');
2962
2963 if (window.location.href.includes('/advert/campaigns')) {
2964 console.log('Страница списка кампаний обнаружена');
2965 setTimeout(() => {
2966 if (document.body) {
2967 createBulkUI();
2968 } else {
2969 console.error('Body не найден');
2970 }
2971 }, 1000);
2972 } else if (window.location.href.includes('/campaigns/auto-campaigns/') && window.location.href.includes('/campaign')) {
2973 console.log('Страница кампании обнаружена');
2974 setTimeout(() => {
2975 if (document.body) {
2976 createUI();
2977 } else {
2978 console.error('Body не найден');
2979 }
2980 }, 1000);
2981 } else {
2982 console.log('Не на странице кампании, UI не создается');
2983 }
2984 }
2985
2986 // Запускаем инициализацию
2987 init();
2988
2989})();