Автоматическая установка стратегии рекламной кампании на основе статистики
Size
109.3 KB
Version
1.8.118
Created
Feb 13, 2026
Updated
about 1 month ago
1// ==UserScript==
2// @name MP Manager Cluster Auto Strategy Setter
3// @description Автоматическая установка стратегии рекламной кампании на основе статистики
4// @version 1.8.118
5// @match https://*.app.mpmgr.ru/*
6// @icon https://app.mpmgr.ru/favicon.ico
7// @grant GM.getValue
8// @grant GM.setValue
9// @grant GM.deleteValue
10// @grant GM.openInTab
11// @grant GM.setClipboard
12// ==/UserScript==
13(function() {
14 'use strict';
15
16 console.log('MP Manager Auto Strategy Setter загружен');
17
18 // Константы
19 const STRATEGY_CODE = 'eyJjYW1wYWlnblR5cGUiOiJTZWFyY2hDYXRhbG9nIiwidmVyc2lvbiI6MiwiZGF0YSI6eyJzdGF0ZSI6IkVuYWJsZWQiLCJtb2RlIjp7InR5cGUiOiJQb3NpdGlvbiIsInBhdXNlVHlwZSI6IlJlc3VtZSJ9LCJzdHJhdGVneSI6eyJ0eXBlIjoiQ2FtcGFpZ25TdGF0cyIsIm1vZGUiOiJQbGFjZSIsImJpZE1vZGUiOiJDb3N0UGVyQ2xpY2siLCJjYW1wYWlnblN0YXRzIjp7Im1heFByaWNlIjo2MCwibWluUHJpY2UiOjEsInBsYXRmb3JtcyI6W10sImludGVydmFsIjoiV2VlayIsInJ1bGVzIjpbeyJ0eXBlIjoiQ29zdFBlckFkZGVkVG9DYXJ0IiwibW9kZSI6IlZhbHVlIiwidmFsdWUiOiIifV19fSwicGxhY2VTdHJhdGVneSI6eyJ0eXBlIjoiS2V5d29yZHMiLCJrZXl3b3JkcyI6eyJrZXl3b3JkcyI6W119fSwiaXNDbHVzdGVyIjpmYWxzZX19';
20 const CLUSTER_STRATEGY_CODE = 'eyJjYW1wYWlnblR5cGUiOiJTZWFyY2hDYXRhbG9nIiwidmVyc2lvbiI6MiwiZGF0YSI6eyJzdGF0ZSI6IkVuYWJsZWQiLCJtb2RlIjp7InR5cGUiOiJQb3NpdGlvbiIsInBhdXNlVHlwZSI6IlJlc3VtZSJ9LCJzdHJhdGVneSI6eyJ0eXBlIjoiQ2FtcGFpZ25TdGF0cyIsIm1vZGUiOiJQbGFjZSIsImJpZE1vZGUiOiJDb3N0UGVyQ2xpY2siLCJjYW1wYWlnblN0YXRzIjp7Im1heFByaWNlIjo2MCwibWluUHJpY2UiOjEsInBsYXRmb3JtcyI6W10sImludGVydmFsIjoiV2VlayIsInJ1bGVzIjpbeyJ0eXBlIjoiQ29zdFBlckFkZGVkVG9DYXJ0IiwibW9kZSI6IlZhbHVlIiwidmFsdWUiOiIifV19fSwicGxhY2VTdHJhdGVneSI6eyJ0eXBlIjoiS2V5d29yZHMiLCJrZXl3b3JkcyI6eyJrZXl3b3JkcyI6W119fSwiaXNDbHVzdGVyIjp0cnVlfX0=';
21
22 // Стратегия для удержания места (обычная)
23 const HOLD_POSITION_STRATEGY_CODE = 'eyJjYW1wYWlnblR5cGUiOiJTZWFyY2hDYXRhbG9nIiwidmVyc2lvbiI6MiwiZGF0YSI6eyJzdGF0ZSI6IkVuYWJsZWQiLCJtb2RlIjp7InR5cGUiOiJQb3NpdGlvbiIsInBhdXNlVHlwZSI6IlJlc3VtZSJ9LCJzdHJhdGVneSI6eyJ0eXBlIjoiQ2FtcGFpZ25TdGF0cyIsIm1vZGUiOiJQbGFjZSIsImJpZE1vZGUiOiJDb3N0UGVyQ2xpY2siLCJjYW1wYWlnblN0YXRzIjp7Im1heFByaWNlIjo2MCwibWluUHJpY2UiOjEsInBsYXRmb3JtcyI6W10sImludGVydmFsIjoiV2VlayIsInJ1bGVzIjpbeyJ0eXBlIjoiQ29zdFBlckFkZGVkVG9DYXJ0IiwibW9kZSI6IlZhbHVlIiwidmFsdWUiOiIifV19fSwicGxhY2VTdHJhdGVneSI6eyJ0eXBlIjoiS2V5d29yZHMiLCJrZXl3b3JkcyI6eyJrZXl3b3JkcyI6W119fSwiaXNDbHVzdGVyIjpmYWxzZX19';
24
25 // Стратегия для удержания места (кластерная)
26 const HOLD_POSITION_CLUSTER_STRATEGY_CODE = 'eyJjYW1wYWlnblR5cGUiOiJTZWFyY2hDYXRhbG9nIiwidmVyc2lvbiI6MiwiZGF0YSI6eyJzdGF0ZSI6IkVuYWJsZWQiLCJtb2RlIjp7InR5cGUiOiJQb3NpdGlvbiIsInBhdXNlVHlwZSI6IlJlc3VtZSJ9LCJzdHJhdGVneSI6eyJ0eXBlIjoiQ2FtcGFpZ25TdGF0cyIsIm1vZGUiOiJQbGFjZSIsImJpZE1vZGUiOiJDb3N0UGVyQ2xpY2siLCJjbHVzdGVyU3RhdHMiOnsibWF4QmlkIjo4MDAwLCJtaW5CaWQiOjQwMCwiaW50ZXJ2YWwiOiJUaHJlZURheXMiLCJydWxlcyI6W3sidHlwZSI6IkF2ZXJhZ2VQb3NpdGlvbiIsIm1vZGUiOiJWYWx1ZSIsInZhbHVlIjoyfSx7InR5cGUiOiJDb3N0UGVyQWRkZWRUb0NhcnQiLCJtb2RlIjoiQXZlcmFnZVZhbHVlIn1dfSwicGxhY2VTdHJhdGVneSI6eyJ0eXBlIjoiS2V5d29yZHMiLCJrZXl3b3JkcyI6eyJrZXl3b3JkcyI6W119fSwiaXNDbHVzdGVyIjp0cnVlfX0=';
27
28 // Глобальные переменные для массовой обработки
29 let isBulkProcessing = false;
30 let bulkDesiredPercentage = 30;
31 let bulkPaused = false;
32
33 // Функция для получения сохраненных стратегий
34 async function getSavedStrategies() {
35 let strategies = await GM.getValue('saved_strategies', []);
36
37 // Миграция: добавляем ID к стратегиям, у которых его нет
38 let needsMigration = false;
39 strategies = strategies.map((strategy, index) => {
40 if (!strategy.id) {
41 needsMigration = true;
42 return {
43 ...strategy,
44 id: 'strategy_migrated_' + Date.now() + '_' + index
45 };
46 }
47 return strategy;
48 });
49
50 // Сохраняем обновленные стратегии, если была миграция
51 if (needsMigration) {
52 await GM.setValue('saved_strategies', strategies);
53 console.log('Миграция стратегий выполнена: добавлены ID');
54 }
55
56 // Если нет сохраненных стратегий, добавляем стратегию по умолчанию
57 if (strategies.length === 0) {
58 return [{
59 id: 'default',
60 name: 'Стратегия по умолчанию',
61 data: STRATEGY_CODE
62 }];
63 }
64 return strategies;
65 }
66
67 // Функция для получения сохраненных стратегий для кластеров
68 async function getSavedClusterStrategies() {
69 let strategies = await GM.getValue('saved_cluster_strategies', []);
70
71 // Миграция: добавляем ID к стратегиям, у которых его нет
72 let needsMigration = false;
73 strategies = strategies.map((strategy, index) => {
74 if (!strategy.id) {
75 needsMigration = true;
76 return {
77 ...strategy,
78 id: 'cluster_strategy_migrated_' + Date.now() + '_' + index
79 };
80 }
81 return strategy;
82 });
83
84 // Сохраняем обновленные стратегии, если была миграция
85 if (needsMigration) {
86 await GM.setValue('saved_cluster_strategies', strategies);
87 console.log('Миграция кластерных стратегий выполнена: добавлены ID');
88 }
89
90 // Если нет сохраненных стратегий, добавляем стратегию по умолчанию
91 if (strategies.length === 0) {
92 return [{
93 id: 'cluster_default',
94 name: 'Кластерная стратегия по умолчанию',
95 data: CLUSTER_STRATEGY_CODE
96 }];
97 }
98 return strategies;
99 }
100
101 // Функция для получения текущей стратегии
102 async function getCurrentStrategy() {
103 const currentId = await GM.getValue('current_strategy_id', 'default');
104 const strategies = await getSavedStrategies();
105 const strategy = strategies.find(s => s.id === currentId);
106 return strategy ? strategy.data : STRATEGY_CODE;
107 }
108
109 // Функция для получения текущей кластерной стратегии
110 async function getCurrentClusterStrategy() {
111 const currentId = await GM.getValue('current_cluster_strategy_id', 'cluster_default');
112 const strategies = await getSavedClusterStrategies();
113 const strategy = strategies.find(s => s.id === currentId);
114 return strategy ? strategy.data : CLUSTER_STRATEGY_CODE;
115 }
116
117 // Функция для получения стратегии удержания места
118 async function getHoldPositionStrategy() {
119 const holdPositionId = await GM.getValue('hold_position_strategy_id', null);
120 if (holdPositionId) {
121 const strategies = await getSavedStrategies();
122 const strategy = strategies.find(s => s.id === holdPositionId);
123 if (strategy) {
124 return strategy.data;
125 }
126 }
127 // Если стратегия удержания места не установлена, используем текущую выбранную стратегию
128 const currentId = await GM.getValue('current_strategy_id', 'default');
129 if (currentId !== 'default') {
130 const strategies = await getSavedStrategies();
131 const strategy = strategies.find(s => s.id === currentId);
132 if (strategy) {
133 console.log('⚠️ Стратегия удержания места не установлена, используем текущую стратегию:', strategy.name);
134 return strategy.data;
135 }
136 }
137 // Возвращаем специальную стратегию удержания места по умолчанию
138 return HOLD_POSITION_STRATEGY_CODE;
139 }
140
141 // Функция для получения кластерной стратегии удержания места
142 async function getHoldPositionClusterStrategy() {
143 const holdPositionId = await GM.getValue('hold_position_cluster_strategy_id', null);
144 if (holdPositionId) {
145 const strategies = await getSavedClusterStrategies();
146 const strategy = strategies.find(s => s.id === holdPositionId);
147 if (strategy) {
148 return strategy.data;
149 }
150 }
151 // Возвращаем специальную кластерную стратегию удержания места по умолчанию
152 return HOLD_POSITION_CLUSTER_STRATEGY_CODE;
153 }
154
155 // Функция для создания модального окна управления стратегиями
156 function createStrategyManagementModal() {
157 return new Promise(async (resolve) => {
158 // Создаем оверлей
159 const overlay = document.createElement('div');
160 overlay.style.cssText = `
161 position: fixed;
162 top: 0;
163 left: 0;
164 width: 100%;
165 height: 100%;
166 background: rgba(0, 0, 0, 0.5);
167 display: flex;
168 justify-content: center;
169 align-items: center;
170 z-index: 10001;
171 `;
172
173 // Создаем модальное окно
174 const modal = document.createElement('div');
175 modal.style.cssText = `
176 background: white;
177 border-radius: 12px;
178 padding: 24px;
179 min-width: 500px;
180 max-width: 600px;
181 max-height: 80vh;
182 overflow-y: auto;
183 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
184 `;
185
186 // Заголовок
187 const title = document.createElement('h2');
188 title.textContent = 'Управление стратегиями';
189 title.style.cssText = `
190 margin: 0 0 20px 0;
191 font-size: 24px;
192 font-weight: bold;
193 color: #333;
194 `;
195 modal.appendChild(title);
196
197 // Контейнер для списка стратегий
198 const listContainer = document.createElement('div');
199 listContainer.style.cssText = `
200 margin-bottom: 20px;
201 max-height: 300px;
202 overflow-y: auto;
203 `;
204
205 // Функция для обновления списка
206 const updateList = async () => {
207 const currentStrategies = await getSavedStrategies();
208 const selectedStrategy = await GM.getValue('current_strategy_id', 'default');
209 const holdPositionStrategyId = await GM.getValue('hold_position_strategy_id', null);
210 listContainer.innerHTML = '';
211
212 if (currentStrategies.length === 0) {
213 const emptyMessage = document.createElement('p');
214 emptyMessage.textContent = 'Нет сохраненных стратегий. Используется стратегия по умолчанию.';
215 emptyMessage.style.cssText = `
216 color: #666;
217 font-style: italic;
218 padding: 20px;
219 text-align: center;
220 `;
221 listContainer.appendChild(emptyMessage);
222 } else {
223 currentStrategies.forEach((strategy, index) => {
224 const item = document.createElement('div');
225 const isSelected = strategy.id === selectedStrategy;
226 const isHoldPosition = strategy.id === holdPositionStrategyId;
227 item.style.cssText = `
228 display: flex;
229 justify-content: space-between;
230 align-items: center;
231 padding: 12px;
232 margin-bottom: 8px;
233 background: ${isSelected ? '#e3f2fd' : '#f5f5f5'};
234 border-radius: 8px;
235 border: ${isSelected ? '2px solid #2196f3' : '1px solid #ddd'};
236 `;
237
238 const nameSpan = document.createElement('span');
239 nameSpan.textContent = strategy.name + (isSelected ? ' ✓' : '') + (isHoldPosition ? ' 📍' : '');
240 nameSpan.style.cssText = `
241 font-size: 16px;
242 color: #333;
243 flex: 1;
244 font-weight: ${isSelected ? 'bold' : 'normal'};
245 `;
246
247 const buttonsContainer = document.createElement('div');
248 buttonsContainer.style.cssText = `
249 display: flex;
250 gap: 8px;
251 `;
252
253 // Кнопка выбора
254 if (!isSelected) {
255 const selectBtn = document.createElement('button');
256 selectBtn.textContent = '✓ Выбрать';
257 selectBtn.style.cssText = `
258 background: #2196f3;
259 color: white;
260 border: none;
261 padding: 6px 12px;
262 border-radius: 6px;
263 cursor: pointer;
264 font-size: 14px;
265 `;
266 selectBtn.onmouseover = () => selectBtn.style.background = '#1976d2';
267 selectBtn.onmouseout = () => selectBtn.style.background = '#2196f3';
268 selectBtn.onclick = async () => {
269 await GM.setValue('current_strategy_id', strategy.id);
270 console.log(`Выбрана стратегия: ${strategy.name} (ID: ${strategy.id})`);
271 await updateList();
272 };
273 buttonsContainer.appendChild(selectBtn);
274 }
275
276 // Кнопка "Удержание места"
277 const holdBtn = document.createElement('button');
278 holdBtn.textContent = isHoldPosition ? '📍 Удалить' : '📍 Удержание';
279 holdBtn.style.cssText = `
280 background: ${isHoldPosition ? '#ff9800' : '#4caf50'};
281 color: white;
282 border: none;
283 padding: 6px 12px;
284 border-radius: 6px;
285 cursor: pointer;
286 font-size: 14px;
287 `;
288 holdBtn.onmouseover = () => holdBtn.style.background = isHoldPosition ? '#f57c00' : '#45a049';
289 holdBtn.onmouseout = () => holdBtn.style.background = isHoldPosition ? '#ff9800' : '#4caf50';
290 holdBtn.onclick = async () => {
291 if (isHoldPosition) {
292 await GM.deleteValue('hold_position_strategy_id');
293 console.log('Стратегия удержания места удалена');
294 } else {
295 await GM.setValue('hold_position_strategy_id', strategy.id);
296 console.log(`Установлена стратегия удержания места: ${strategy.name} (ID: ${strategy.id})`);
297 }
298 await updateList();
299 };
300 buttonsContainer.appendChild(holdBtn);
301
302 // Кнопка удаления
303 const deleteBtn = document.createElement('button');
304 deleteBtn.textContent = '🗑️';
305 deleteBtn.style.cssText = `
306 background: #f44336;
307 color: white;
308 border: none;
309 padding: 6px 12px;
310 border-radius: 6px;
311 cursor: pointer;
312 font-size: 14px;
313 `;
314 deleteBtn.onmouseover = () => deleteBtn.style.background = '#d32f2f';
315 deleteBtn.onmouseout = () => deleteBtn.style.background = '#f44336';
316 deleteBtn.onclick = async () => {
317 if (confirm(`Удалить стратегию "${strategy.name}"?`)) {
318 currentStrategies.splice(index, 1);
319 await GM.setValue('saved_strategies', currentStrategies);
320 // Если удаляем выбранную стратегию, возвращаемся к дефолтной
321 if (isSelected) {
322 await GM.setValue('current_strategy_id', 'default');
323 }
324 // Если удаляем стратегию удержания места, очищаем её
325 if (isHoldPosition) {
326 await GM.deleteValue('hold_position_strategy_id');
327 }
328 await updateList();
329 }
330 };
331 buttonsContainer.appendChild(deleteBtn);
332
333 item.appendChild(nameSpan);
334 item.appendChild(buttonsContainer);
335 listContainer.appendChild(item);
336 });
337 }
338 };
339
340 await updateList();
341 modal.appendChild(listContainer);
342
343 // Кнопка добавления стратегии
344 const addButton = document.createElement('button');
345 addButton.textContent = '➕ Добавить новую стратегию';
346 addButton.style.cssText = `
347 width: 100%;
348 background: #4caf50;
349 color: white;
350 border: none;
351 padding: 12px;
352 border-radius: 8px;
353 cursor: pointer;
354 font-size: 16px;
355 font-weight: bold;
356 margin-bottom: 12px;
357 `;
358 addButton.onmouseover = () => addButton.style.background = '#45a049';
359 addButton.onmouseout = () => addButton.style.background = '#4caf50';
360 addButton.onclick = async () => {
361 const name = prompt('Введите название стратегии:');
362 if (!name) return;
363
364 const data = prompt('Вставьте код стратегии (скопируйте из MP Manager):');
365 if (!data) return;
366
367 const currentStrategies = await GM.getValue('saved_strategies', []);
368 const newId = 'strategy_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
369 currentStrategies.push({ id: newId, name, data });
370 await GM.setValue('saved_strategies', currentStrategies);
371 alert(`Стратегия "${name}" сохранена!`);
372 await updateList();
373 };
374 modal.appendChild(addButton);
375
376 // Кнопка закрытия
377 const closeButton = document.createElement('button');
378 closeButton.textContent = 'Закрыть';
379 closeButton.style.cssText = `
380 width: 100%;
381 background: #666;
382 color: white;
383 border: none;
384 padding: 12px;
385 border-radius: 8px;
386 cursor: pointer;
387 font-size: 16px;
388 `;
389 closeButton.onmouseover = () => closeButton.style.background = '#555';
390 closeButton.onmouseout = () => closeButton.style.background = '#666';
391 closeButton.onclick = () => {
392 document.body.removeChild(overlay);
393 resolve();
394 };
395 modal.appendChild(closeButton);
396
397 overlay.appendChild(modal);
398 document.body.appendChild(overlay);
399
400 // Закрытие по клику на оверлей
401 overlay.onclick = (e) => {
402 if (e.target === overlay) {
403 document.body.removeChild(overlay);
404 resolve();
405 }
406 };
407 });
408 }
409
410 // Функция для создания модального окна управления кластерными стратегиями
411 function createClusterStrategyManagementModal() {
412 return new Promise(async (resolve) => {
413 // Создаем оверлей
414 const overlay = document.createElement('div');
415 overlay.style.cssText = `
416 position: fixed;
417 top: 0;
418 left: 0;
419 width: 100%;
420 height: 100%;
421 background: rgba(0, 0, 0, 0.5);
422 display: flex;
423 justify-content: center;
424 align-items: center;
425 z-index: 10001;
426 `;
427
428 // Создаем модальное окно
429 const modal = document.createElement('div');
430 modal.style.cssText = `
431 background: white;
432 border-radius: 12px;
433 padding: 24px;
434 min-width: 500px;
435 max-width: 600px;
436 max-height: 80vh;
437 overflow-y: auto;
438 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
439 `;
440
441 // Заголовок
442 const title = document.createElement('h2');
443 title.textContent = 'Управление стратегиями для кластеров';
444 title.style.cssText = `
445 margin: 0 0 20px 0;
446 font-size: 24px;
447 font-weight: bold;
448 color: #333;
449 `;
450 modal.appendChild(title);
451
452 // Контейнер для списка стратегий
453 const listContainer = document.createElement('div');
454 listContainer.style.cssText = `
455 margin-bottom: 20px;
456 max-height: 300px;
457 overflow-y: auto;
458 `;
459
460 // Функция для обновления списка
461 const updateList = async () => {
462 const currentStrategies = await getSavedClusterStrategies();
463 const selectedStrategy = await GM.getValue('current_cluster_strategy_id', 'cluster_default');
464 const holdPositionStrategyId = await GM.getValue('hold_position_cluster_strategy_id', null);
465 listContainer.innerHTML = '';
466
467 if (currentStrategies.length === 0) {
468 const emptyMessage = document.createElement('p');
469 emptyMessage.textContent = 'Нет сохраненных кластерных стратегий. Используется стратегия по умолчанию.';
470 emptyMessage.style.cssText = `
471 color: #666;
472 font-style: italic;
473 padding: 20px;
474 text-align: center;
475 `;
476 listContainer.appendChild(emptyMessage);
477 } else {
478 currentStrategies.forEach((strategy, index) => {
479 const item = document.createElement('div');
480 const isSelected = strategy.id === selectedStrategy;
481 const isHoldPosition = strategy.id === holdPositionStrategyId;
482 item.style.cssText = `
483 display: flex;
484 justify-content: space-between;
485 align-items: center;
486 padding: 12px;
487 margin-bottom: 8px;
488 background: ${isSelected ? '#e3f2fd' : '#f5f5f5'};
489 border-radius: 8px;
490 border: ${isSelected ? '2px solid #2196f3' : '1px solid #ddd'};
491 `;
492
493 const nameSpan = document.createElement('span');
494 nameSpan.textContent = strategy.name + (isSelected ? ' ✓' : '') + (isHoldPosition ? ' 📍' : '');
495 nameSpan.style.cssText = `
496 font-size: 16px;
497 color: #333;
498 flex: 1;
499 font-weight: ${isSelected ? 'bold' : 'normal'};
500 `;
501
502 const buttonsContainer = document.createElement('div');
503 buttonsContainer.style.cssText = `
504 display: flex;
505 gap: 8px;
506 `;
507
508 // Кнопка выбора
509 if (!isSelected) {
510 const selectBtn = document.createElement('button');
511 selectBtn.textContent = '✓ Выбрать';
512 selectBtn.style.cssText = `
513 background: #2196f3;
514 color: white;
515 border: none;
516 padding: 6px 12px;
517 border-radius: 6px;
518 cursor: pointer;
519 font-size: 14px;
520 `;
521 selectBtn.onmouseover = () => selectBtn.style.background = '#1976d2';
522 selectBtn.onmouseout = () => selectBtn.style.background = '#2196f3';
523 selectBtn.onclick = async () => {
524 await GM.setValue('current_cluster_strategy_id', strategy.id);
525 console.log(`Выбрана кластерная стратегия: ${strategy.name} (ID: ${strategy.id})`);
526 await updateList();
527 };
528 buttonsContainer.appendChild(selectBtn);
529 }
530
531 // Кнопка "Удержание места"
532 const holdBtn = document.createElement('button');
533 holdBtn.textContent = isHoldPosition ? '📍 Удалить' : '📍 Удержание';
534 holdBtn.style.cssText = `
535 background: ${isHoldPosition ? '#ff9800' : '#4caf50'};
536 color: white;
537 border: none;
538 padding: 6px 12px;
539 border-radius: 6px;
540 cursor: pointer;
541 font-size: 14px;
542 `;
543 holdBtn.onmouseover = () => holdBtn.style.background = isHoldPosition ? '#f57c00' : '#45a049';
544 holdBtn.onmouseout = () => holdBtn.style.background = isHoldPosition ? '#ff9800' : '#4caf50';
545 holdBtn.onclick = async () => {
546 if (isHoldPosition) {
547 await GM.deleteValue('hold_position_cluster_strategy_id');
548 console.log('Кластерная стратегия удержания места удалена');
549 } else {
550 await GM.setValue('hold_position_cluster_strategy_id', strategy.id);
551 console.log(`Установлена кластерная стратегия удержания места: ${strategy.name} (ID: ${strategy.id})`);
552 }
553 await updateList();
554 };
555 buttonsContainer.appendChild(holdBtn);
556
557 // Кнопка удаления
558 const deleteBtn = document.createElement('button');
559 deleteBtn.textContent = '🗑️';
560 deleteBtn.style.cssText = `
561 background: #f44336;
562 color: white;
563 border: none;
564 padding: 6px 12px;
565 border-radius: 6px;
566 cursor: pointer;
567 font-size: 14px;
568 `;
569 deleteBtn.onmouseover = () => deleteBtn.style.background = '#d32f2f';
570 deleteBtn.onmouseout = () => deleteBtn.style.background = '#f44336';
571 deleteBtn.onclick = async () => {
572 if (confirm(`Удалить кластерную стратегию "${strategy.name}"?`)) {
573 currentStrategies.splice(index, 1);
574 await GM.setValue('saved_cluster_strategies', currentStrategies);
575 // Если удаляем выбранную стратегию, возвращаемся к дефолтной
576 if (isSelected) {
577 await GM.setValue('current_cluster_strategy_id', 'cluster_default');
578 }
579 // Если удаляем стратегию удержания места, очищаем её
580 if (isHoldPosition) {
581 await GM.deleteValue('hold_position_cluster_strategy_id');
582 }
583 await updateList();
584 }
585 };
586 buttonsContainer.appendChild(deleteBtn);
587
588 item.appendChild(nameSpan);
589 item.appendChild(buttonsContainer);
590 listContainer.appendChild(item);
591 });
592 }
593 };
594
595 await updateList();
596 modal.appendChild(listContainer);
597
598 // Кнопка добавления стратегии
599 const addButton = document.createElement('button');
600 addButton.textContent = '➕ Добавить новую кластерную стратегию';
601 addButton.style.cssText = `
602 width: 100%;
603 background: #4caf50;
604 color: white;
605 border: none;
606 padding: 12px;
607 border-radius: 8px;
608 cursor: pointer;
609 font-size: 16px;
610 font-weight: bold;
611 margin-bottom: 12px;
612 `;
613 addButton.onmouseover = () => addButton.style.background = '#45a049';
614 addButton.onmouseout = () => addButton.style.background = '#4caf50';
615 addButton.onclick = async () => {
616 const name = prompt('Введите название кластерной стратегии:');
617 if (!name) return;
618
619 const data = prompt('Вставьте код кластерной стратегии (скопируйте из MP Manager):');
620 if (!data) return;
621
622 const currentStrategies = await GM.getValue('saved_cluster_strategies', []);
623 const newId = 'cluster_strategy_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
624 currentStrategies.push({ id: newId, name, data });
625 await GM.setValue('saved_cluster_strategies', currentStrategies);
626 alert(`Кластерная стратегия "${name}" сохранена!`);
627 await updateList();
628 };
629 modal.appendChild(addButton);
630
631 // Кнопка закрытия
632 const closeButton = document.createElement('button');
633 closeButton.textContent = 'Закрыть';
634 closeButton.style.cssText = `
635 width: 100%;
636 background: #666;
637 color: white;
638 border: none;
639 padding: 12px;
640 border-radius: 8px;
641 cursor: pointer;
642 font-size: 16px;
643 `;
644 closeButton.onmouseover = () => closeButton.style.background = '#555';
645 closeButton.onmouseout = () => closeButton.style.background = '#666';
646 closeButton.onclick = () => {
647 document.body.removeChild(overlay);
648 resolve();
649 };
650 modal.appendChild(closeButton);
651
652 overlay.appendChild(modal);
653 document.body.appendChild(overlay);
654
655 // Закрытие по клику на оверлей
656 overlay.onclick = (e) => {
657 if (e.target === overlay) {
658 document.body.removeChild(overlay);
659 resolve();
660 }
661 };
662 });
663 }
664
665 // Функция для создания модального окна управления стратегиями удержания места
666 function createHoldPositionStrategyManagementModal() {
667 return new Promise(async (resolve) => {
668 // Создаем оверлей
669 const overlay = document.createElement('div');
670 overlay.style.cssText = `
671 position: fixed;
672 top: 0;
673 left: 0;
674 width: 100%;
675 height: 100%;
676 background: rgba(0, 0, 0, 0.5);
677 display: flex;
678 justify-content: center;
679 align-items: center;
680 z-index: 10001;
681 `;
682
683 // Создаем модальное окно
684 const modal = document.createElement('div');
685 modal.style.cssText = `
686 background: white;
687 border-radius: 12px;
688 padding: 24px;
689 min-width: 500px;
690 max-width: 600px;
691 max-height: 80vh;
692 overflow-y: auto;
693 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
694 `;
695
696 // Заголовок
697 const title = document.createElement('h2');
698 title.textContent = 'Стратегии удержания места';
699 title.style.cssText = `
700 margin: 0 0 20px 0;
701 font-size: 24px;
702 font-weight: bold;
703 color: #333;
704 `;
705 modal.appendChild(title);
706
707 // Описание
708 const description = document.createElement('p');
709 description.textContent = 'Здесь вы можете управлять стратегиями для обычных кампаний и кластеров, которые будут применяться при включенном чекбоксе "Удержание места".';
710 description.style.cssText = `
711 margin: 0 0 20px 0;
712 font-size: 14px;
713 color: #666;
714 `;
715 modal.appendChild(description);
716
717 // Секция для обычных стратегий
718 const normalSection = document.createElement('div');
719 normalSection.style.cssText = `
720 margin-bottom: 30px;
721 padding: 15px;
722 background: #f9f9f9;
723 border-radius: 8px;
724 `;
725
726 const normalTitle = document.createElement('h3');
727 normalTitle.textContent = '📋 Обычные кампании';
728 normalTitle.style.cssText = `
729 margin: 0 0 15px 0;
730 font-size: 18px;
731 font-weight: bold;
732 color: #333;
733 `;
734 normalSection.appendChild(normalTitle);
735
736 const normalListContainer = document.createElement('div');
737 normalListContainer.style.cssText = `
738 margin-bottom: 15px;
739 max-height: 200px;
740 overflow-y: auto;
741 `;
742
743 // Функция для обновления списка обычных стратегий
744 const updateNormalList = async () => {
745 const strategies = await getSavedStrategies();
746 const holdPositionStrategyId = await GM.getValue('hold_position_strategy_id', null);
747 normalListContainer.innerHTML = '';
748
749 if (strategies.length === 0) {
750 const emptyMessage = document.createElement('p');
751 emptyMessage.textContent = 'Нет сохраненных стратегий.';
752 emptyMessage.style.cssText = `
753 color: #666;
754 font-style: italic;
755 padding: 10px;
756 text-align: center;
757 `;
758 normalListContainer.appendChild(emptyMessage);
759 } else {
760 strategies.forEach((strategy) => {
761 const item = document.createElement('div');
762 const isHoldPosition = strategy.id === holdPositionStrategyId;
763 item.style.cssText = `
764 display: flex;
765 justify-content: space-between;
766 align-items: center;
767 padding: 10px;
768 margin-bottom: 8px;
769 background: ${isHoldPosition ? '#e8f5e9' : 'white'};
770 border-radius: 6px;
771 border: ${isHoldPosition ? '2px solid #4caf50' : '1px solid #ddd'};
772 `;
773
774 const nameSpan = document.createElement('span');
775 nameSpan.textContent = strategy.name + (isHoldPosition ? ' ✓' : '');
776 nameSpan.style.cssText = `
777 font-size: 14px;
778 color: #333;
779 flex: 1;
780 font-weight: ${isHoldPosition ? 'bold' : 'normal'};
781 `;
782
783 const selectBtn = document.createElement('button');
784 selectBtn.textContent = isHoldPosition ? '✓ Выбрана' : 'Выбрать';
785 selectBtn.disabled = isHoldPosition;
786 selectBtn.style.cssText = `
787 background: ${isHoldPosition ? '#4caf50' : '#2196f3'};
788 color: white;
789 border: none;
790 padding: 6px 12px;
791 border-radius: 6px;
792 cursor: ${isHoldPosition ? 'default' : 'pointer'};
793 font-size: 13px;
794 opacity: ${isHoldPosition ? '0.7' : '1'};
795 `;
796 if (!isHoldPosition) {
797 selectBtn.onmouseover = () => selectBtn.style.background = '#1976d2';
798 selectBtn.onmouseout = () => selectBtn.style.background = '#2196f3';
799 selectBtn.onclick = async () => {
800 await GM.setValue('hold_position_strategy_id', strategy.id);
801 console.log(`Установлена стратегия удержания места: ${strategy.name}`);
802 await updateNormalList();
803 };
804 }
805
806 item.appendChild(nameSpan);
807 item.appendChild(selectBtn);
808 normalListContainer.appendChild(item);
809 });
810 }
811 };
812
813 await updateNormalList();
814 normalSection.appendChild(normalListContainer);
815
816 const normalResetBtn = document.createElement('button');
817 normalResetBtn.textContent = '🔄 Сбросить на стандартную';
818 normalResetBtn.style.cssText = `
819 width: 100%;
820 background: #ff9800;
821 color: white;
822 border: none;
823 padding: 10px;
824 border-radius: 6px;
825 cursor: pointer;
826 font-size: 14px;
827 font-weight: 600;
828 `;
829 normalResetBtn.onmouseover = () => normalResetBtn.style.background = '#f57c00';
830 normalResetBtn.onmouseout = () => normalResetBtn.style.background = '#ff9800';
831 normalResetBtn.onclick = async () => {
832 await GM.deleteValue('hold_position_strategy_id');
833 console.log('Стратегия удержания места сброшена на стандартную');
834 await updateNormalList();
835 };
836 normalSection.appendChild(normalResetBtn);
837
838 modal.appendChild(normalSection);
839
840 // Секция для кластерных стратегий
841 const clusterSection = document.createElement('div');
842 clusterSection.style.cssText = `
843 margin-bottom: 20px;
844 padding: 15px;
845 background: #f9f9f9;
846 border-radius: 8px;
847 `;
848
849 const clusterTitle = document.createElement('h3');
850 clusterTitle.textContent = '🎯 Кластеры';
851 clusterTitle.style.cssText = `
852 margin: 0 0 15px 0;
853 font-size: 18px;
854 font-weight: bold;
855 color: #333;
856 `;
857 clusterSection.appendChild(clusterTitle);
858
859 const clusterListContainer = document.createElement('div');
860 clusterListContainer.style.cssText = `
861 margin-bottom: 15px;
862 max-height: 200px;
863 overflow-y: auto;
864 `;
865
866 // Функция для обновления списка кластерных стратегий
867 const updateClusterList = async () => {
868 const strategies = await getSavedClusterStrategies();
869 const holdPositionStrategyId = await GM.getValue('hold_position_cluster_strategy_id', null);
870 clusterListContainer.innerHTML = '';
871
872 if (strategies.length === 0) {
873 const emptyMessage = document.createElement('p');
874 emptyMessage.textContent = 'Нет сохраненных кластерных стратегий.';
875 emptyMessage.style.cssText = `
876 color: #666;
877 font-style: italic;
878 padding: 10px;
879 text-align: center;
880 `;
881 clusterListContainer.appendChild(emptyMessage);
882 } else {
883 strategies.forEach((strategy) => {
884 const item = document.createElement('div');
885 const isHoldPosition = strategy.id === holdPositionStrategyId;
886 item.style.cssText = `
887 display: flex;
888 justify-content: space-between;
889 align-items: center;
890 padding: 10px;
891 margin-bottom: 8px;
892 background: ${isHoldPosition ? '#e8f5e9' : 'white'};
893 border-radius: 6px;
894 border: ${isHoldPosition ? '2px solid #4caf50' : '1px solid #ddd'};
895 `;
896
897 const nameSpan = document.createElement('span');
898 nameSpan.textContent = strategy.name + (isHoldPosition ? ' ✓' : '');
899 nameSpan.style.cssText = `
900 font-size: 14px;
901 color: #333;
902 flex: 1;
903 font-weight: ${isHoldPosition ? 'bold' : 'normal'};
904 `;
905
906 const selectBtn = document.createElement('button');
907 selectBtn.textContent = isHoldPosition ? '✓ Выбрана' : 'Выбрать';
908 selectBtn.disabled = isHoldPosition;
909 selectBtn.style.cssText = `
910 background: ${isHoldPosition ? '#4caf50' : '#2196f3'};
911 color: white;
912 border: none;
913 padding: 6px 12px;
914 border-radius: 6px;
915 cursor: ${isHoldPosition ? 'default' : 'pointer'};
916 font-size: 13px;
917 opacity: ${isHoldPosition ? '0.7' : '1'};
918 `;
919 if (!isHoldPosition) {
920 selectBtn.onmouseover = () => selectBtn.style.background = '#1976d2';
921 selectBtn.onmouseout = () => selectBtn.style.background = '#2196f3';
922 selectBtn.onclick = async () => {
923 await GM.setValue('hold_position_cluster_strategy_id', strategy.id);
924 console.log(`Установлена кластерная стратегия удержания места: ${strategy.name}`);
925 await updateClusterList();
926 };
927 }
928
929 item.appendChild(nameSpan);
930 item.appendChild(selectBtn);
931 clusterListContainer.appendChild(item);
932 });
933 }
934 };
935
936 await updateClusterList();
937 clusterSection.appendChild(clusterListContainer);
938
939 const clusterResetBtn = document.createElement('button');
940 clusterResetBtn.textContent = '🔄 Сбросить на стандартную';
941 clusterResetBtn.style.cssText = `
942 width: 100%;
943 background: #ff9800;
944 color: white;
945 border: none;
946 padding: 10px;
947 border-radius: 6px;
948 cursor: pointer;
949 font-size: 14px;
950 font-weight: 600;
951 `;
952 clusterResetBtn.onmouseover = () => clusterResetBtn.style.background = '#f57c00';
953 clusterResetBtn.onmouseout = () => clusterResetBtn.style.background = '#ff9800';
954 clusterResetBtn.onclick = async () => {
955 await GM.deleteValue('hold_position_cluster_strategy_id');
956 console.log('Кластерная стратегия удержания места сброшена на стандартную');
957 await updateClusterList();
958 };
959 clusterSection.appendChild(clusterResetBtn);
960
961 modal.appendChild(clusterSection);
962
963 // Кнопка закрытия
964 const closeButton = document.createElement('button');
965 closeButton.textContent = 'Закрыть';
966 closeButton.style.cssText = `
967 width: 100%;
968 background: #666;
969 color: white;
970 border: none;
971 padding: 12px;
972 border-radius: 8px;
973 cursor: pointer;
974 font-size: 16px;
975 `;
976 closeButton.onmouseover = () => closeButton.style.background = '#555';
977 closeButton.onmouseout = () => closeButton.style.background = '#666';
978 closeButton.onclick = () => {
979 document.body.removeChild(overlay);
980 resolve();
981 };
982 modal.appendChild(closeButton);
983
984 overlay.appendChild(modal);
985 document.body.appendChild(overlay);
986
987 // Закрытие по клику на оверлей
988 overlay.onclick = (e) => {
989 if (e.target === overlay) {
990 document.body.removeChild(overlay);
991 resolve();
992 }
993 };
994 });
995 }
996
997 // Функция для создания UI
998 function createUI() {
999 console.log('Создание UI панели');
1000
1001 // Проверяем, есть ли сохраненный ДРР для массовой обработки
1002 GM.getValue('bulkProcessingDRR', null).then(savedDRR => {
1003 if (savedDRR !== null) {
1004 console.log(`Найден сохраненный ДРР для массовой обработки: ${savedDRR}`);
1005 // Автоматически запускаем стратегию с сохраненным ДРР
1006 setTimeout(() => {
1007 const input = document.getElementById('desired-percentage');
1008 if (input) {
1009 input.value = savedDRR;
1010 // Запускаем стратегию автоматически
1011 runStrategy();
1012 }
1013 }, 2000);
1014 }
1015 });
1016
1017 const panel = document.createElement('div');
1018 panel.id = 'auto-strategy-panel';
1019 panel.style.cssText = `
1020 position: fixed;
1021 top: 20px;
1022 right: 20px;
1023 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
1024 padding: 15px;
1025 border-radius: 12px;
1026 box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
1027 z-index: 10000;
1028 min-width: 280px;
1029 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1030 color: white;
1031 transition: all 0.3s ease;
1032 cursor: move;
1033 `;
1034
1035 panel.innerHTML = `
1036 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; cursor: pointer;" id="panel-header">
1037 <h3 style="margin: 0; font-size: 16px; font-weight: 600;">🚀 Авто-стратегия</h3>
1038 <span id="toggle-icon" style="font-size: 18px;">▼</span>
1039 </div>
1040
1041 <div id="panel-content" style="display: none;">
1042 <div style="margin-bottom: 15px;">
1043 <label style="display: block; margin-bottom: 8px; font-size: 13px; font-weight: 500;">
1044 Желаемый % рекламных расходов:
1045 </label>
1046 <input
1047 type="number"
1048 id="desired-percentage"
1049 value="30"
1050 min="1"
1051 max="100"
1052 step="0.1"
1053 style="
1054 width: 100%;
1055 padding: 10px;
1056 border: none;
1057 border-radius: 8px;
1058 font-size: 14px;
1059 box-sizing: border-box;
1060 background: rgba(255, 255, 255, 0.95);
1061 color: #333;
1062 cursor: text;
1063 "
1064 />
1065 </div>
1066
1067 <div style="margin-bottom: 15px;">
1068 <label style="display: flex; align-items: center; cursor: pointer; font-size: 13px; font-weight: 500;">
1069 <input
1070 type="checkbox"
1071 id="apply-to-clusters"
1072 checked
1073 style="
1074 margin-right: 8px;
1075 width: 18px;
1076 height: 18px;
1077 cursor: pointer;
1078 "
1079 />
1080 Применить стратегию к кластерам
1081 </label>
1082 </div>
1083
1084 <div style="margin-bottom: 15px;">
1085 <label style="display: flex; align-items: center; cursor: pointer; font-size: 13px; font-weight: 500;">
1086 <input
1087 type="checkbox"
1088 id="hold-position"
1089 checked
1090 style="
1091 margin-right: 8px;
1092 width: 18px;
1093 height: 18px;
1094 cursor: pointer;
1095 "
1096 />
1097 Удержание места
1098 </label>
1099 </div>
1100
1101 <button
1102 id="run-strategy-btn"
1103 style="
1104 width: 100%;
1105 padding: 12px;
1106 background: white;
1107 color: #667eea;
1108 border: none;
1109 border-radius: 8px;
1110 font-size: 14px;
1111 font-weight: 600;
1112 cursor: pointer;
1113 transition: all 0.3s ease;
1114 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
1115 margin-bottom: 10px;
1116 "
1117 onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 6px 16px rgba(0, 0, 0, 0.2)';"
1118 onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='0 4px 12px rgba(0, 0, 0, 0.15)';"
1119 >
1120 Запустить
1121 </button>
1122
1123 <button
1124 id="manage-strategies-btn"
1125 style="
1126 width: 100%;
1127 padding: 10px;
1128 background: rgba(255, 255, 255, 0.2);
1129 color: white;
1130 border: 1px solid rgba(255, 255, 255, 0.3);
1131 border-radius: 8px;
1132 font-size: 13px;
1133 font-weight: 600;
1134 cursor: pointer;
1135 transition: all 0.3s ease;
1136 margin-bottom: 8px;
1137 "
1138 onmouseover="this.style.background='rgba(255, 255, 255, 0.3)';"
1139 onmouseout="this.style.background='rgba(255, 255, 255, 0.2)';"
1140 >
1141 ⚙️ Управление стратегиями
1142 </button>
1143
1144 <button
1145 id="manage-cluster-strategies-btn"
1146 style="
1147 width: 100%;
1148 padding: 10px;
1149 background: rgba(255, 255, 255, 0.2);
1150 color: white;
1151 border: 1px solid rgba(255, 255, 255, 0.3);
1152 border-radius: 8px;
1153 font-size: 13px;
1154 font-weight: 600;
1155 cursor: pointer;
1156 transition: all 0.3s ease;
1157 margin-bottom: 8px;
1158 "
1159 onmouseover="this.style.background='rgba(255, 255, 255, 0.3)';"
1160 onmouseout="this.style.background='rgba(255, 255, 255, 0.2)';"
1161 >
1162 🎯 Стратегии для кластеров
1163 </button>
1164
1165 <button
1166 id="manage-hold-position-strategies-btn"
1167 style="
1168 width: 100%;
1169 padding: 10px;
1170 background: rgba(255, 255, 255, 0.2);
1171 color: white;
1172 border: 1px solid rgba(255, 255, 255, 0.3);
1173 border-radius: 8px;
1174 font-size: 13px;
1175 font-weight: 600;
1176 cursor: pointer;
1177 transition: all 0.3s ease;
1178 "
1179 onmouseover="this.style.background='rgba(255, 255, 255, 0.3)';"
1180 onmouseout="this.style.background='rgba(255, 255, 255, 0.2)';"
1181 >
1182 📍 Стратегии удержания места
1183 </button>
1184
1185 <div id="status-message" style="
1186 margin-top: 15px;
1187 padding: 10px;
1188 border-radius: 8px;
1189 font-size: 12px;
1190 background: rgba(255, 255, 255, 0.2);
1191 display: none;
1192 "></div>
1193 </div>
1194 `;
1195
1196 document.body.appendChild(panel);
1197 console.log('UI панель создана');
1198
1199 // Добавляем возможность перетаскивания
1200 makeDraggable(panel);
1201
1202 // Добавляем обработчик клика на заголовок для разворачивания/сворачивания
1203 document.getElementById('panel-header').addEventListener('click', () => {
1204 const content = document.getElementById('panel-content');
1205 const icon = document.getElementById('toggle-icon');
1206 if (content.style.display === 'none') {
1207 content.style.display = 'block';
1208 icon.textContent = '▲';
1209 } else {
1210 content.style.display = 'none';
1211 icon.textContent = '▼';
1212 }
1213 });
1214
1215 // Добавляем обработчик клика на кнопку
1216 document.getElementById('run-strategy-btn').addEventListener('click', runStrategy);
1217
1218 // Добавляем обработчик для кнопки управления стратегиями
1219 document.getElementById('manage-strategies-btn').addEventListener('click', async () => {
1220 await createStrategyManagementModal();
1221 });
1222
1223 // Добавляем обработчик для кнопки управления кластерными стратегиями
1224 document.getElementById('manage-cluster-strategies-btn').addEventListener('click', async () => {
1225 await createClusterStrategyManagementModal();
1226 });
1227
1228 // Добавляем обработчик для кнопки управления стратегиями удержания места
1229 document.getElementById('manage-hold-position-strategies-btn').addEventListener('click', async () => {
1230 await createHoldPositionStrategyManagementModal();
1231 });
1232 }
1233
1234 // Функция для создания UI панели массового изменения
1235 function createBulkUI() {
1236 console.log('Создание UI панели массового изменения');
1237
1238 const panel = document.createElement('div');
1239 panel.id = 'bulk-strategy-panel';
1240 panel.style.cssText = `
1241 position: fixed;
1242 top: 20px;
1243 right: 20px;
1244 background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
1245 padding: 15px;
1246 border-radius: 12px;
1247 box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
1248 z-index: 10000;
1249 min-width: 300px;
1250 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1251 color: white;
1252 transition: all 0.3s ease;
1253 cursor: move;
1254 `;
1255
1256 panel.innerHTML = `
1257 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; cursor: pointer;" id="bulk-panel-header">
1258 <h3 style="margin: 0; font-size: 16px; font-weight: 600;">📦 Массовое изменение</h3>
1259 <span id="bulk-toggle-icon" style="font-size: 18px;">▼</span>
1260 </div>
1261
1262 <div id="bulk-panel-content" style="display: none;">
1263 <div style="margin-bottom: 15px;">
1264 <label style="display: block; margin-bottom: 8px; font-size: 13px; font-weight: 500;">
1265 Желаемый % рекламных расходов (ДРР):
1266 </label>
1267 <input
1268 type="number"
1269 id="bulk-desired-percentage"
1270 value="30"
1271 min="1"
1272 max="100"
1273 step="0.1"
1274 style="
1275 width: 100%;
1276 padding: 10px;
1277 border: none;
1278 border-radius: 8px;
1279 font-size: 14px;
1280 box-sizing: border-box;
1281 background: rgba(255, 255, 255, 0.95);
1282 color: #333;
1283 cursor: text;
1284 "
1285 />
1286 </div>
1287
1288 <div id="bulk-campaigns-info" style="
1289 margin-bottom: 15px;
1290 padding: 10px;
1291 border-radius: 8px;
1292 font-size: 12px;
1293 background: rgba(255, 255, 255, 0.2);
1294 ">
1295 Найдено кампаний: <span id="campaigns-count">0</span>
1296 </div>
1297
1298 <button
1299 id="run-bulk-strategy-btn"
1300 style="
1301 width: 100%;
1302 padding: 12px;
1303 background: white;
1304 color: #f5576c;
1305 border: none;
1306 border-radius: 8px;
1307 font-size: 14px;
1308 font-weight: 600;
1309 cursor: pointer;
1310 transition: all 0.3s ease;
1311 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
1312 margin-bottom: 10px;
1313 "
1314 onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 6px 16px rgba(0, 0, 0, 0.2)';"
1315 onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='0 4px 12px rgba(0, 0, 0, 0.15)';"
1316 >
1317 Запустить
1318 </button>
1319
1320 <button
1321 id="bulk-manage-strategies-btn"
1322 style="
1323 width: 100%;
1324 padding: 10px;
1325 background: rgba(255, 255, 255, 0.2);
1326 color: white;
1327 border: 1px solid rgba(255, 255, 255, 0.3);
1328 border-radius: 8px;
1329 font-size: 13px;
1330 font-weight: 600;
1331 cursor: pointer;
1332 transition: all 0.3s ease;
1333 margin-bottom: 10px;
1334 "
1335 onmouseover="this.style.background='rgba(255, 255, 255, 0.3)';"
1336 onmouseout="this.style.background='rgba(255, 255, 255, 0.2)';"
1337 >
1338 ⚙️ Управление стратегиями
1339 </button>
1340
1341 <button
1342 id="bulk-manage-cluster-strategies-btn"
1343 style="
1344 width: 100%;
1345 padding: 10px;
1346 background: rgba(255, 255, 255, 0.2);
1347 color: white;
1348 border: 1px solid rgba(255, 255, 255, 0.3);
1349 border-radius: 8px;
1350 font-size: 13px;
1351 font-weight: 600;
1352 cursor: pointer;
1353 transition: all 0.3s ease;
1354 margin-bottom: 10px;
1355 "
1356 onmouseover="this.style.background='rgba(255, 255, 255, 0.3)';"
1357 onmouseout="this.style.background='rgba(255, 255, 255, 0.2)';"
1358 >
1359 🎯 Стратегии для кластеров
1360 </button>
1361
1362 <div style="margin-bottom: 15px;">
1363 <label style="display: flex; align-items: center; cursor: pointer; font-size: 13px; font-weight: 500;">
1364 <input
1365 type="checkbox"
1366 id="bulk-apply-to-clusters"
1367 checked
1368 style="
1369 margin-right: 8px;
1370 width: 18px;
1371 height: 18px;
1372 cursor: pointer;
1373 "
1374 />
1375 Применить стратегию к кластерам
1376 </label>
1377 </div>
1378
1379 <div style="margin-bottom: 15px;">
1380 <label style="display: flex; align-items: center; cursor: pointer; font-size: 13px; font-weight: 500;">
1381 <input
1382 type="checkbox"
1383 id="bulk-hold-position"
1384 checked
1385 style="
1386 margin-right: 8px;
1387 width: 18px;
1388 height: 18px;
1389 cursor: pointer;
1390 "
1391 />
1392 Удержание места
1393 </label>
1394 </div>
1395
1396 <div id="bulk-control-buttons" style="
1397 margin-top: 10px;
1398 display: none;
1399 gap: 10px;
1400 ">
1401 <button
1402 id="pause-bulk-btn"
1403 style="
1404 flex: 1;
1405 padding: 10px;
1406 background: rgba(255, 255, 255, 0.9);
1407 color: #f5576c;
1408 border: none;
1409 border-radius: 8px;
1410 font-size: 13px;
1411 font-weight: 600;
1412 cursor: pointer;
1413 transition: all 0.3s ease;
1414 "
1415 >
1416 ⏏️ Пауза
1417 </button>
1418 <button
1419 id="stop-bulk-btn"
1420 style="
1421 flex: 1;
1422 padding: 10px;
1423 background: rgba(255, 59, 48, 0.9);
1424 color: white;
1425 border: none;
1426 border-radius: 8px;
1427 font-size: 13px;
1428 font-weight: 600;
1429 cursor: pointer;
1430 transition: all 0.3s ease;
1431 "
1432 >
1433 ⏹️ Стоп
1434 </button>
1435 </div>
1436
1437 <div id="bulk-status-message" style="
1438 margin-top: 15px;
1439 padding: 10px;
1440 border-radius: 8px;
1441 font-size: 12px;
1442 background: rgba(255, 255, 255, 0.2);
1443 display: none;
1444 "></div>
1445
1446 <div id="bulk-progress" style="
1447 margin-top: 15px;
1448 display: none;
1449 ">
1450 <div style="margin-bottom: 5px; font-size: 12px;">
1451 Прогресс: <span id="bulk-progress-text">0/0</span>
1452 </div>
1453 <div style="
1454 width: 100%;
1455 height: 8px;
1456 background: rgba(255, 255, 255, 0.3);
1457 border-radius: 4px;
1458 overflow: hidden;
1459 ">
1460 <div id="bulk-progress-bar" style="
1461 width: 0%;
1462 height: 100%;
1463 background: white;
1464 transition: width 0.3s ease;
1465 "></div>
1466 </div>
1467 </div>
1468 </div>
1469 `;
1470
1471 document.body.appendChild(panel);
1472 console.log('UI панель массового изменения создана');
1473
1474 // Добавляем возможность перетаскивания
1475 makeDraggable(panel);
1476
1477 // Подсчитываем количество кампаний
1478 updateCampaignsCount();
1479
1480 // Добавляем обработчик клика на заголовок для разворачивания/сворачивания
1481 document.getElementById('bulk-panel-header').addEventListener('click', () => {
1482 const content = document.getElementById('bulk-panel-content');
1483 const icon = document.getElementById('bulk-toggle-icon');
1484 if (content.style.display === 'none') {
1485 content.style.display = 'block';
1486 icon.textContent = '▲';
1487 } else {
1488 content.style.display = 'none';
1489 icon.textContent = '▼';
1490 }
1491 });
1492
1493 // Добавляем обработчик клика на кнопку
1494 document.getElementById('run-bulk-strategy-btn').addEventListener('click', runBulkStrategy);
1495
1496 // Добавляем обработчик для кнопки управления стратегиями
1497 document.getElementById('bulk-manage-strategies-btn').addEventListener('click', async () => {
1498 await createStrategyManagementModal();
1499 });
1500
1501 // Добавляем обработчик для кнопки управления кластерными стратегиями
1502 document.getElementById('bulk-manage-cluster-strategies-btn').addEventListener('click', async () => {
1503 await createClusterStrategyManagementModal();
1504 });
1505
1506 // Добавляем обработчики для кнопок управления
1507 document.getElementById('pause-bulk-btn').addEventListener('click', pauseBulkProcessing);
1508 document.getElementById('stop-bulk-btn').addEventListener('click', stopBulkProcessing);
1509 }
1510
1511 // Функция для создания перетаскиваемого элемента
1512 function makeDraggable(element) {
1513 let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
1514 let isDragging = false;
1515
1516 element.onmousedown = dragMouseDown;
1517
1518 function dragMouseDown(e) {
1519 // Не перетаскиваем, если кликнули на input, button или другие интерактивные элементы
1520 if (e.target.tagName === 'INPUT' || e.target.tagName === 'BUTTON' || e.target.tagName === 'TEXTAREA') {
1521 return;
1522 }
1523
1524 e.preventDefault();
1525 isDragging = true;
1526 pos3 = e.clientX;
1527 pos4 = e.clientY;
1528 document.onmouseup = closeDragElement;
1529 document.onmousemove = elementDrag;
1530 }
1531
1532 function elementDrag(e) {
1533 if (!isDragging) return;
1534 pos1 = pos3 - e.clientX;
1535 pos2 = pos4 - e.clientY;
1536 pos3 = e.clientX;
1537 pos4 = e.clientY;
1538 element.style.top = (element.offsetTop - pos2) + 'px';
1539 element.style.left = (element.offsetLeft - pos1) + 'px';
1540 element.style.right = 'auto';
1541 element.style.bottom = 'auto';
1542 }
1543
1544 function closeDragElement() {
1545 isDragging = false;
1546 document.onmouseup = null;
1547 document.onmousemove = null;
1548 }
1549 }
1550
1551 // Функция для обновления количества кампаний
1552 function updateCampaignsCount() {
1553 const campaignLinks = document.querySelectorAll('a[href*="/campaigns/auto-campaigns/"][href*="/campaign"]');
1554 const count = campaignLinks.length;
1555 const countElement = document.getElementById('campaigns-count');
1556 if (countElement) {
1557 countElement.textContent = count;
1558 }
1559 console.log(`Найдено кампаний: ${count}`);
1560 }
1561
1562 // Функция для показа статуса массовой обработки
1563 function showBulkStatus(message, isError = false) {
1564 const statusDiv = document.getElementById('bulk-status-message');
1565 if (statusDiv) {
1566 statusDiv.textContent = message;
1567 statusDiv.style.display = 'block';
1568 statusDiv.style.background = isError ? 'rgba(255, 59, 48, 0.9)' : 'rgba(52, 199, 89, 0.9)';
1569 console.log(`Статус массовой обработки: ${message}`);
1570 }
1571 }
1572
1573 // Функция для обновления прогресса
1574 function updateBulkProgress(current, total) {
1575 const progressDiv = document.getElementById('bulk-progress');
1576 const progressText = document.getElementById('bulk-progress-text');
1577 const progressBar = document.getElementById('bulk-progress-bar');
1578
1579 if (progressDiv && progressText && progressBar) {
1580 progressDiv.style.display = 'block';
1581 progressText.textContent = `${current}/${total}`;
1582 const percentage = (current / total) * 100;
1583 progressBar.style.width = `${percentage}%`;
1584 }
1585 }
1586
1587 // Функция для прокрутки страницы вниз для загрузки всех кампаний
1588 async function scrollToLoadAllCampaigns() {
1589 console.log('Начинаем загрузку всех кампаний через прокрутку...');
1590
1591 // Находим контейнер с прокруткой
1592 const tableContainer = document.querySelector('.container.MuiBox-root.css-9hf803');
1593
1594 if (!tableContainer) {
1595 console.error('Контейнер таблицы не найден');
1596 return [];
1597 }
1598
1599 console.log('Контейнер найден, начинаем прокрутку...');
1600 console.log(`Высота контейнера: ${tableContainer.scrollHeight}px`);
1601
1602 // Собираем уникальные ссылки во время прокрутки
1603 const uniqueLinks = new Set();
1604
1605 // Прокручиваем контейнер постепенно, чтобы загрузить все кампании
1606 let previousLinksCount = 0;
1607 let stableCount = 0;
1608 const maxAttempts = 200; // Максимум попыток
1609 let attempts = 0;
1610 const scrollStep = 500; // Прокручиваем по 500px за раз
1611
1612 while (attempts < maxAttempts) {
1613 // Собираем ссылки на текущем шаге
1614 const currentLinks = document.querySelectorAll('a[href*="/campaigns/auto-campaigns/"][href*="/campaign"]');
1615 currentLinks.forEach(link => {
1616 uniqueLinks.add(link.href);
1617 });
1618
1619 const currentCount = uniqueLinks.size;
1620 console.log(`Загружено кампаний: ${currentCount}, прокрутка: ${tableContainer.scrollTop}/${tableContainer.scrollHeight}`);
1621
1622 // Прокручиваем контейнер постепенно
1623 tableContainer.scrollTop += scrollStep;
1624
1625 // Ждем загрузки новых элементов
1626 await wait(500);
1627
1628 // Если количество не изменилось
1629 if (currentCount === previousLinksCount) {
1630 stableCount++;
1631 // Если количество стабильно 5 раз подряд - значит все загружено
1632 if (stableCount >= 5) {
1633 console.log('Все кампании загружены');
1634 break;
1635 }
1636 } else {
1637 stableCount = 0;
1638 previousLinksCount = currentCount;
1639 }
1640
1641 // Если достигли конца контейнера
1642 if (tableContainer.scrollTop + tableContainer.clientHeight >= tableContainer.scrollHeight - 10) {
1643 console.log('Достигнут конец контейнера');
1644 // Ждем еще немного для загрузки последних элементов
1645 await wait(1000);
1646
1647 // Собираем последние ссылки
1648 const finalLinks = document.querySelectorAll('a[href*="/campaigns/auto-campaigns/"][href*="/campaign"]');
1649 finalLinks.forEach(link => {
1650 uniqueLinks.add(link.href);
1651 });
1652
1653 // Проверяем еще раз количество
1654 if (uniqueLinks.size === previousLinksCount) {
1655 break;
1656 }
1657 previousLinksCount = uniqueLinks.size;
1658 }
1659
1660 attempts++;
1661 }
1662
1663 // Преобразуем Set в массив URL (НЕ прокручиваем обратно!)
1664 const links = Array.from(uniqueLinks);
1665
1666 console.log(`Найдено кампаний: ${links.length}`);
1667 console.log(`Всего попыток прокрутки: ${attempts}`);
1668 return links;
1669 }
1670
1671 // Функция для массовой обработки кампаний
1672 async function runBulkStrategy() {
1673 if (isBulkProcessing) {
1674 showBulkStatus('Обработка уже выполняется', true);
1675 return;
1676 }
1677
1678 try {
1679 isBulkProcessing = true;
1680 console.log('Начало массовой обработки кампаний');
1681
1682 bulkDesiredPercentage = parseFloat(document.getElementById('bulk-desired-percentage').value);
1683 if (!bulkDesiredPercentage || bulkDesiredPercentage <= 0) {
1684 showBulkStatus('Ошибка: введите корректный процент', true);
1685 isBulkProcessing = false;
1686 return;
1687 }
1688 console.log(`Желаемый процент: ${bulkDesiredPercentage}%`);
1689
1690 // Сохраняем ДРР для автоматической обработки
1691 await GM.setValue('bulkProcessingDRR', bulkDesiredPercentage);
1692 console.log(`ДРР ${bulkDesiredPercentage} сохранен для массовой обработки`);
1693
1694 // Сохраняем состояние чекбоксов
1695 const bulkApplyToClusters = document.getElementById('bulk-apply-to-clusters');
1696 const bulkHoldPosition = document.getElementById('bulk-hold-position');
1697 await GM.setValue('bulkApplyToClusters', bulkApplyToClusters ? bulkApplyToClusters.checked : true);
1698 await GM.setValue('bulkHoldPosition', bulkHoldPosition ? bulkHoldPosition.checked : true);
1699 console.log(`Сохранены настройки: применить к кластерам = ${bulkApplyToClusters?.checked}, удержание места = ${bulkHoldPosition?.checked}`);
1700
1701 // Прокручиваем страницу для загрузки всех кампаний
1702 const campaignLinks = await scrollToLoadAllCampaigns();
1703
1704 if (campaignLinks.length === 0) {
1705 showBulkStatus('Ошибка: кампании не найдены', true);
1706 isBulkProcessing = false;
1707 await GM.deleteValue('bulkProcessingDRR');
1708 return;
1709 }
1710
1711 console.log(`Найдено кампаний для обработки: ${campaignLinks.length}`);
1712 showBulkStatus(`Начинаем обработку ${campaignLinks.length} кампаний...`);
1713 updateBulkProgress(0, campaignLinks.length);
1714
1715 // Сохраняем список кампаний (массив URL), текущий индекс и общее количество
1716 await GM.setValue('bulkCampaigns', JSON.stringify(campaignLinks));
1717 await GM.setValue('bulkCurrentIndex', 0);
1718 await GM.setValue('bulkTotalCampaigns', campaignLinks.length);
1719
1720 // Показываем кнопки управления
1721 const controlButtons = document.getElementById('bulk-control-buttons');
1722 if (controlButtons) {
1723 controlButtons.style.display = 'flex';
1724 }
1725
1726 // Открываем первую кампанию в новой вкладке через window.open
1727 if (campaignLinks.length > 0) {
1728 console.log(`Открытие кампании 1/${campaignLinks.length}: ${campaignLinks[0]}`);
1729 window.open(campaignLinks[0], '_blank');
1730 }
1731
1732 } catch (error) {
1733 console.error('Ошибка при массовой обработке:', error);
1734 showBulkStatus(`Ошибка: ${error.message}`, true);
1735 isBulkProcessing = false;
1736 await GM.deleteValue('bulkProcessingDRR');
1737 }
1738 }
1739
1740 // Функция для паузы массовой обработки
1741 async function pauseBulkProcessing() {
1742 bulkPaused = !bulkPaused;
1743 const pauseBtn = document.getElementById('pause-bulk-btn');
1744
1745 if (bulkPaused) {
1746 await GM.setValue('bulkPaused', true);
1747 pauseBtn.textContent = '▶️ Продолжить';
1748 showBulkStatus('⏸️ Обработка приостановлена');
1749 console.log('Массовая обработка приостановлена');
1750 } else {
1751 await GM.deleteValue('bulkPaused');
1752 pauseBtn.textContent = '⏸️ Пауза';
1753 showBulkStatus('▶️ Обработка возобновлена');
1754 console.log('Массовая обработка возобновлена');
1755
1756 // Продолжаем обработку - открываем следующую кампанию
1757 const campaignsJson = await GM.getValue('bulkCampaigns', null);
1758 const currentIndex = await GM.getValue('bulkCurrentIndex', 0);
1759 const totalCampaigns = await GM.getValue('bulkTotalCampaigns', 0);
1760
1761 if (campaignsJson) {
1762 const campaigns = JSON.parse(campaignsJson);
1763 const nextIndex = currentIndex + 1;
1764
1765 console.log(`Обработано кампаний: ${nextIndex} из ${totalCampaigns}`);
1766
1767 if (nextIndex < campaigns.length) {
1768 // Сохраняем новый индекс
1769 await GM.setValue('bulkCurrentIndex', nextIndex);
1770
1771 // Открываем следующую кампанию в новой вкладке через window.open
1772 console.log(`Открываем кампанию ${nextIndex + 1}: ${campaigns[nextIndex]}`);
1773 const newTab = window.open(campaigns[nextIndex], '_blank');
1774
1775 if (newTab) {
1776 // Закрываем текущую вкладку
1777 await wait(1000);
1778 window.close();
1779 } else {
1780 // Если блокировщик всплывающих окон, используем редирект
1781 console.log('Блокировщик всплывающих окон, используем редирект');
1782 window.location.href = campaigns[nextIndex];
1783 }
1784 }
1785 }
1786 }
1787 }
1788
1789 // Функция для остановки массовой обработки
1790 async function stopBulkProcessing() {
1791 await GM.deleteValue('bulkProcessingDRR');
1792 await GM.deleteValue('bulkCampaigns');
1793 await GM.deleteValue('bulkCurrentIndex');
1794 await GM.deleteValue('bulkTotalCampaigns');
1795
1796 showBulkStatus('⏹️ Обработка остановлена', true);
1797 console.log('Массовая обработка остановлена');
1798
1799 // Скрываем кнопки управления
1800 const controlButtons = document.getElementById('bulk-control-buttons');
1801 if (controlButtons) {
1802 controlButtons.style.display = 'none';
1803 }
1804
1805 isBulkProcessing = false;
1806 bulkPaused = false;
1807 }
1808
1809 // Функция для показа статуса
1810 function showStatus(message, isError = false) {
1811 const statusDiv = document.getElementById('status-message');
1812 if (statusDiv) {
1813 statusDiv.textContent = message;
1814 statusDiv.style.display = 'block';
1815 statusDiv.style.background = isError ? 'rgba(255, 59, 48, 0.9)' : 'rgba(52, 199, 89, 0.9)';
1816 console.log(`Статус: ${message}`);
1817 }
1818 }
1819
1820 // Функция для ожидания
1821 function wait(ms) {
1822 return new Promise(resolve => setTimeout(resolve, ms));
1823 }
1824
1825 // Функция для парсинга числа из строки
1826 function parseNumber(str) {
1827 if (!str) return 0;
1828 // Убираем все символы кроме цифр, точек и запятых
1829 const cleaned = str.replace(/[^\d.,]/g, '').replace(/\s/g, '');
1830 // Заменяем запятую на точку
1831 const normalized = cleaned.replace(',', '.');
1832 return parseFloat(normalized) || 0;
1833 }
1834
1835 // Функция для парсинга процента
1836 function parsePercentage(str) {
1837 if (!str) return 0;
1838 const cleaned = str.replace('%', '').replace(',', '.').trim();
1839 return parseFloat(cleaned) || 0;
1840 }
1841
1842 // Основная функция запуска стратегии
1843 async function runStrategy() {
1844 try {
1845 console.log('Начало выполнения стратегии');
1846 showStatus('Запуск процесса...');
1847
1848 const desiredPercentage = parseFloat(document.getElementById('desired-percentage').value);
1849 if (!desiredPercentage || desiredPercentage <= 0) {
1850 showStatus('Ошибка: введите корректный процент', true);
1851 await handleStrategyError();
1852 return;
1853 }
1854 console.log(`Желаемый процент: ${desiredPercentage}%`);
1855
1856 // Проверяем, включен ли чекбокс "Удержание места"
1857 const holdPositionCheckbox = document.getElementById('hold-position');
1858 const shouldApplyHoldPosition = holdPositionCheckbox ? holdPositionCheckbox.checked : false;
1859
1860 // Шаг 1: Кликаем на статистику
1861 showStatus('Открытие статистики...');
1862 const statsButton = document.querySelector('.css-amj7dw');
1863 if (!statsButton) {
1864 showStatus('Ошибка: кнопка статистики не найдена', true);
1865 await handleStrategyError();
1866 return;
1867 }
1868 statsButton.click();
1869 console.log('Клик на статистику выполнен');
1870 await wait(2000);
1871
1872 // Шаг 1.5: Выбираем режим "Товары"
1873 showStatus('Выбор режима "Товары"...');
1874 const modeLabels = Array.from(document.querySelectorAll('label'));
1875 let modeInput = null;
1876
1877 for (const label of modeLabels) {
1878 if (label.textContent.trim() === 'Режим') {
1879 const inputId = label.getAttribute('for');
1880 if (inputId) {
1881 modeInput = document.getElementById(inputId);
1882 console.log(`Найдено поле Режим с id: ${inputId}`);
1883 break;
1884 }
1885 }
1886 }
1887
1888 if (modeInput) {
1889 modeInput.value = 'Товары';
1890 const inputEvent = new Event('input', { bubbles: true });
1891 const changeEvent = new Event('change', { bubbles: true });
1892 modeInput.dispatchEvent(inputEvent);
1893 modeInput.dispatchEvent(changeEvent);
1894
1895 console.log('Установлено значение: Товары');
1896 await wait(1000);
1897
1898 const options = document.querySelectorAll('[role="option"]');
1899 console.log(`Найдено опций: ${options.length}`);
1900
1901 if (options.length > 0) {
1902 const tovarOption = Array.from(options).find(opt => opt.textContent.includes('Товары'));
1903 if (tovarOption) {
1904 tovarOption.click();
1905 console.log('Выбран режим "Товары"');
1906 await wait(1000);
1907 }
1908 }
1909 }
1910
1911 // Шаг 2: Извлекаем данные из статистики
1912 showStatus('Извлечение данных...');
1913 await wait(500);
1914
1915 const stats = Array.from(document.querySelectorAll('.MuiTypography-caption.css-1et52kr'));
1916
1917 let sumOrders = 0;
1918 let ordersCount = 0;
1919 let cartToOrderPercent = 0;
1920
1921 stats.forEach(el => {
1922 const text = el.textContent.trim();
1923 const valueElement = el.closest('.MuiBox-root')?.querySelector('.MuiTypography-h3 .MuiTypography-body1');
1924 const value = valueElement ? valueElement.textContent.trim() : '';
1925
1926 console.log(`Найден показатель: ${text} = ${value}`);
1927
1928 if (text === 'Сумма заказов') {
1929 sumOrders = parseNumber(value);
1930 console.log(`Сумма заказов: ${sumOrders}`);
1931 } else if (text === 'Заказов') {
1932 ordersCount = parseNumber(value);
1933 console.log(`Заказов: ${ordersCount}`);
1934 } else if (text === 'Корзина → Заказ') {
1935 cartToOrderPercent = parsePercentage(value);
1936 console.log(`Корзина → Заказ: ${cartToOrderPercent}%`);
1937 }
1938 });
1939
1940 if (sumOrders === 0 || ordersCount === 0 || cartToOrderPercent === 0) {
1941 showStatus('Ошибка: не удалось получить данные статистики', true);
1942 console.error('Недостаточно данных:', { sumOrders, ordersCount, cartToOrderPercent });
1943 await handleStrategyError();
1944 return;
1945 }
1946
1947 // Шаг 3: Вычисляем стоимость корзины
1948 const cartCost = (sumOrders / ordersCount) * (desiredPercentage / 100) * (cartToOrderPercent / 100);
1949 const cartCostRounded = Math.round(cartCost * 100) / 100;
1950 console.log(`Расчет: (${sumOrders} / ${ordersCount}) * (${desiredPercentage} / 100) * (${cartToOrderPercent} / 100) = ${cartCostRounded}`);
1951 showStatus(`Рассчитано: ${cartCostRounded} ₽`);
1952
1953 // Закрываем статистику
1954 statsButton.click();
1955 await wait(500);
1956
1957 // Шаг 4: Кликаем на "Вставить стратегию"
1958 showStatus('Вставка стратегии...');
1959 const insertButtons = document.querySelectorAll('.css-5kbhos');
1960 let insertButton = null;
1961
1962 for (const btn of insertButtons) {
1963 if (btn.textContent.includes('Вставить стратегию')) {
1964 insertButton = btn;
1965 break;
1966 }
1967 }
1968
1969 if (!insertButton && insertButtons.length >= 2) {
1970 insertButton = insertButtons[1];
1971 } else if (!insertButton && insertButtons.length === 1) {
1972 insertButton = insertButtons[0];
1973 }
1974
1975 if (!insertButton) {
1976 showStatus('Ошибка: кнопка "Вставить стратегию" не найдена', true);
1977 await handleStrategyError();
1978 return;
1979 }
1980
1981 // Получаем текущую выбранную стратегию
1982 let currentStrategyCode;
1983 if (shouldApplyHoldPosition) {
1984 currentStrategyCode = await getHoldPositionStrategy();
1985 console.log('Используем стратегию удержания места, длина:', currentStrategyCode.length);
1986 } else {
1987 currentStrategyCode = await getCurrentStrategy();
1988 console.log('Используем обычную стратегию, длина:', currentStrategyCode.length);
1989 }
1990
1991 // Копируем код стратегии в буфер обмена
1992 console.log('Копируем стратегию в буфер обмена...');
1993
1994 try {
1995 await navigator.clipboard.writeText(currentStrategyCode);
1996 console.log('✓ Стратегия скопирована через navigator.clipboard');
1997 } catch {
1998 console.log('navigator.clipboard не сработал, пробуем GM.setClipboard...');
1999 try {
2000 await GM.setClipboard(currentStrategyCode);
2001 console.log('✓ Стратегия скопирована через GM.setClipboard');
2002 } catch (e2) {
2003 console.error('Ошибка копирования через GM.setClipboard:', e2.message);
2004 }
2005 }
2006
2007 await wait(500);
2008
2009 insertButton.click();
2010 console.log('Клик на "Вставить стратегию" выполнен');
2011 await wait(500);
2012
2013 console.log('✓ Стратегия вставлена из буфера обмена');
2014 await wait(500);
2015
2016 // Шаг 5: Находим поле "Желаемое значение" и вставляем результат
2017 showStatus('Заполнение поля...');
2018
2019 const inputLabels = Array.from(document.querySelectorAll('.MuiInputLabel-root'));
2020 let targetInput = null;
2021
2022 for (const label of inputLabels) {
2023 const labelText = label.textContent;
2024 if (labelText.includes('Желаемое значение За корзину')) {
2025 const inputId = label.getAttribute('for');
2026 if (inputId) {
2027 targetInput = document.getElementById(inputId);
2028 console.log(`Найдено поле: ${labelText}, id: ${inputId}`);
2029 break;
2030 }
2031 }
2032 }
2033
2034 if (!targetInput) {
2035 const inputs = document.querySelectorAll('input[type="number"]');
2036 for (const input of inputs) {
2037 const name = input.getAttribute('name') || '';
2038 if (name.includes('rules') && name.includes('value')) {
2039 targetInput = input;
2040 console.log(`Найдено поле по имени: ${name}`);
2041 break;
2042 }
2043 }
2044 }
2045
2046 if (!targetInput) {
2047 showStatus('Ошибка: поле для ввода не найдено', true);
2048 console.error('Не удалось найти поле для ввода значения');
2049 await handleStrategyError();
2050 return;
2051 }
2052
2053 targetInput.focus();
2054 targetInput.value = cartCostRounded.toString();
2055
2056 const inputEvent = new Event('input', { bubbles: true });
2057 const changeEvent = new Event('change', { bubbles: true });
2058 targetInput.dispatchEvent(inputEvent);
2059 targetInput.dispatchEvent(changeEvent);
2060
2061 console.log(`Значение ${cartCostRounded} установлено в поле ${targetInput.id}`);
2062 await wait(500);
2063
2064 // Шаг 6: Нажимаем "Сохранить"
2065 showStatus('Сохранение...');
2066 const saveButtons = document.querySelectorAll('button');
2067 let saveButton = null;
2068
2069 for (const btn of saveButtons) {
2070 if (btn.textContent.trim() === 'Сохранить') {
2071 saveButton = btn;
2072 break;
2073 }
2074 }
2075
2076 if (!saveButton) {
2077 showStatus('Ошибка: кнопка "Сохранить" не найдена', true);
2078 await handleStrategyError();
2079 return;
2080 }
2081
2082 saveButton.click();
2083 console.log('Клик на "Сохранить" выполнен');
2084 await wait(500);
2085
2086 showStatus(`✅ Готово! Стоимость корзины: ${cartCostRounded} ₽`);
2087 console.log('Стратегия успешно установлена');
2088
2089 // Проверяем, идет ли массовая обработка
2090 const savedDRR = await GM.getValue('bulkProcessingDRR', null);
2091 if (savedDRR !== null) {
2092 // Проверяем настройки чекбоксов
2093 const bulkApplyToClusters = await GM.getValue('bulkApplyToClusters', true);
2094 const bulkHoldPosition = await GM.getValue('bulkHoldPosition', true);
2095
2096 console.log(`Настройки массовой обработки: применить к кластерам = ${bulkApplyToClusters}, удержание места = ${bulkHoldPosition}`);
2097
2098 // Если нужно применить стратегию к кластерам
2099 if (bulkApplyToClusters) {
2100 console.log('Переходим к обработке кластеров...');
2101 showStatus('🎯 Обработка кластеров...');
2102
2103 // Ждем немного после сохранения
2104 await wait(2000);
2105
2106 // Ищем вкладку "Кластеры"
2107 const tabs = document.querySelectorAll('[role="tab"]');
2108 let clustersTab = null;
2109
2110 for (const tab of tabs) {
2111 if (tab.textContent.includes('Кластеры')) {
2112 clustersTab = tab;
2113 console.log('Найдена вкладка "Кластеры"');
2114 break;
2115 }
2116 }
2117
2118 if (clustersTab) {
2119 // Кликаем на вкладку "Кластеры"
2120 clustersTab.click();
2121 console.log('Клик на вкладку "Кластеры" выполнен');
2122 await wait(2000);
2123
2124 // Ищем все ссылки на кластеры
2125 const clusterLinks = document.querySelectorAll('a[href*="/cluster"]');
2126 console.log(`Найдено кластеров: ${clusterLinks.length}`);
2127
2128 if (clusterLinks.length > 0) {
2129 // Обрабатываем каждый кластер
2130 for (let i = 0; i < clusterLinks.length; i++) {
2131 const clusterLink = clusterLinks[i];
2132 const clusterUrl = clusterLink.href;
2133
2134 console.log(`Обработка кластера ${i + 1}/${clusterLinks.length}: ${clusterUrl}`);
2135 showStatus(`🎯 Кластер ${i + 1}/${clusterLinks.length}...`);
2136
2137 // Переходим к кластеру
2138 window.location.href = clusterUrl;
2139
2140 // Ждем загрузки страницы кластера и применяем стратегию
2141 // (это будет обработано при следующей загрузке страницы)
2142 return;
2143 }
2144 } else {
2145 console.log('Кластеры не найдены, переходим к следующей кампании');
2146 }
2147 } else {
2148 console.log('Вкладка "Кластеры" не найдена, переходим к следующей кампании');
2149 }
2150 }
2151
2152 console.log('Массовая обработка активна, переходим к следующей кампании');
2153 showStatus('✅ Готово! Переход к следующей кампании через 3 секунды...');
2154
2155 // Ждем 3 секунды перед переходом
2156 await wait(3000);
2157
2158 const campaignsJson = await GM.getValue('bulkCampaigns', null);
2159 const currentIndex = await GM.getValue('bulkCurrentIndex', 0);
2160 const totalCampaigns = await GM.getValue('bulkTotalCampaigns', 0);
2161
2162 if (campaignsJson) {
2163 const campaigns = JSON.parse(campaignsJson);
2164 const nextIndex = currentIndex + 1;
2165
2166 console.log(`Обработано кампаний: ${nextIndex} из ${totalCampaigns}`);
2167
2168 if (nextIndex < campaigns.length) {
2169 // Сохраняем новый индекс
2170 await GM.setValue('bulkCurrentIndex', nextIndex);
2171
2172 // Переходим к следующей кампании
2173 console.log(`Переходим к кампании ${nextIndex + 1}: ${campaigns[nextIndex]}`);
2174 window.location.href = campaigns[nextIndex];
2175 } else {
2176 // Все кампании обработаны
2177 console.log('Все кампании обработаны');
2178 await GM.deleteValue('bulkProcessingDRR');
2179 await GM.deleteValue('bulkCampaigns');
2180 await GM.deleteValue('bulkCurrentIndex');
2181 await GM.deleteValue('bulkTotalCampaigns');
2182 await GM.deleteValue('bulkApplyToClusters');
2183 await GM.deleteValue('bulkHoldPosition');
2184
2185 showStatus('✅ Все кампании обработаны!');
2186 alert('✅ Все кампании обработаны!');
2187 }
2188 }
2189 }
2190
2191 } catch (error) {
2192 console.error('Ошибка при выполнении стратегии:', error);
2193 showStatus(`Ошибка: ${error.message}`, true);
2194 await handleStrategyError();
2195 }
2196 }
2197
2198 // Функция для обработки ошибок и перехода к следующей кампании
2199 async function handleStrategyError() {
2200 const savedDRR = await GM.getValue('bulkProcessingDRR', null);
2201 if (savedDRR !== null) {
2202 console.log('Обнаружена ошибка при массовой обработке, ждем 10 секунд');
2203 showStatus('⚠️ Ошибка! Переход к следующей кампании через 10 секунд...', true);
2204
2205 for (let i = 10; i > 0; i--) {
2206 showStatus(`⚠️ Ошибка! Переход к следующей через ${i} сек...`, true);
2207 await wait(1000);
2208 }
2209
2210 const campaignsJson = await GM.getValue('bulkCampaigns', null);
2211 const currentIndex = await GM.getValue('bulkCurrentIndex', 0);
2212 const totalCampaigns = await GM.getValue('bulkTotalCampaigns', 0);
2213
2214 if (campaignsJson) {
2215 const campaigns = JSON.parse(campaignsJson);
2216 const nextIndex = currentIndex + 1;
2217
2218 console.log(`Обработано кампаний: ${nextIndex} из ${totalCampaigns}`);
2219
2220 if (nextIndex < campaigns.length) {
2221 await GM.setValue('bulkCurrentIndex', nextIndex);
2222 console.log(`Переходим к кампании ${nextIndex + 1}: ${campaigns[nextIndex]}`);
2223 // Просто перенаправляем текущую вкладку на следующую кампанию
2224 window.location.href = campaigns[nextIndex];
2225 } else {
2226 console.log('Все кампании обработаны');
2227 await GM.deleteValue('bulkProcessingDRR');
2228 await GM.deleteValue('bulkCampaigns');
2229 await GM.deleteValue('bulkCurrentIndex');
2230 await GM.deleteValue('bulkTotalCampaigns');
2231 await GM.deleteValue('bulkApplyToClusters');
2232 await GM.deleteValue('bulkHoldPosition');
2233
2234 showStatus('✅ Все кампании обработаны!');
2235 alert('✅ Все кампании обработаны!');
2236 }
2237 }
2238 }
2239 }
2240
2241 // Инициализация
2242 function init() {
2243 console.log('Инициализация расширения');
2244
2245 if (window.location.href.includes('/advert/campaigns')) {
2246 console.log('Страница списка кампаний обнаружена');
2247 setTimeout(() => {
2248 if (document.body) {
2249 createBulkUI();
2250 } else {
2251 console.error('Body не найден');
2252 }
2253 }, 1000);
2254 } else if (window.location.href.includes('/campaigns/auto-campaigns/') && window.location.href.includes('/campaign')) {
2255 console.log('Страница кампании обнаружена');
2256 setTimeout(() => {
2257 if (document.body) {
2258 createUI();
2259 } else {
2260 console.error('Body не найден');
2261 }
2262 }, 1000);
2263 } else {
2264 console.log('Не на странице кампании, UI не создается');
2265 }
2266 }
2267
2268 // Запускаем инициализацию
2269 init();
2270
2271})();