Drive2 Old Auto UnSubscriber

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

当前为 2025-02-17 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Drive2 Old Auto UnSubscriber
// @namespace    drive2.com
// @version      0.81
// @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';
const 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(' ') ?? '';

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 {
            console.log('Кнопка "Показать ещё" не найдена, остановка.');
            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();
        await loadMyCars();

        updateStats();
        await clearEmptyBlocks();
        await new Promise(resolve => setTimeout(resolve, 3000));
        console.log('Загрузили блоки с авто, приступаем к их обработке');
        processBlocks();
    } else {
        console.log('Кнопка не найдена, остановка процесса');
    }
};

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

function addCloseButton(element) {
    if (!element) return;
    const closeButton = document.createElement('button');
    closeButton.innerHTML = '&times;'; // Символ "×" (крестик)
    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('Ответ сервера:', result);
        } else {
            console.error('Ошибка запроса:', response.status);
        }
    } catch (error) {
        console.error('Ошибка выполнения POST-запроса:', error);
    }
};

const processBlocks = async () => {
    const blocks = document.querySelectorAll('.' + carBlockClass);
    console.error('На странице найдено ' + blocks.length + ' авто');
    updateStats();

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

        // Делаем GET-запрос для проверки подписки
        const response = await fetch(`/_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);
        const myCarIds = myCars.map(car => car.id);
        const hasMyCar = followedCarIds ? followedCarIds.some(carId => myCarIds.includes(carId)) : false;

        if (hasMyCar) {
            console.log('Блок ' + userId + ' содержит одну из моих машин, пропускаем.');
        } 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);
}

checkButton.addEventListener('click', processBlocks);

init_me();

if (isCarsFollowingPage()) {
    document.body.appendChild(statsDiv);
    updateStats();
}