Drive2 Old Auto UnSubscriber

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

Versión del día 29/01/2025. Echa un vistazo a la versión más reciente.

// ==UserScript==
// @name         Drive2 Old Auto UnSubscriber
// @namespace    drive2.ru
// @version      0.74
// @description  работает на стренице подписок на сашины. проврка на бывшие авто и взаимные подписки, автоматически отписывается от бывших авто и от машин чьи владельцы не во взаимной подписке.
// @author       drive2 lover
// @match        https://www.drive2.ru/*/carsfollowing
// @match        https://www.drive2.com/*/carsfollowing
// @license MIT
// @grant        none

// ==/UserScript==

let tail = '';
let fctx = '';

const MY_CARS_KEY = 'my_cars';
const myCars = localStorage.getItem(MY_CARS_KEY) ? JSON.parse(localStorage.getItem(MY_CARS_KEY)) : false;

console.log('myCars', myCars);

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 = '10px';
statsDiv.style.zIndex = '1000';
document.body.appendChild(statsDiv);

let stats = {
    pages: 0,
    totalBlocks: 0,
    processedBlocks: 0,
    removedBlocks: 0,
    unsubscribedBlocks: 0,
    oldSubscribedBlocks: 0
};

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';

const updateStats = () => {
    statsDiv.innerHTML = `<div>
            Всего страниц: ${stats.pages}<br>
            Всего блоков: ${stats.totalBlocks}<br>
            Обработано: ${stats.processedBlocks}<br>
            Удалено: ${stats.removedBlocks}<br>
            Отписались: ${stats.unsubscribedBlocks}<br>
            Подписан на старые авто: ${stats.oldSubscribedBlocks}
        </div>`;
    statsDiv.appendChild(checkButton);
};

updateStats();

// Функция для поиска и клика по кнопке
const clickMoreButton = async () => {
    const button = document.querySelector('button.x-box-more');

    if (button) {
        stats.pages++;
        console.log('Загружаем страницу ' + stats.pages);
        button.click();

        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));
        }
        updateStats();

        await new Promise(resolve => setTimeout(resolve, 2000));
        processBlocks();

    } else {
        console.log('Кнопка не найдена, продолжаем проверку...');
    }
};

checkButton.addEventListener('click', clickMoreButton);

const unsubscribeCar = async (id) => {
    const url = '/ajax/subscription';
    const data = {
        _: 'unsubscribe',
        type: 'car',
        id: id, // Используем переданный параметр
        '.FCTX': '_wfqzlwoyisgAAUdQnJ1LldlYi4xOjg2NDY5MTEyODQ1NTEzODkzNTMBE9aV1TTAfio19mv_lNB3SPb24bE'
    };

    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(); // Если сервер вернул JSON
            console.log('Ответ сервера:', result);
        } else {
            console.error('Ошибка запроса:', response.status);
        }
    } catch (error) {
        console.error('Ошибка выполнения POST-запроса:', error);
    }
};

const processBlocks = async () => {
    const blocks = document.querySelectorAll('.c-car-card-sa');
    stats.totalBlocks = blocks.length;
    updateStats();
    myCars

    for (const block of blocks) {
        const titleElement = block.querySelector('.c-car-title');

        // Если блок уже подписан на старую машину, пропускаем его
        if (titleElement.classList.contains('x-secondary-color')) {
            stats.oldSubscribedBlocks++;
            stats.processedBlocks++;
            updateStats();
            continue;
        }

        const subscribeButton = block.querySelector('subscribe-button');
        const userId = block.querySelector('a.c-username').getAttribute('data-ihc-token');

        // Делаем GET-запрос для проверки подписки
        const response = await fetch(`https://www.drive2.com/_api/hovercards/${userId}?tail=${tail}`);
        const data = await response.json();

        const followedCarIds = data?.subscriptions?.followedCars?.map(car => {
            const match = car.url.match(/\/(\d+)\//);
            return match ? match[1] : null;
        }).filter(Boolean); // Убираем null
        const myCarIds = myCars.map(car => car.id);
        const hasMyCar = followedCarIds ? followedCarIds.some(carId => myCarIds.includes(carId)) : false;

        if (hasMyCar) {
            console.log('Блок содержит одну из моих машин, пропускаем.');
        } else {
            console.log('Блок не содержит моих машин, отписываемся.');
            unsubscribeCar(subscribeButton.getAttribute('uid'));
            stats.unsubscribedBlocks++;
        }

        // Удаляем блок и обновляем статистику
        block.remove();
        stats.removedBlocks++;
        stats.processedBlocks++;
        updateStats();

        // Ждём 1 секунду перед обработкой следующего блока
        await new Promise(resolve => setTimeout(resolve, 2000));
    }
    clickMoreButton();
};

function init_me() {
    // находим секретные ключи для автоматических запросов
    if (window.d2Env) {
        tail = window.d2Env.userId; // Получаем userId
        fctx = window.d2Env.formContext['.FCTX']; // Получаем FCTX

        console.log('userId:', tail);
        console.log('FCTX:', fctx);
    } else {
        alert('обновите версию скрипта!');
        return;
    }
}

init_me();