Drive2 Auto Helper

работает на стренице подписок на сашины. проврка на бывшие авто и взаимные подписки, автоматически отписывается от бывших авто и от машин чьи владельцы не во взаимной подписке.

  1. // ==UserScript==
  2. // @name Drive2 Auto Helper
  3. // @namespace drive2.com
  4. // @version 0.93
  5. // @description работает на стренице подписок на сашины. проврка на бывшие авто и взаимные подписки, автоматически отписывается от бывших авто и от машин чьи владельцы не во взаимной подписке.
  6. // @author drive2 lover
  7. // @match https://www.drive2.com/*
  8. // @match https://www.drive2.ru/*
  9. // @license MIT
  10. // @grant none
  11.  
  12. // ==/UserScript==
  13. let tail = '';
  14. let fctx = '';
  15.  
  16. const MY_CARS_KEY = '__my_cached_cars__';
  17.  
  18. function getCarsKeyWithDate() {
  19. return MY_CARS_KEY + fctx;
  20. }
  21.  
  22. function getWithExpiry(key) {
  23. const itemStr = localStorage.getItem(key);
  24. if (!itemStr) return null;
  25.  
  26. try {
  27. const item = JSON.parse(itemStr);
  28. if (Date.now() > item.expiry) {
  29. localStorage.removeItem(key); // чистим мусор
  30. return null;
  31. }
  32. return item.value;
  33. } catch {
  34. localStorage.removeItem(key); // вдруг поломалось
  35. return null;
  36. }
  37. }
  38.  
  39. let myCars = getWithExpiry(getCarsKeyWithDate()) || [];
  40.  
  41. const localStorageKey = 'saveLastPage';
  42.  
  43. let stats = {
  44. pages: 0,
  45. totalBlocks: 0,
  46. processedBlocks: 0,
  47. removedBlocks: 0,
  48. unsubscribedBlocks: 0,
  49. oldSubscribedBlocks: 0
  50. };
  51.  
  52. // Создаем блок статистики
  53. const statsDiv = document.createElement('div');
  54. statsDiv.style.position = 'fixed';
  55. statsDiv.style.top = '0';
  56. statsDiv.style.left = '0';
  57. statsDiv.style.backgroundColor = '#333';
  58. statsDiv.style.color = '#fff';
  59. statsDiv.style.padding = '15px';
  60. statsDiv.style.margin = '10px';
  61. statsDiv.style.zIndex = '1000';
  62.  
  63. // Блок "Отписываться от авто"
  64. const unsubscribeCheckboxDiv = document.createElement('div');
  65. const unsubscribeCheckbox = document.createElement('input');
  66. unsubscribeCheckbox.type = 'checkbox';
  67. unsubscribeCheckbox.checked = true;
  68. unsubscribeCheckbox.id = 'unsubscribe-checkbox';
  69. const unsubscribeLabel = document.createElement('label');
  70. unsubscribeLabel.textContent = ' Отписываться от авто';
  71. unsubscribeLabel.style.marginRight = '10px';
  72. unsubscribeLabel.setAttribute('for', 'unsubscribe-checkbox');
  73. unsubscribeCheckboxDiv.appendChild(unsubscribeCheckbox);
  74. unsubscribeCheckboxDiv.appendChild(unsubscribeLabel);
  75. // statsDiv.appendChild(unsubscribeCheckboxDiv);
  76.  
  77. // Блок "Скрывать старые авто"
  78. const hideOldCarsCheckboxDiv = document.createElement('div');
  79. const hideOldCarsCheckbox = document.createElement('input');
  80. hideOldCarsCheckbox.type = 'checkbox';
  81. hideOldCarsCheckbox.checked = true;
  82. hideOldCarsCheckbox.id = 'hide-old-cars-checkbox';
  83. const hideOldCarsLabel = document.createElement('label');
  84. hideOldCarsLabel.textContent = ' Скрывать старые авто';
  85. hideOldCarsLabel.style.marginRight = '10px';
  86. hideOldCarsLabel.setAttribute('for', 'hide-old-cars-checkbox');
  87. hideOldCarsCheckboxDiv.appendChild(hideOldCarsCheckbox);
  88. hideOldCarsCheckboxDiv.appendChild(hideOldCarsLabel);
  89. // statsDiv.appendChild(hideOldCarsCheckboxDiv);
  90.  
  91. // Кнопка "Проверить"
  92. const checkButton = document.createElement('button');
  93. checkButton.innerText = 'Проверить';
  94. checkButton.style.marginTop = '10px';
  95. checkButton.style.padding = '5px 10px';
  96. checkButton.style.backgroundColor = '#007bff';
  97. checkButton.style.color = '#fff';
  98. checkButton.style.border = 'none';
  99. checkButton.style.borderRadius = '5px';
  100. checkButton.style.cursor = 'pointer';
  101. // statsDiv.appendChild(checkButton);
  102.  
  103. // Блок для ввода страниц и кнопки "Пропустить страниц"
  104. const skipPagesDiv = document.createElement('div');
  105. skipPagesDiv.style.marginTop = '10px';
  106.  
  107. // Поле ввода количества страниц
  108. const pagesInput = document.createElement('input');
  109. pagesInput.type = 'number';
  110. pagesInput.min = '1';
  111. pagesInput.value = localStorage.getItem(localStorageKey) ?? 1;
  112. pagesInput.style.width = '50px';
  113. pagesInput.style.marginRight = '10px';
  114.  
  115. // Кнопка "Пропустить страниц"
  116. const skipButton = document.createElement('button');
  117. skipButton.innerText = 'Пропустить страниц';
  118. skipButton.style.padding = '5px 10px';
  119. skipButton.style.backgroundColor = '#dc3545';
  120. skipButton.style.color = '#fff';
  121. skipButton.style.border = 'none';
  122. skipButton.style.borderRadius = '5px';
  123. skipButton.style.cursor = 'pointer';
  124.  
  125. // Добавляем инпут и кнопку в блок
  126. skipPagesDiv.appendChild(pagesInput);
  127. skipPagesDiv.appendChild(skipButton);
  128. //statsDiv.appendChild(skipPagesDiv);
  129.  
  130. const unsubscribeLimitDiv = document.createElement('div');
  131. const limitInput = document.createElement('input');
  132. limitInput.type = 'number';
  133. limitInput.min = '0';
  134. limitInput.value = 25;
  135. limitInput.id = 'limitInput';
  136. limitInput.style.width = '50px';
  137. limitInput.style.marginRight = '10px';
  138. unsubscribeLimitDiv.appendChild(limitInput);
  139. const limitLabel = document.createElement('label');
  140. limitLabel.textContent = ' Лимит отписок от авто';
  141. limitLabel.style.marginRight = '10px';
  142. limitLabel.setAttribute('for', 'limitInput');
  143. unsubscribeLimitDiv.appendChild(limitLabel);
  144.  
  145. const carBlock = document.querySelector('.l-container .u-link-area');
  146. const carBlockClass = carBlock?.parentElement?.classList?.[0] ?? null;
  147. const carsBlockClass = carBlock?.parentElement?.parentElement?.className ?? null;
  148. const carsBlockClassFormatted = carsBlockClass?.split(' ').map(className => '.' + className).join('') ?? '';
  149.  
  150. console.log('carBlockClass ' + carBlockClass);
  151. console.log('carsBlockClassFormatted ' + carsBlockClassFormatted);
  152.  
  153. async function init_me() {
  154. if (window.d2Env) {
  155. tail = window.d2Env.userId;
  156. fctx = window.d2Env.formContext['.FCTX'];
  157. } else {
  158. alert('обновите версию скрипта!');
  159. return;
  160. }
  161. }
  162.  
  163. // Функция для нажатия кнопки загрузки страниц и очистки элементов
  164. async function skipPages() {
  165. let pagesToSkip = parseInt(pagesInput.value, 10);
  166. if (isNaN(pagesToSkip) || pagesToSkip <= 0) {
  167. alert('Введите корректное число страниц');
  168. return;
  169. }
  170.  
  171. for (let i = 0; i < pagesToSkip; i++) {
  172. const loadMoreButton = document.querySelector('button.x-box-more');
  173. if (loadMoreButton) {
  174. await clearGrid();
  175. loadMoreButton.click();
  176. console.log(`Нажатие ${i + 1} на кнопку "Показать ещё"`);
  177. await new Promise(resolve => setTimeout(resolve, 3000)); // Ждём 3 секунды
  178. stats.pages++;
  179. updateStats();
  180. await clearEmptyBlocks();
  181. } else {
  182. alert('Кнопка "Показать ещё" не найдена, остановка.');
  183. break;
  184. }
  185. }
  186. }
  187.  
  188. // Функция очистки блока .o-grid.o-grid--2.o-grid--equal
  189. async function clearGrid() {
  190. const grids = document.querySelectorAll(carsBlockClassFormatted);
  191. if (grids.length > 0) {
  192. let blocks;
  193. for (const grid of grids) {
  194. blocks = grid.querySelectorAll('.' + carBlockClass);
  195. if (blocks.length) {
  196. stats.processedBlocks += blocks.length;
  197. blocks.forEach(car => car.remove());
  198. }
  199. }
  200. console.log('Удалены авто из пропущенных страниц.');
  201. } else {
  202. console.log('Не найдено блоков для очистки.');
  203. }
  204. }
  205.  
  206. skipButton.addEventListener('click', skipPages);
  207.  
  208. const updateStats = () => {
  209. statsDiv.innerHTML = `<div>
  210. Страниц пройдено: ${stats.pages}<br>
  211. Иконок авто в очереди: ${stats.totalBlocks}<br>
  212. Обработано: ${stats.processedBlocks}<br>
  213. Отписались автоматом: ${stats.unsubscribedBlocks}<br>
  214. Подписан на старые авто: ${stats.oldSubscribedBlocks}
  215. </div>`;
  216. statsDiv.appendChild(unsubscribeCheckboxDiv);
  217. statsDiv.appendChild(hideOldCarsCheckboxDiv);
  218. statsDiv.appendChild(unsubscribeLimitDiv);
  219. statsDiv.appendChild(checkButton);
  220. statsDiv.appendChild(skipPagesDiv);
  221. localStorage.setItem(localStorageKey, stats.pages);
  222. };
  223.  
  224. // Функция для поиска и клика по кнопке "Загрузить еще"
  225. const clickMoreButton = async () => {
  226. const button = document.querySelector('button.x-box-more');
  227.  
  228. if (document.querySelector('#limitInput').value < 1) {
  229. alert('Лимит отписок закончился. Во избежание блокировки аккаунта продолжайте завтра.');
  230. return true;
  231. }
  232.  
  233. if (button) {
  234. console.error('Нашли кнопку дальнейшей загрузки');
  235.  
  236. stats.pages++;
  237. console.log('Загружаем страницу ' + stats.pages);
  238. button.click();
  239.  
  240. updateStats();
  241. await clearEmptyBlocks();
  242. await new Promise(resolve => setTimeout(resolve, 3000));
  243. console.log('Загрузили блоки с авто, приступаем к их обработке');
  244. processBlocks();
  245. } else {
  246. alert('Кнопка не найдена, остановка процесса');
  247. }
  248. };
  249.  
  250. async function clearEmptyBlocks()
  251. {
  252. document.querySelectorAll(carsBlockClassFormatted).forEach(grid => {if (!grid.querySelector('div')) { grid.remove(); }});
  253. }
  254.  
  255. function setWithExpiry(key, value, ttlMs) {
  256. const item = {
  257. value,
  258. expiry: Date.now() + ttlMs,
  259. };
  260. localStorage.setItem(key, JSON.stringify(item));
  261. }
  262.  
  263. async function loadMyCars() {
  264. if (!myCars || myCars.length === 0) {
  265. const response = await fetch('/my/r/');
  266. const html = await response.text();
  267. const parser = new DOMParser();
  268. const doc = parser.parseFromString(html, 'text/html');
  269.  
  270. const cars = [];
  271. doc.querySelectorAll('.c-car-draglist__item .c-car-card__caption a').forEach(car => {
  272. if (!car.classList.contains('x-secondary-color')) {
  273. const id = car.href.match(/\/(\d+)\//)[1];
  274. cars.push({
  275. id,
  276. name: car.textContent.trim()
  277. });
  278. }
  279. });
  280.  
  281. setWithExpiry(getCarsKeyWithDate(), cars, 24 * 60 * 60 * 1000);
  282. myCars = cars;
  283.  
  284. console.log('обновил кеш моих авто');
  285. }
  286. console.log('кеш моих авто', myCars?.map(car => car.name)?.join(', ') ?? '');
  287. }
  288.  
  289. function addCloseButton(element) {
  290. if (!element) return;
  291. const closeButton = document.createElement('button');
  292. closeButton.innerHTML = '&times;'; // Символ "×" (крестик)
  293. closeButton.style.position = 'absolute';
  294. closeButton.style.top = '5px';
  295. closeButton.style.right = '5px';
  296. closeButton.style.background = 'red';
  297. closeButton.style.color = 'white';
  298. closeButton.style.border = 'none';
  299. closeButton.style.padding = '5px 10px';
  300. closeButton.style.cursor = 'pointer';
  301. closeButton.style.fontSize = '16px';
  302. closeButton.style.borderRadius = '50%';
  303. closeButton.addEventListener('click', function () {
  304. this.parentNode.remove(); // Удаляет родительский элемент кнопки
  305. });
  306. if (window.getComputedStyle(element).position === 'static') {
  307. element.style.position = 'relative';
  308. }
  309. element.appendChild(closeButton);
  310.  
  311. element.classList.remove(carBlockClass);
  312. element.querySelector('a.u-link-area').remove();
  313. element.style.position = 'sticky';
  314. }
  315.  
  316. const unsubscribeCar = async (id) => {
  317. const url = '/ajax/subscription';
  318. const data = {
  319. _: 'unsubscribe',
  320. type: 'car',
  321. id: id,
  322. '.FCTX': fctx
  323. };
  324.  
  325. try {
  326. const response = await fetch(url, {
  327. method: 'POST',
  328. headers: {'Content-Type': 'application/x-www-form-urlencoded'},
  329. body: new URLSearchParams(data).toString()
  330. });
  331.  
  332. if (response.ok) {
  333. const result = await response.json();
  334. console.log('Ответ unsubscribeCar:', result);
  335. return result;
  336. } else {
  337. console.error('Ошибка запроса:', response.status);
  338. }
  339. } catch (error) {
  340. console.error('Ошибка выполнения POST-запроса:', error);
  341. }
  342. };
  343.  
  344. const followUser = async (id) => {
  345. const url = '/ajax/subscription';
  346. const data = {
  347. _: 'subscribe',
  348. type: 'user',
  349. id: id.replace('p/',''),
  350. '.FCTX': fctx
  351. };
  352.  
  353. try {
  354. const response = await fetch(url, {
  355. method: 'POST',
  356. headers: {'Content-Type': 'application/x-www-form-urlencoded'},
  357. body: new URLSearchParams(data).toString()
  358. });
  359.  
  360. if (response.ok) {
  361. const result = await response.json();
  362. console.log('Ответ subscribeCar:', result);
  363. return result;
  364. } else {
  365. console.error('Ошибка запроса:', response.status);
  366. }
  367. } catch (error) {
  368. console.error('Ошибка выполнения POST-запроса:', error);
  369. }
  370. };
  371.  
  372. const unfollowUser = async (id) => {
  373. const url = '/ajax/subscription';
  374. const data = {
  375. _: 'unsubscribe',
  376. type: 'user',
  377. id: id,
  378. '.FCTX': fctx
  379. };
  380.  
  381. try {
  382. const response = await fetch(url, {
  383. method: 'POST',
  384. headers: {'Content-Type': 'application/x-www-form-urlencoded'},
  385. body: new URLSearchParams(data).toString()
  386. });
  387.  
  388. if (response.ok) {
  389. const result = await response.json();
  390. return result?.success;
  391. } else {
  392. console.error('Ошибка запроса:', response.status);
  393. }
  394. } catch (error) {
  395. console.error('Ошибка выполнения POST-запроса:', error);
  396. }
  397. };
  398.  
  399.  
  400. const shareCar = async (token, comment) => {
  401. const url = '/_api/share';
  402. const data = {
  403. token: token,
  404. comment: comment,
  405. '.FCTX': fctx
  406. };
  407.  
  408. try {
  409. const response = await fetch(url, {
  410. method: 'POST',
  411. headers: {'Content-Type': 'application/x-www-form-urlencoded'},
  412. body: new URLSearchParams(data).toString()
  413. });
  414.  
  415. if (response.ok) {
  416. const result = await response.json();
  417. return result?.success;
  418. } else {
  419. console.error('Ошибка запроса:', response.status);
  420. }
  421. } catch (error) {
  422. console.error('Ошибка выполнения POST-запроса:', error);
  423. }
  424. };
  425.  
  426. const likesSend = async (token) => {
  427. const url = '/_api/likes';
  428. const data = {
  429. token: token,
  430. '.FCTX': fctx
  431. };
  432.  
  433. try {
  434. const response = await fetch(url, {
  435. method: 'POST',
  436. headers: {'Content-Type': 'application/x-www-form-urlencoded'},
  437. body: new URLSearchParams(data).toString()
  438. });
  439.  
  440. if (response.ok) {
  441. const result = await response.json();
  442. console.log('like');
  443. return result;
  444. } else {
  445. console.error('Ошибка запроса:', response.status);
  446. }
  447. } catch (error) {
  448. console.error('Ошибка выполнения POST-запроса:', error);
  449. }
  450. };
  451.  
  452. const subscribeCar = async (id) => {
  453. const url = '/ajax/subscription';
  454. const data = {
  455. _: 'subscribe',
  456. type: 'car',
  457. id: id,
  458. '.FCTX': fctx
  459. };
  460.  
  461. try {
  462. const response = await fetch(url, {
  463. method: 'POST',
  464. headers: {'Content-Type': 'application/x-www-form-urlencoded'},
  465. body: new URLSearchParams(data).toString()
  466. });
  467.  
  468. if (response.ok) {
  469. const result = await response.json();
  470. console.log('subscribeCar');
  471. return result;
  472. } else {
  473. console.error('Ошибка запроса:', response.status);
  474. }
  475. } catch (error) {
  476. console.error('Ошибка выполнения POST-запроса:', error);
  477. }
  478. };
  479.  
  480. async function loadUser(userId) {
  481. const response = await fetch(`/_api/hovercards/${userId}?tail=${tail}`);
  482. return await response.json();
  483. }
  484.  
  485. function isUserSubscribedOnMyCars(data) {
  486. const followedCarIds = data?.subscriptions?.followedCars?.map(car => {
  487. const match = car.url.match(/\/(\d+)\//);
  488. return match ? match[1] : null;
  489. }).filter(Boolean);
  490. const myCarIds = myCars ? myCars.map(car => car.id) : [];
  491. if (myCarIds.length == 0) {
  492. console.error('У текущего юзера не найдены авто');
  493. return false;
  494. }
  495. if (followedCarIds ? followedCarIds.some(carId => myCarIds.includes(carId)) : false) {
  496. return myCars
  497. .filter(car => followedCarIds.includes(car.id))
  498. .map(car => car.name)
  499. .join(', ');
  500. }
  501. return false;
  502. }
  503.  
  504. const processBlocks = async () => {
  505. const blocks = document.querySelectorAll('.' + carBlockClass);
  506. console.error('На странице найдено ' + blocks.length + ' авто');
  507. updateStats();
  508. let myCarNames = '';
  509.  
  510. for (const block of blocks) {
  511.  
  512. if (document.querySelector('#limitInput').value < 1) {
  513. alert('Лимит отписок закончился. Во избежание блокировки аккаунта продолжайте завтра.');
  514. return true;
  515. }
  516.  
  517. const titleElement = block.querySelector('.c-car-title');
  518. stats.totalBlocks = document.querySelectorAll('.' + carBlockClass).length;
  519.  
  520. // Если чекбокс скрытия старых авто включен и блок старый – пропускаем или удаляем
  521. if (titleElement.classList.contains('x-secondary-color')) {
  522. stats.oldSubscribedBlocks++;
  523. stats.processedBlocks++;
  524. if (hideOldCarsCheckbox.checked) {
  525. block.remove();
  526. stats.removedBlocks++;
  527. console.error('Старое авто, удаляем');
  528. } else {
  529. console.error('Старое авто, оставляем на странице');
  530. addCloseButton(block);
  531. }
  532. updateStats();
  533. continue;
  534. }
  535.  
  536. const subscribeButton = block.querySelector('subscribe-button');
  537. const userId = block.querySelector('a.c-username')?.getAttribute('data-ihc-token');
  538.  
  539. if (!userId) {
  540. console.error('Не найден userId, пропускаем');
  541. continue
  542. };
  543.  
  544. const data = await loadUser(userId);
  545. const myCarNames = isUserSubscribedOnMyCars(data);
  546. if (myCarNames) {
  547. console.log(`Юзер номер ${userId} подписан на (${myCarNames}), пропускаем.`);
  548. } else {
  549. let uid = subscribeButton.getAttribute('uid');
  550. console.log('Юзер номер ' + userId + ' не подписан на мои авто.');
  551. if (unsubscribeCheckbox.checked) {
  552. unsubscribeCar(uid);
  553. stats.unsubscribedBlocks++;
  554. document.querySelector('#limitInput').value = document.querySelector('#limitInput').value - 1;
  555. console.error('Отписываемся от авто с номером ' + uid);
  556. }
  557. }
  558.  
  559. // Удаляем блок, если он не содержит мою машину
  560. block.remove();
  561. stats.removedBlocks++;
  562. stats.processedBlocks++;
  563. updateStats();
  564.  
  565. // Ждём 1 секунду перед обработкой следующего блока
  566. await new Promise(resolve => setTimeout(resolve, 2000));
  567. }
  568. console.error('Обработали все авто');
  569.  
  570. clickMoreButton();
  571. };
  572.  
  573. function isCarsFollowingPage()
  574. {
  575. return (/^\/users\/(.*)\/carsfollowing/).test(window.location.pathname);
  576. }
  577.  
  578. function isSomeCarFollowingPage()
  579. {
  580. return (/^\/r\/(.*)\/followers/).test(window.location.pathname);
  581. }
  582.  
  583. function addSubscribeButton() {
  584. const counterElement = document.querySelector('.x-title-header .x-title .c-counter');
  585. if (counterElement) {
  586. const button = document.createElement('button');
  587. button.textContent = 'Подписаться на эти авто';
  588. button.classList.add('c-button');
  589. button.classList.add('c-button--primary');
  590. button.style.marginLeft = '10px';
  591. button.addEventListener('click', function () {
  592. scrapeUsers();
  593. });
  594. counterElement.after(button);
  595. }
  596. }
  597.  
  598. async function scrapeUsers() {
  599. let users = new Set();
  600. let usersDone = 0;
  601. let pagesProcessed = 0;
  602. let subscribeToCar = 0;
  603. let lostUsers = 0;
  604. let lostSubscribe = 0;
  605. let repostCar = 0;
  606.  
  607. let infoBox = document.createElement('div');
  608. infoBox.style.position = 'fixed';
  609. infoBox.style.top = '10px';
  610. infoBox.style.right = '10px';
  611. infoBox.style.background = 'rgb(51, 51, 51)';
  612. infoBox.style.color = 'white';
  613. infoBox.style.padding = '10px';
  614. infoBox.style.borderRadius = '5px';
  615. infoBox.style.zIndex = '1000';
  616. document.body.appendChild(infoBox);
  617.  
  618. function updateInfoBox() {
  619. let progress = users.size > 0 ? Math.round((usersDone / users.size) * 100) : 0;
  620. infoBox.innerHTML = `Страниц обработано: ${pagesProcessed}
  621. <br> Пользователей собрано: ${users.size}
  622. <br> Пользователей обработано: ${usersDone}
  623. <br> Пользователей пропущено: ${lostUsers}<br> (посещали сайт > чем месяц назад или нет актуальных авто)
  624. <br> Подписано на авто: ${subscribeToCar}<br> (даже если уже был на авто подписан)
  625. <br> Не удалось подписаться: ${lostSubscribe}
  626. <br> Репост: ${repostCar}
  627. <br>
  628. <div style='width: 100%; background: #555; height: 10px; border-radius: 5px; margin-top: 5px;'>
  629. <div style='width: ${progress}%; background: #4caf50; height: 10px; border-radius: 5px;'></div>
  630. </div>`;
  631. }
  632.  
  633. async function likeCar(carUrl, fullCaption, myCarNames) {
  634. try {
  635. await new Promise(resolve => setTimeout(resolve, 1000));
  636. let response = await fetch(carUrl);
  637. let html = await response.text();
  638. let parser = new DOMParser();
  639. let doc = parser.parseFromString(html, 'text/html');
  640. let likeButton = doc.querySelector('like-button');
  641. let likeButtonActive = likeButton?.hasAttribute('active') ?? false;
  642. let likeButtonDisabled = likeButton?.hasAttribute('disabled') ?? false;
  643. if (likeButton) {
  644. let key = likeButton.getAttribute('key');
  645. if (key && !likeButtonActive && !likeButtonDisabled) {
  646. await new Promise(resolve => setTimeout(resolve, 1000));
  647. likesSend(key);
  648. }
  649. }
  650.  
  651. let repostButton = doc.querySelector('repost-button');
  652. let repostButtonDisabled = repostButton?.hasAttribute('disabled');
  653. if (myCarNames && repostButton && !repostButtonDisabled && repostButton.hasAttribute('token')) {
  654. await new Promise(resolve => setTimeout(resolve, 1000));
  655. let result = await shareCar(repostButton.getAttribute('token'), 'Подписывайтесь на ' + fixString(fullCaption));
  656. if (result) {
  657. repostCar++;
  658. }
  659. }
  660.  
  661. let subscribeButton = doc.querySelector('subscribe-button');
  662. let subscribeButtonSubscribed = subscribeButton?.hasAttribute('subscribed') ?? false;
  663.  
  664. // если авто уже было когда-то подписано
  665. // был сделан лайк и репост
  666. // но по какой-то причине отписались
  667. // то подписываться повторно наверное и не нужно
  668. if (subscribeButton && !subscribeButtonSubscribed && repostButtonDisabled && likeButtonActive) {
  669. console.error(`Авто уже имеет лайк и репост но мы не подписаны, видимо есть причина, пропускам его`);
  670. lostSubscribe++;
  671. return;
  672. }
  673.  
  674. if (subscribeButton && !subscribeButtonSubscribed && subscribeButton.hasAttribute('uid')) {
  675. await new Promise(resolve => setTimeout(resolve, 1000));
  676. let result = await subscribeCar(subscribeButton.getAttribute('uid'));
  677. if (result?.types.length) {
  678. subscribeToCar++;
  679. } else {
  680. lostSubscribe++;
  681. }
  682. }
  683.  
  684.  
  685. } catch (error) {
  686. console.error(`Ошибка загрузки страницы авто ${carUrl}:`, error);
  687. }
  688. return null;
  689. }
  690.  
  691. function fixString(str) {
  692. let symbolsToReplace = ['🇷🇺', '☭'];
  693. return symbolsToReplace.reduce((acc, symbol) => acc.replaceAll(symbol, '🇺🇦'), str);
  694. }
  695.  
  696. async function processUsers(userList) {
  697. for (let userId of userList) {
  698. try {
  699. usersDone++;
  700. await new Promise(resolve => setTimeout(resolve, 1000));
  701. const data = await loadUser(userId);
  702. if (data?.lastVisit && shouldSkipUser(data.lastVisit)) {
  703. console.log(`Пропускаем пользователя ${data.nickname} (${userId}) из-за даты последнего визита: ${data.lastVisit}`);
  704. lostUsers++;
  705. continue;
  706. }
  707. const myCarNames = isUserSubscribedOnMyCars(data);
  708. if (data?.cars) {
  709. let i = 0;
  710. for (let car of data.cars) {
  711. i++;
  712. if (car.belongState === "My" && i <= myCars.length) {
  713. let carId = parseCarId(car.url);
  714. if (carId) {
  715. console.log(`Проверяем авто: ${car.fullCaption} ${car.url}`);
  716. updateInfoBox();
  717. }
  718. await likeCar(car.url, car.fullCaption, myCarNames);
  719. }
  720. }
  721. } else {
  722. lostUsers++;
  723. console.log('Не найдено авто у ' + userId);
  724. }
  725. if (data?.isFollowable) {
  726. if (data?.isFollowed === false && data?.subscriptions?.followsMe === true) {
  727. await new Promise(resolve => setTimeout(resolve, 1000));
  728. followUser(userId);
  729. }
  730. if (data?.isFollowed === true && data?.subscriptions?.followsMe === false) {
  731. await new Promise(resolve => setTimeout(resolve, 1000));
  732. unfollowUser(userId);
  733. }
  734. }
  735. } catch (error) {
  736. console.error(`Ошибка загрузки данных пользователя ${userId}:`, error);
  737. }
  738. }
  739. console.log('Обработка всех пользователей завершена.');
  740. alert('Обработка всех пользователей завершена.');
  741. }
  742.  
  743. function shouldSkipUser(lastVisit) {
  744. if (!lastVisit) return true;
  745. if (lastVisit.includes("Сейчас онлайн")) return false;
  746. if (lastVisit.includes("Был больше года назад")) return true;
  747. let monthMatch = lastVisit.match(/Был (\d+) (month|месяц)/);
  748. if (monthMatch) {
  749. let months = parseInt(monthMatch[1], 10);
  750. return months > 1;
  751. }
  752. return false;
  753. }
  754.  
  755. function parseCarId(carUrl) {
  756. let match = carUrl.match(/\/(\d+)\/?$/);
  757. return match ? match[1] : null;
  758. }
  759.  
  760. async function processPage() {
  761. let mainContainer = document.querySelector('.l-container div.g-column-mid');
  762. if (!mainContainer) {
  763. console.log('Основной контейнер не найден');
  764. return;
  765. }
  766.  
  767. let boxes = mainContainer.querySelectorAll('.x-box.o-f');
  768. boxes.forEach(box => {
  769. let userDivs = box.querySelectorAll('div > div');
  770. userDivs.forEach(div => {
  771. let userLink = div.querySelector('a.c-username');
  772. if (userLink) {
  773. let token = userLink.getAttribute('data-ihc-token');
  774. if (token) {
  775. users.add(token);
  776. }
  777. }
  778. div.remove();
  779. });
  780.  
  781. if (box.children.length === 0) {
  782. console.log('Удаляем пустой контейнер');
  783. box.remove();
  784. }
  785. });
  786.  
  787. updateInfoBox();
  788.  
  789. let loadMoreButton = document.querySelector('.x-box-more');
  790. if (loadMoreButton) {
  791. loadMoreButton.click();
  792. pagesProcessed++;
  793. updateInfoBox();
  794. await new Promise(resolve => setTimeout(resolve, 2000));
  795. processPage();
  796. } else {
  797. console.log(`Сбор завершен. Всего пользователей собрано: ${users.size}`);
  798. processUsers(Array.from(users));
  799. }
  800. }
  801.  
  802. processPage();
  803. }
  804.  
  805. checkButton.addEventListener('click', processBlocks);
  806.  
  807. loadMyCars();
  808. init_me();
  809.  
  810. if (isCarsFollowingPage()) {
  811. document.body.appendChild(statsDiv);
  812. updateStats();
  813. }
  814.  
  815. if (isSomeCarFollowingPage()) {
  816. addSubscribeButton();
  817. }
  818.  
  819. document.querySelector('.l-dv__i')?.remove();
  820. document.querySelector('.c-dv-side.o-row.o-sticky.o-f')?.remove();
  821.  
  822. async function autoLike() {
  823. const buttons = document.querySelectorAll('like-button'); // или '.like-button', если это класс
  824. for (const button of buttons) {
  825. let kind = button?.getAttribute('kind') ?? '';
  826. if (!button.hasAttribute('active') && button.hasAttribute('kind') && (kind === 'c' || kind === 'ubr' || kind === 'cjr')) {
  827. button.setAttribute('active', '');
  828. await new Promise(resolve => setTimeout(resolve, 500));
  829. likesSend(button.getAttribute('key'));
  830. }
  831. }
  832. }
  833. setInterval(autoLike, 3000);