Добавляет фильтр по минимальной и максимальной цене на страницу поиска Steam. Работает с бесконечной прокруткой и без перезагрузок.
// ==UserScript==
// @name Steam Custom Price Filter (RU) - v2.3
// @namespace http://tampermonkey.net/
// @version 2.3
// @description Добавляет фильтр по минимальной и максимальной цене на страницу поиска Steam. Работает с бесконечной прокруткой и без перезагрузок.
// @author torch
// @match https://store.steampowered.com/search/*
// @grant GM_addStyle
// @run-at document-end
// @license MIT
// ==/UserScript==
(function() {
'use strict';
let debounceTimer;
let observer; // Объявляем наблюдателя в глобальной области видимости скрипта
// Добавляем наш CSS-класс для скрытия элементов. !important гарантирует, что наш стиль будет приоритетнее.
GM_addStyle('.custom-price-hidden { display: none !important; }');
// --- Функция для создания UI фильтра ---
function createPriceFilterUI() {
const rightColumn = document.getElementById('additional_search_options');
if (!rightColumn) {
setTimeout(createPriceFilterUI, 500);
return;
}
if (document.getElementById('custom_price_filter_container')) return;
const filterContainer = document.createElement('div');
filterContainer.className = 'block search_collapse_block';
filterContainer.id = 'custom_price_filter_container';
filterContainer.innerHTML = `
<div class="block_header">
<div>Пользовательская цена</div>
</div>
<div class="block_content block_content_inner" style="display: block;">
<div id="custom_price_filter_inputs">
<input type="number" id="min_price_input" placeholder="От (руб.)" min="0" step="1">
<span>-</span>
<input type="number" id="max_price_input" placeholder="До (руб.)" min="0" step="1">
</div>
</div>
`;
rightColumn.prepend(filterContainer);
GM_addStyle(`
#custom_price_filter_inputs { display: flex; align-items: center; justify-content: space-between; gap: 5px; padding: 5px 0; }
#custom_price_filter_inputs input[type="number"] { width: 42%; padding: 5px; border: 1px solid #3d4450; background-color: #31363f; color: #c7d5e0; border-radius: 3px; }
#custom_price_filter_inputs input[type="number"]:focus { border-color: #5b9ed8; outline: none; }
#custom_price_filter_inputs input::-webkit-outer-spin-button,
#custom_price_filter_inputs input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
#custom_price_filter_inputs input[type=number] { -moz-appearance: textfield; }
`);
// Назначаем обработчики событий
const minPriceInput = document.getElementById('min_price_input');
const maxPriceInput = document.getElementById('max_price_input');
const handleInput = (event) => {
event.stopPropagation();
debounce(applyPriceFilter, 300);
};
const handleKeydown = (event) => event.stopPropagation();
minPriceInput.addEventListener('input', handleInput);
minPriceInput.addEventListener('keydown', handleKeydown);
maxPriceInput.addEventListener('input', handleInput);
maxPriceInput.addEventListener('keydown', handleKeydown);
}
function debounce(func, delay) {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(func, delay);
}
// --- Основная функция фильтрации ---
function applyPriceFilter() {
const minPriceInput = document.getElementById('min_price_input');
const maxPriceInput = document.getElementById('max_price_input');
if (!minPriceInput || !maxPriceInput) return;
const minPrice = minPriceInput.value ? parseFloat(minPriceInput.value) * 100 : null;
const maxPrice = maxPriceInput.value ? parseFloat(maxPriceInput.value) * 100 : null;
const resultsRows = document.querySelectorAll('#search_resultsRows > a.search_result_row');
resultsRows.forEach(row => {
const priceElement = row.querySelector('[data-price-final]');
const priceAttr = priceElement ? priceElement.getAttribute('data-price-final') : '0';
let itemPrice = 0;
if (priceAttr !== null && priceAttr !== "") {
const parsedPrice = parseInt(priceAttr, 10);
if (!isNaN(parsedPrice)) itemPrice = parsedPrice;
}
const isMinOk = minPrice === null || itemPrice >= minPrice;
const isMaxOk = maxPrice === null || itemPrice <= maxPrice;
if (isMinOk && isMaxOk) {
row.classList.remove('custom-price-hidden');
} else {
row.classList.add('custom-price-hidden');
}
});
}
// --- Функция для запуска наблюдателя ---
function startObserver() {
const targetNode = document.getElementById('search_results');
if (!targetNode) {
setTimeout(startObserver, 500);
return;
}
observer = new MutationObserver((mutations) => {
for(const mutation of mutations) {
// Мы реагируем, если были добавлены новые элементы (игры)
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
// Используем debounce, чтобы примениться один раз после завершения всех добавлений
debounce(applyPriceFilter, 150);
break;
}
}
});
// Наблюдаем за контейнером, в который Steam добавляет новые результаты
observer.observe(targetNode, { childList: true, subtree: true });
}
// --- Инициализация ---
// Ждем, пока страница полностью загрузится, чтобы все элементы были на месте
window.addEventListener('load', () => {
createPriceFilterUI();
startObserver();
// Применяем фильтр один раз после загрузки страницы
setTimeout(applyPriceFilter, 500);
});
})();