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