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