您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds price sorting functionality to Untappd with full filter support
// ==UserScript== // @name Untappd Price Sorter // @name:ru Untappd Price Sorter - Сортировка по цене // @namespace http://tampermonkey.net/ // @description Adds price sorting functionality to Untappd with full filter support // @description:ru Добавляет сортировку по цене на сайте Untappd с полной поддержкой фильтров // @version 1.1 // @author Levi Somerset // @match https://untappd.com/v/* // @match https://untappd.com/venue/* // @grant none // @run-at document-end // @license MIT // @icon https://untappd.com/assets/favicon-32x32-v2.png // @noframes // ==/UserScript== (function() { 'use strict'; // Определение языка интерфейса function detectLanguage() { // Простой способ определить язык по тексту в интерфейсе const menuText = $('.menu-sorting li').first().text().trim(); return menuText.match(/[а-яА-Я]/) ? 'ru' : 'en'; } // Локализованные строки const texts = { en: { lowToHigh: 'By Price (Low to High)', highToLow: 'By Price (High to Low)' }, ru: { lowToHigh: 'По цене (от низкой к высокой)', highToLow: 'По цене (от высокой к низкой)' } }; // Переменные для хранения состояния сортировки let priceSort = { active: false, ascending: true }; // Ждем загрузки страницы и jQuery function waitForElements() { if (typeof $ !== 'undefined' && $('.menu-sorting').length > 0) { initPriceSorting(); } else { setTimeout(waitForElements, 500); } } // Функция извлечения цены из элемента меню function extractPrice(item) { const priceText = $(item).find('.price').first().text().trim(); if (!priceText) return Number.MAX_VALUE; const match = priceText.match(/(\d+[\.,]?\d*)/); if (match && match[1]) { return parseFloat(match[1].replace(',', '.')); } return Number.MAX_VALUE; } // Функция для получения уникального идентификатора элемента function getItemIdentifier(item) { const beerName = $(item).find('.beer-details h5 a').first().text().trim(); const brewery = $(item).find('.beer-details h6 a').first().text().trim(); const size = $(item).find('.size').first().text().trim(); return `${beerName}_${brewery}_${size}`; } // Модификация функции отправки запроса function patchSendRequest() { // Сохраняем оригинальные функции const originalSendRequest = window._sendRequest; const originalSendPaginatedRequest = window._sendPaginatedRequest; // Переопределяем функцию отправки запроса window._sendRequest = function(url, type) { originalSendRequest.call(this, url, type); // Если активна сортировка по цене, применяем её после загрузки данных if (priceSort.active) { $(document).one('ajaxComplete', function(event, xhr, settings) { if (settings.url.includes('apireqs')) { setTimeout(function() { sortElements(); }, 800); } }); } }; // Переопределяем функцию пагинации window._sendPaginatedRequest = function(url, type) { originalSendPaginatedRequest.call(this, url, type); // Если активна сортировка по цене, сортируем новые элементы if (priceSort.active) { $(document).one('ajaxComplete', function(event, xhr, settings) { if (settings.url.includes('apireqs')) { setTimeout(function() { sortElements(); }, 800); } }); } }; } // Основная функция сортировки элементов function sortElements() { // Проверяем режим отображения if ($('.sort-filter-results').is(':visible')) { // Режим фильтрации - здесь все элементы в одном контейнере const container = $('.sort-filter-results'); const listContainer = container.find('ul.menu-section-list'); if (listContainer.length > 0) { // Сортировка элементов внутри списка const items = listContainer.children('li.sorting-item').toArray(); if (items.length > 0) { sortAndReplace(items, listContainer); } } else { // Если нет списка, обрабатываем элементы непосредственно в контейнере const items = container.children('li.sorting-item').toArray(); if (items.length > 0) { sortAndReplace(items, container); } } } else { // Стандартный режим - множество секций с отдельными списками $('.menu-section').each(function() { const section = $(this); const sectionList = section.find('ul.menu-section-list'); if (sectionList.length > 0) { const items = sectionList.children('li.menu-item').toArray(); if (items.length > 0) { sortAndReplace(items, sectionList); } } }); } } // Функция сортировки элементов и их замены в контейнере function sortAndReplace(items, container) { // Запоминаем положение прокрутки const scrollPosition = $(window).scrollTop(); // Сортируем элементы по цене items.sort(function(a, b) { const priceA = extractPrice(a); const priceB = extractPrice(b); return priceSort.ascending ? priceA - priceB : priceB - priceA; }); // Удаляем дубликаты const uniqueItems = []; const seenItems = new Set(); for (const item of items) { const identifier = getItemIdentifier(item); if (!seenItems.has(identifier)) { seenItems.add(identifier); uniqueItems.push(item); } } // Очищаем контейнер container.empty(); // Добавляем отсортированные элементы по одному for (let i = 0; i < uniqueItems.length; i++) { container.append(uniqueItems[i]); } // Восстанавливаем положение прокрутки $(window).scrollTop(scrollPosition); } // Добавляем опции сортировки по цене и инициализируем функционал function initPriceSorting() { // Определяем язык const lang = detectLanguage(); // Добавляем новые опции сортировки в меню if ($('.sort-items[data-sort-key="price_asc"]').length === 0) { $('.menu-sorting').append( '<li class="sort-items" data-sort-key="price_asc">' + '<span>' + texts[lang].lowToHigh + '</span>' + '</li>' + '<li class="sort-items" data-sort-key="price_desc">' + '<span>' + texts[lang].highToLow + '</span>' + '</li>' ); } // Модифицируем функции запросов сайта patchSendRequest(); // Проверяем, активна ли уже сортировка по цене const currentSort = $('.selected_sort').html().trim(); if (currentSort === 'price_asc' || currentSort === 'price_desc') { priceSort.active = true; priceSort.ascending = (currentSort === 'price_asc'); // Применяем сортировку после полной загрузки страницы setTimeout(function() { sortElements(); }, 800); } // Обработчик для кнопок сортировки $(document).off('click', '.menu-sorting li.sort-items').on('click', '.menu-sorting li.sort-items', function(e) { const sortKey = $(this).attr('data-sort-key'); // Обновляем UI $('.sort-items span').removeClass('active'); $(this).find('span').addClass('active'); $('.selected_sort').html(sortKey); // Обновляем состояние сортировки priceSort.active = (sortKey === 'price_asc' || sortKey === 'price_desc'); priceSort.ascending = (sortKey === 'price_asc'); if (priceSort.active) { // Для цены используем клиентскую сортировку, если нет активных фильтров if ($('.menu-filter-options').is(':visible') && ($("#style_picker").val() !== 'all' || $("#country_picker").val() !== 'all' || $("#brewery_picker").val() !== 'all' || $("#hasNotHadBefore").is(':checked') || $(".search-text").val() !== '')) { // С фильтрами используем стандартный механизм сайта const url = buildUrl(); window._sendRequest(url, "menu"); } else { // Для стандартного режима без фильтров - сортируем сразу setTimeout(function() { sortElements(); }, 300); } } else { // Для других типов сортировки используем стандартный механизм const url = buildUrl(); window._sendRequest(url, "menu"); } }); // Обработчик для кнопки "Show More" $('.more-list-items').off('click.pricesorter').on('click.pricesorter', function() { if (!priceSort.active) return; // Отслеживаем загрузку новых элементов $(document).one('ajaxComplete', function() { setTimeout(function() { sortElements(); }, 800); }); }); console.log('Untappd Price Sorter v4.2: Сортировка по цене успешно добавлена!'); } // Запускаем инициализацию waitForElements(); })();