работает на стренице подписок на сашины. проврка на бывшие авто и взаимные подписки, автоматически отписывается от бывших авто и от машин чьи владельцы не во взаимной подписке.
目前為
// ==UserScript==
// @name Drive2 Old Auto UnSubscriber
// @namespace drive2.com
// @version 0.83
// @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('') ?? '';
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));
alert('обновил кеш моих авто', cars);
} else {
alert('кеш моих авто уже загружен', 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('Ответ сервера:', 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 ? 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);
loadMyCars();
init_me();
if (isCarsFollowingPage()) {
document.body.appendChild(statsDiv);
updateStats();
}