您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
работает на стренице подписок на сашины. проврка на бывшие авто и взаимные подписки, автоматически отписывается от бывших авто и от машин чьи владельцы не во взаимной подписке.
当前为
// ==UserScript== // @name Drive2 Old Auto UnSubscriber // @namespace drive2.com // @version 0.87 // @description работает на стренице подписок на сашины. проврка на бывшие авто и взаимные подписки, автоматически отписывается от бывших авто и от машин чьи владельцы не во взаимной подписке. // @author drive2 lover // @match https://www.drive2.com/* // @match https://www.drive2.ru/* // @license MIT // @grant none // ==/UserScript== let tail = ''; let fctx = ''; const MY_CARS_KEY = 'my_cars__'; let myCars = localStorage.getItem(MY_CARS_KEY) ? JSON.parse(localStorage.getItem(MY_CARS_KEY)) : false; const localStorageKey = 'saveLastPage'; let stats = { pages: 0, totalBlocks: 0, processedBlocks: 0, removedBlocks: 0, unsubscribedBlocks: 0, oldSubscribedBlocks: 0 }; // Создаем блок статистики const statsDiv = document.createElement('div'); statsDiv.style.position = 'fixed'; statsDiv.style.top = '0'; statsDiv.style.left = '0'; statsDiv.style.backgroundColor = '#333'; statsDiv.style.color = '#fff'; statsDiv.style.padding = '15px'; statsDiv.style.margin = '10px'; statsDiv.style.zIndex = '1000'; // Блок "Отписываться от авто" const unsubscribeCheckboxDiv = document.createElement('div'); const unsubscribeCheckbox = document.createElement('input'); unsubscribeCheckbox.type = 'checkbox'; unsubscribeCheckbox.checked = true; unsubscribeCheckbox.id = 'unsubscribe-checkbox'; const unsubscribeLabel = document.createElement('label'); unsubscribeLabel.textContent = ' Отписываться от авто'; unsubscribeLabel.style.marginRight = '10px'; unsubscribeLabel.setAttribute('for', 'unsubscribe-checkbox'); unsubscribeCheckboxDiv.appendChild(unsubscribeCheckbox); unsubscribeCheckboxDiv.appendChild(unsubscribeLabel); // statsDiv.appendChild(unsubscribeCheckboxDiv); // Блок "Скрывать старые авто" const hideOldCarsCheckboxDiv = document.createElement('div'); const hideOldCarsCheckbox = document.createElement('input'); hideOldCarsCheckbox.type = 'checkbox'; hideOldCarsCheckbox.checked = true; hideOldCarsCheckbox.id = 'hide-old-cars-checkbox'; const hideOldCarsLabel = document.createElement('label'); hideOldCarsLabel.textContent = ' Скрывать старые авто'; hideOldCarsLabel.style.marginRight = '10px'; hideOldCarsLabel.setAttribute('for', 'hide-old-cars-checkbox'); hideOldCarsCheckboxDiv.appendChild(hideOldCarsCheckbox); hideOldCarsCheckboxDiv.appendChild(hideOldCarsLabel); // statsDiv.appendChild(hideOldCarsCheckboxDiv); // Кнопка "Проверить" const checkButton = document.createElement('button'); checkButton.innerText = 'Проверить'; checkButton.style.marginTop = '10px'; checkButton.style.padding = '5px 10px'; checkButton.style.backgroundColor = '#007bff'; checkButton.style.color = '#fff'; checkButton.style.border = 'none'; checkButton.style.borderRadius = '5px'; checkButton.style.cursor = 'pointer'; // statsDiv.appendChild(checkButton); // Блок для ввода страниц и кнопки "Пропустить страниц" const skipPagesDiv = document.createElement('div'); skipPagesDiv.style.marginTop = '10px'; // Поле ввода количества страниц const pagesInput = document.createElement('input'); pagesInput.type = 'number'; pagesInput.min = '1'; pagesInput.value = localStorage.getItem(localStorageKey) ?? 1; pagesInput.style.width = '50px'; pagesInput.style.marginRight = '10px'; // Кнопка "Пропустить страниц" const skipButton = document.createElement('button'); skipButton.innerText = 'Пропустить страниц'; skipButton.style.padding = '5px 10px'; skipButton.style.backgroundColor = '#dc3545'; skipButton.style.color = '#fff'; skipButton.style.border = 'none'; skipButton.style.borderRadius = '5px'; skipButton.style.cursor = 'pointer'; // Добавляем инпут и кнопку в блок skipPagesDiv.appendChild(pagesInput); skipPagesDiv.appendChild(skipButton); //statsDiv.appendChild(skipPagesDiv); const carBlock = document.querySelector('.l-container .u-link-area'); const carBlockClass = carBlock?.parentElement?.classList?.[0] ?? null; const carsBlockClass = carBlock?.parentElement?.parentElement?.className ?? null; const carsBlockClassFormatted = carsBlockClass?.split(' ').map(className => '.' + className).join('') ?? ''; console.log('carBlockClass ' + carBlockClass); console.log('carsBlockClassFormatted ' + carsBlockClassFormatted); async function init_me() { if (window.d2Env) { tail = window.d2Env.userId; fctx = window.d2Env.formContext['.FCTX']; } else { alert('обновите версию скрипта!'); return; } } // Функция для нажатия кнопки загрузки страниц и очистки элементов async function skipPages() { let pagesToSkip = parseInt(pagesInput.value, 10); if (isNaN(pagesToSkip) || pagesToSkip <= 0) { alert('Введите корректное число страниц'); return; } for (let i = 0; i < pagesToSkip; i++) { const loadMoreButton = document.querySelector('button.x-box-more'); if (loadMoreButton) { await clearGrid(); loadMoreButton.click(); console.log(`Нажатие ${i + 1} на кнопку "Показать ещё"`); await new Promise(resolve => setTimeout(resolve, 3000)); // Ждём 3 секунды stats.pages++; updateStats(); await clearEmptyBlocks(); } else { alert('Кнопка "Показать ещё" не найдена, остановка.'); break; } } } // Функция очистки блока .o-grid.o-grid--2.o-grid--equal async function clearGrid() { const grids = document.querySelectorAll(carsBlockClassFormatted); if (grids.length > 0) { let blocks; for (const grid of grids) { blocks = grid.querySelectorAll('.' + carBlockClass); if (blocks.length) { stats.processedBlocks += blocks.length; blocks.forEach(car => car.remove()); } } console.log('Удалены авто из пропущенных страниц.'); } else { console.log('Не найдено блоков для очистки.'); } } skipButton.addEventListener('click', skipPages); const updateStats = () => { statsDiv.innerHTML = `<div> Страниц пройдено: ${stats.pages}<br> Иконок авто в очереди: ${stats.totalBlocks}<br> Обработано: ${stats.processedBlocks}<br> Отписались автоматом: ${stats.unsubscribedBlocks}<br> Подписан на старые авто: ${stats.oldSubscribedBlocks} </div>`; statsDiv.appendChild(unsubscribeCheckboxDiv); statsDiv.appendChild(hideOldCarsCheckboxDiv); statsDiv.appendChild(checkButton); statsDiv.appendChild(skipPagesDiv); localStorage.setItem(localStorageKey, stats.pages); }; // Функция для поиска и клика по кнопке "Загрузить еще" const clickMoreButton = async () => { const button = document.querySelector('button.x-box-more'); if (button) { console.error('Нашли кнопку дальнейшей загрузки'); stats.pages++; console.log('Загружаем страницу ' + stats.pages); button.click(); updateStats(); await clearEmptyBlocks(); await new Promise(resolve => setTimeout(resolve, 3000)); console.log('Загрузили блоки с авто, приступаем к их обработке'); processBlocks(); } else { alert('Кнопка не найдена, остановка процесса'); } }; async function clearEmptyBlocks() { document.querySelectorAll(carsBlockClassFormatted).forEach(grid => {if (!grid.querySelector('div')) { grid.remove(); }}); } async function loadMyCars() { if (!localStorage.getItem(MY_CARS_KEY)) { const response = await fetch('/my/r/'); const html = await response.text(); const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const cars = []; doc.querySelectorAll('.c-car-draglist__item .c-car-card__caption a').forEach(car => { if (!car.classList.contains('x-secondary-color')) { const id = car.href.match(/\/(\d+)\//)[1]; cars.push({ id, name: car.textContent.trim() }); } }); localStorage.setItem(MY_CARS_KEY, JSON.stringify(cars)); console.log('обновил кеш моих авто', cars); } else { console.log('кеш моих авто уже загружен', myCars); } } function addCloseButton(element) { if (!element) return; const closeButton = document.createElement('button'); closeButton.innerHTML = '×'; // Символ "×" (крестик) closeButton.style.position = 'absolute'; closeButton.style.top = '5px'; closeButton.style.right = '5px'; closeButton.style.background = 'red'; closeButton.style.color = 'white'; closeButton.style.border = 'none'; closeButton.style.padding = '5px 10px'; closeButton.style.cursor = 'pointer'; closeButton.style.fontSize = '16px'; closeButton.style.borderRadius = '50%'; closeButton.addEventListener('click', function () { this.parentNode.remove(); // Удаляет родительский элемент кнопки }); if (window.getComputedStyle(element).position === 'static') { element.style.position = 'relative'; } element.appendChild(closeButton); element.classList.remove(carBlockClass); element.querySelector('a.u-link-area').remove(); element.style.position = 'sticky'; } const unsubscribeCar = async (id) => { const url = '/ajax/subscription'; const data = { _: 'unsubscribe', type: 'car', id: id, '.FCTX': fctx }; try { const response = await fetch(url, { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: new URLSearchParams(data).toString() }); if (response.ok) { const result = await response.json(); console.log('Ответ unsubscribeCar:', result); return result; } else { console.error('Ошибка запроса:', response.status); } } catch (error) { console.error('Ошибка выполнения POST-запроса:', error); } }; const followUser = async (id) => { const url = '/ajax/subscription'; const data = { _: 'subscribe', type: 'user', id: id, '.FCTX': fctx }; try { const response = await fetch(url, { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: new URLSearchParams(data).toString() }); if (response.ok) { const result = await response.json(); console.log('Ответ subscribeCar:', result); return result; } else { console.error('Ошибка запроса:', response.status); } } catch (error) { console.error('Ошибка выполнения POST-запроса:', error); } }; const unfollowUser = async (id) => { const url = '/ajax/subscription'; const data = { _: 'unsubscribe', type: 'user', id: id, '.FCTX': fctx }; try { const response = await fetch(url, { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: new URLSearchParams(data).toString() }); if (response.ok) { const result = await response.json(); return result?.success; } else { console.error('Ошибка запроса:', response.status); } } catch (error) { console.error('Ошибка выполнения POST-запроса:', error); } }; const shareCar = async (token, comment) => { const url = '/_api/share'; const data = { token: token, comment: comment, '.FCTX': fctx }; try { const response = await fetch(url, { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: new URLSearchParams(data).toString() }); if (response.ok) { const result = await response.json(); return result?.success; } else { console.error('Ошибка запроса:', response.status); } } catch (error) { console.error('Ошибка выполнения POST-запроса:', error); } }; const likesCar = async (token) => { const url = '/_api/likes'; const data = { token: token, '.FCTX': fctx }; try { const response = await fetch(url, { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: new URLSearchParams(data).toString() }); if (response.ok) { const result = await response.json(); console.log('Ответ like:', result); return result; } else { console.error('Ошибка запроса:', response.status); } } catch (error) { console.error('Ошибка выполнения POST-запроса:', error); } }; const subscribeCar = async (id) => { const url = '/ajax/subscription'; const data = { _: 'subscribe', type: 'car', id: id, '.FCTX': fctx }; try { const response = await fetch(url, { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: new URLSearchParams(data).toString() }); if (response.ok) { const result = await response.json(); console.log('Ответ subscribeCar:', result); return result; } else { console.error('Ошибка запроса:', response.status); } } catch (error) { console.error('Ошибка выполнения POST-запроса:', error); } }; async function loadUser(userId) { const response = await fetch(`/_api/hovercards/${userId}?tail=${tail}`); return await response.json(); } function isUserSubscribedOnMyCars(data) { const followedCarIds = data?.subscriptions?.followedCars?.map(car => { const match = car.url.match(/\/(\d+)\//); return match ? match[1] : null; }).filter(Boolean); const myCarIds = myCars ? myCars.map(car => car.id) : []; if (myCarIds.length == 0) { console.error('У текущего юзера не найдены авто'); return false; } if (followedCarIds ? followedCarIds.some(carId => myCarIds.includes(carId)) : false) { return myCars .filter(car => followedCarIds.includes(car.id)) .map(car => car.name) .join(', '); } return false; } const processBlocks = async () => { const blocks = document.querySelectorAll('.' + carBlockClass); console.error('На странице найдено ' + blocks.length + ' авто'); updateStats(); let myCarNames = ''; for (const block of blocks) { const titleElement = block.querySelector('.c-car-title'); stats.totalBlocks = document.querySelectorAll('.' + carBlockClass).length; // Если чекбокс скрытия старых авто включен и блок старый – пропускаем или удаляем if (titleElement.classList.contains('x-secondary-color')) { stats.oldSubscribedBlocks++; stats.processedBlocks++; if (hideOldCarsCheckbox.checked) { block.remove(); stats.removedBlocks++; console.error('Старое авто, удаляем'); } else { console.error('Старое авто, оставляем на странице'); addCloseButton(block); } updateStats(); continue; } const subscribeButton = block.querySelector('subscribe-button'); const userId = block.querySelector('a.c-username')?.getAttribute('data-ihc-token'); if (!userId) { console.error('Не найден userId, пропускаем'); continue }; const data = await loadUser(userId); const myCarNames = isUserSubscribedOnMyCars(data); if (myCarNames) { console.log(`Юзер номер ${userId} подписан на (${myCarNames}), пропускаем.`); } else { let uid = subscribeButton.getAttribute('uid'); console.log('Юзер номер ' + userId + ' не подписан на мои авто.'); if (unsubscribeCheckbox.checked) { unsubscribeCar(uid); stats.unsubscribedBlocks++; console.error('Отписываемся от авто с номером ' + uid); } } // Удаляем блок, если он не содержит мою машину block.remove(); stats.removedBlocks++; stats.processedBlocks++; updateStats(); // Ждём 1 секунду перед обработкой следующего блока await new Promise(resolve => setTimeout(resolve, 2000)); } console.error('Обработали все авто'); clickMoreButton(); }; function isCarsFollowingPage() { return (/^\/users\/(.*)\/carsfollowing/).test(window.location.pathname); } function isSomeCarFollowingPage() { return (/^\/r\/(.*)\/followers/).test(window.location.pathname); } function addSubscribeButton() { const counterElement = document.querySelector('.x-title-header .x-title .c-counter'); if (counterElement) { const button = document.createElement('button'); button.textContent = 'Подписаться на эти авто'; button.classList.add('c-button'); button.classList.add('c-button--primary'); button.style.marginLeft = '10px'; button.addEventListener('click', function () { scrapeUsers(); }); counterElement.after(button); } } async function scrapeUsers() { let users = new Set(); let usersDone = 0; let pagesProcessed = 0; let subscribeToCar = 0; let lostUsers = 0; let lostSubscribe = 0; let repostCar = 0; let infoBox = document.createElement('div'); infoBox.style.position = 'fixed'; infoBox.style.top = '10px'; infoBox.style.right = '10px'; infoBox.style.background = 'rgb(51, 51, 51)'; infoBox.style.color = 'white'; infoBox.style.padding = '10px'; infoBox.style.borderRadius = '5px'; infoBox.style.zIndex = '1000'; document.body.appendChild(infoBox); function updateInfoBox() { let progress = users.size > 0 ? Math.round((usersDone / users.size) * 100) : 0; infoBox.innerHTML = `Страниц обработано: ${pagesProcessed} <br> Пользователей собрано: ${users.size} <br> Пользователей обработано: ${usersDone} <br> Пользователей пропущено: ${lostUsers}<br> (посещали сайт > чем месяц назад или нет актуальных авто) <br> Подписано на авто: ${subscribeToCar}<br> (даже если уже был на авто подписан) <br> Не удалось подписаться: ${lostSubscribe} <br> Репост: ${repostCar} <br> <div style='width: 100%; background: #555; height: 10px; border-radius: 5px; margin-top: 5px;'> <div style='width: ${progress}%; background: #4caf50; height: 10px; border-radius: 5px;'></div> </div>`; } async function likeCar(carUrl, fullCaption, myCarNames) { try { await new Promise(resolve => setTimeout(resolve, 1000)); let response = await fetch(carUrl); let html = await response.text(); let parser = new DOMParser(); let doc = parser.parseFromString(html, 'text/html'); let likeButton = doc.querySelector('like-button'); if (likeButton) { let active = likeButton.hasAttribute('active'); let disabled = likeButton.hasAttribute('disabled'); let key = likeButton.getAttribute('key'); if (key && !active && !disabled) { await new Promise(resolve => setTimeout(resolve, 1000)); likesCar(key); } } let subscribeButton = doc.querySelector('subscribe-button'); if (subscribeButton && !subscribeButton.hasAttribute('subscribed') && subscribeButton.hasAttribute('uid')) { await new Promise(resolve => setTimeout(resolve, 1000)); let result = await subscribeCar(subscribeButton.getAttribute('uid')); if (result?.types.length) { subscribeToCar++; } else { lostSubscribe++; } } let repostButton = doc.querySelector('repost-button'); if (myCarNames && repostButton && !repostButton.hasAttribute('disabled') && repostButton.hasAttribute('token')) { await new Promise(resolve => setTimeout(resolve, 1000)); let symbolsToReplace = ['🇷🇺', '☭']; let result = await shareCar(repostButton.getAttribute('token'), 'Подписывайтесь на ' + fixString(fullCaption)); if (result) { repostCar++; } } } catch (error) { console.error(`Ошибка загрузки страницы авто ${carUrl}:`, error); } return null; } function fixString(str) { let symbolsToReplace = ['🇷🇺', '☭']; return symbolsToReplace.reduce((acc, symbol) => acc.replaceAll(symbol, '🇺🇦'), str); } async function processUsers(userList) { for (let userId of userList) { try { usersDone++; await new Promise(resolve => setTimeout(resolve, 1000)); const data = await loadUser(userId); if (data?.lastVisit && shouldSkipUser(data.lastVisit)) { console.log(`Пропускаем пользователя ${data.nickname} (${userId}) из-за даты последнего визита: ${data.lastVisit}`); lostUsers++; continue; } const myCarNames = isUserSubscribedOnMyCars(data); if (data?.cars) { for (let car of data.cars) { if (car.belongState === "My") { let carId = parseCarId(car.url); if (carId) { console.log(`Проверяем авто: ${car.fullCaption}`); await likeCar(car.url, car.fullCaption, myCarNames); updateInfoBox(); } } } } else { lostUsers++; console.log('Не найдено авто у ' + userId); } if (data?.isFollowable) { if (data?.isFollowed === false && data?.subscriptions?.followsMe === true) { await new Promise(resolve => setTimeout(resolve, 1000)); followUser(userId); } if (data?.isFollowed === true && data?.subscriptions?.followsMe === false) { await new Promise(resolve => setTimeout(resolve, 1000)); unfollowUser(userId); } } } catch (error) { console.error(`Ошибка загрузки данных пользователя ${userId}:`, error); } } console.log('Обработка всех пользователей завершена.'); alert('Обработка всех пользователей завершена.'); } function shouldSkipUser(lastVisit) { if (!lastVisit) return true; if (lastVisit.includes("Сейчас онлайн")) return false; if (lastVisit.includes("Был больше года назад")) return true; let monthMatch = lastVisit.match(/Был (\d+) (month|месяц)/); if (monthMatch) { let months = parseInt(monthMatch[1], 10); return months > 1; } return false; } function parseCarId(carUrl) { let match = carUrl.match(/\/(\d+)\/?$/); return match ? match[1] : null; } async function processPage() { let mainContainer = document.querySelector('.l-page-main.g-column-mid'); if (!mainContainer) { console.log('Основной контейнер не найден'); return; } let boxes = mainContainer.querySelectorAll('.x-box.o-f'); boxes.forEach(box => { let userDivs = box.querySelectorAll('div > div'); userDivs.forEach(div => { let userLink = div.querySelector('a.c-username'); if (userLink) { let token = userLink.getAttribute('data-ihc-token'); if (token) { users.add(token); } } div.remove(); }); if (box.children.length === 0) { console.log('Удаляем пустой контейнер'); box.remove(); } }); updateInfoBox(); let loadMoreButton = document.querySelector('.x-box-more'); if (loadMoreButton) { loadMoreButton.click(); pagesProcessed++; updateInfoBox(); await new Promise(resolve => setTimeout(resolve, 2000)); processPage(); } else { console.log(`Сбор завершен. Всего пользователей собрано: ${users.size}`); processUsers(Array.from(users)); } } processPage(); } checkButton.addEventListener('click', processBlocks); loadMyCars(); init_me(); if (isCarsFollowingPage()) { document.body.appendChild(statsDiv); updateStats(); } if (isSomeCarFollowingPage()) { addSubscribeButton(); } document.querySelector('.l-dv__i')?.remove(); document.querySelector('.c-dv-side.o-row.o-sticky.o-f')?.remove();