Drive2 Old Auto UnSubscriber

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

目前为 2025-03-17 提交的版本,查看 最新版本

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