Скрывает видео в VK по количеству просмотров (минимум, максимум, диапазон). Ищет по тексту, игнорирует фреймы.
// ==UserScript==
// @name VK Video Filter (Text-Based Scanner)
// @namespace http://tampermonkey.net/
// @version 4.2
// @description Скрывает видео в VK по количеству просмотров (минимум, максимум, диапазон). Ищет по тексту, игнорирует фреймы.
// @author torch
// @match https://vk.com/*
// @match https://vkvideo.ru/*
// @match https://vksport.vkvideo.ru/*
// @icon https://vk.com/images/icons/favicons/fav_logo_2x.ico
// @grant none
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
// Защита от запуска внутри скрытых фреймов
if (window.top !== window.self) {
return;
}
const CONFIG_KEY = 'vk_vf_4_settings';
// --- State ---
let config = {
minViews: 10000,
maxViews: 0, // 0 = нет ограничений
isEnabled: true,
debugMode: false
};
// Загрузка настроек
try {
const saved = localStorage.getItem(CONFIG_KEY);
if (saved) config = { ...config, ...JSON.parse(saved) };
} catch (e) {}
// --- Helpers ---
const log = (msg, color = '#0f0') => {
console.log(`%c[VK Filter] ${msg}`, `color: ${color}; background: #222; padding: 2px;`);
};
// Парсер: ищет число перед словом "просмотров"
function extractViewsFromText(fullText) {
if (!fullText) return -1;
// Нормализация текста
let text = fullText.toLowerCase()
.replace(/[\n\r]/g, ' ')
.replace(/ /g, ' ')
.replace(/\u00A0/g, ' ');
// Убираем пробелы МЕЖДУ цифрами (например, "1 250" -> "1250")
text = text.replace(/(\d)\s+(?=\d)/g, '$1');
// Сжимаем множественные пробелы
text = text.replace(/\s+/g, ' ');
// Регулярка
const regex = /([\d,.]+)\s*(тыс|млн|млрд|k|m|b)?\.?\s*просмотр/gi;
const matches = [...text.matchAll(regex)];
if (matches.length === 0) return -1;
// Берем последнее совпадение (защита от "просмотров" в названии видео)
const match = matches[matches.length - 1];
let numStr = match[1].replace(',', '.'); // 1,5 -> 1.5
let num = parseFloat(numStr);
const multiplier = match[2];
if (multiplier) {
if (multiplier.startsWith('тыс') || multiplier === 'k') num *= 1000;
else if (multiplier.startsWith('млн') || multiplier === 'm') num *= 1000000;
else if (multiplier.startsWith('млрд') || multiplier === 'b') num *= 1000000000;
}
return Math.round(num);
}
// --- Core Logic ---
function scanAndHide() {
if (!config.isEnabled && !config.debugMode) return;
const selector = [
'[data-testid="video_card_layout"]', // Новый дизайн VK Video
'.VideoCard', // Старый дизайн
'.video_item', // Классический ВК
'.vkitVideoCardLayout__card--xI1tS', // Обфусцированные классы
'.VideoCardList__videoItem--VPDyl' // Контейнер списка
].join(',');
const cards = document.querySelectorAll(selector);
const processHash = `${config.minViews}_${config.maxViews}`;
cards.forEach(card => {
// Пропускаем уже обработанные (учитываем хэш настроек, чтобы при смене От/До перепроверить все)
if (card.dataset.vvfProcessed === processHash && !config.debugMode) return;
// Ищем текст карточки
const cardText = card.innerText || card.textContent;
const views = extractViewsFromText(cardText);
if (views === -1) {
if (config.debugMode) {
card.style.border = '2px dashed orange';
card.title = `[VK Filter] Не нашел слово "просмотров" в тексте:\n${cardText.substring(0, 50)}...`;
}
return;
}
// Логика фильтрации (Диапазон, Мин, Макс)
let shouldHide = false;
let hideReason = '';
if (config.minViews > 0 && views < config.minViews) {
shouldHide = true;
hideReason = `Меньше минимума (${config.minViews})`;
} else if (config.maxViews > 0 && views > config.maxViews) {
shouldHide = true;
hideReason = `Больше максимума (${config.maxViews})`;
}
// Применяем стили скрытия
if (shouldHide) {
if (config.debugMode) {
card.style.border = '4px solid red';
card.style.opacity = '0.5';
card.style.display = '';
card.title = `[VK Filter] ПРОСМОТРОВ: ${views} (${hideReason})`;
} else {
card.style.display = 'none';
card.style.border = '';
}
} else {
card.style.display = '';
if (config.debugMode) {
card.style.border = '4px solid green';
card.style.opacity = '1';
card.title = `[VK Filter] ПРОСМОТРОВ: ${views} (Попадает в фильтр)`;
} else {
card.style.border = '';
}
}
card.dataset.vvfProcessed = processHash;
});
}
function resetAll() {
const cards = document.querySelectorAll('[data-testid="video_card_layout"], .VideoCard, .video_item, [class*="VideoCard"]');
cards.forEach(c => {
c.style.display = '';
c.style.border = '';
c.style.opacity = '';
delete c.dataset.vvfProcessed;
});
}
// --- UI Construction ---
function buildUI() {
if (document.getElementById('vvf-root')) return;
const root = document.createElement('div');
root.id = 'vvf-root';
root.innerHTML = `
<style>
#vvf-btn {
position: fixed; bottom: 20px; left: 20px; width: 50px; height: 50px;
background: #2D2D2D; border: 2px solid #555; border-radius: 50%;
color: white; display: flex; align-items: center; justify-content: center;
cursor: pointer; z-index: 9999999; font-size: 24px; user-select: none;
transition: 0.2s;
}
#vvf-btn:hover { background: #444; transform: scale(1.05); }
#vvf-menu {
position: fixed; bottom: 80px; left: 20px; width: 280px;
background: #191919; color: #eee; padding: 20px; border-radius: 16px;
z-index: 9999999; display: none; border: 1px solid #444;
font-family: -apple-system, system-ui, sans-serif;
box-shadow: 0 10px 40px rgba(0,0,0,0.8);
}
.vvf-row { margin-bottom: 15px; display: flex; align-items: center; justify-content: space-between; }
.vvf-title { font-size: 16px; font-weight: bold; margin-bottom: 15px; display: block; color: #fff; }
.vvf-input {
background: #333; border: 1px solid #555; color: white;
padding: 8px; border-radius: 8px; width: 45%; font-size: 14px; box-sizing: border-box;
}
.vvf-btn-action {
width: 100%; padding: 10px; background: #0077FF; color: white;
border: none; border-radius: 8px; cursor: pointer; font-weight: 600;
font-size: 14px;
}
.vvf-btn-action:hover { background: #0066dd; }
.vvf-chk { transform: scale(1.3); }
.vvf-hint { font-size: 11px; color: #888; text-align: center; margin-bottom: 15px; margin-top: -10px; }
</style>
<div id="vvf-btn">👁️</div>
<div id="vvf-menu">
<span class="vvf-title">Фильтр Просмотров v4.2</span>
<div class="vvf-row">
<label>Включено</label>
<input type="checkbox" id="vvf-enabled" class="vvf-chk" ${config.isEnabled ? 'checked' : ''}>
</div>
<div class="vvf-row">
<label style="color:#fa0">Режим отладки<br><span style="font-size:10px; color:#888">(рамки вместо скрытия)</span></label>
<input type="checkbox" id="vvf-debug" class="vvf-chk" ${config.debugMode ? 'checked' : ''}>
</div>
<div class="vvf-row" style="margin-bottom: 5px;">
<label>Диапазон (оставьте 0, если не нужно)</label>
</div>
<div class="vvf-row" style="justify-content: flex-start; gap: 10px;">
<input type="number" id="vvf-min" class="vvf-input" placeholder="От" value="${config.minViews || ''}" title="Скрывать видео, где просмотров меньше чем">
<span style="color: #666;">—</span>
<input type="number" id="vvf-max" class="vvf-input" placeholder="До" value="${config.maxViews || ''}" title="Скрывать видео, где просмотров больше чем">
</div>
<div class="vvf-hint">Например: От 10000 До 0</div>
<button id="vvf-save" class="vvf-btn-action">Применить</button>
</div>
`;
document.body.appendChild(root);
const btn = document.getElementById('vvf-btn');
const menu = document.getElementById('vvf-menu');
const save = document.getElementById('vvf-save');
btn.onclick = () => { menu.style.display = menu.style.display === 'none' ? 'block' : 'none'; };
save.onclick = () => {
config.isEnabled = document.getElementById('vvf-enabled').checked;
config.debugMode = document.getElementById('vvf-debug').checked;
// Читаем значения. Пустое поле, отрицательное число или NaN превращаем в 0
const rawMin = parseInt(document.getElementById('vvf-min').value);
const rawMax = parseInt(document.getElementById('vvf-max').value);
config.minViews = isNaN(rawMin) || rawMin < 0 ? 0 : rawMin;
config.maxViews = isNaN(rawMax) || rawMax < 0 ? 0 : rawMax;
// Обновляем визуально поля, если пользователь ввел дичь (минусы, буквы)
document.getElementById('vvf-min').value = config.minViews || '';
document.getElementById('vvf-max').value = config.maxViews || '';
localStorage.setItem(CONFIG_KEY, JSON.stringify(config));
resetAll();
scanAndHide();
};
}
// --- Init ---
log('Скрипт v4.2 загружен');
buildUI();
// Запускаем цикл проверки
setInterval(scanAndHide, 1000);
})();