Перейти к содержимому
Zone of Games Forum
0wn3df1x

Скрипты Оунедфикса

Рекомендованные сообщения

Ultimate Steam Enhancer || Tamper Monkey

Ultimate Steam Enhancer — это мощный пользовательский скрипт для платформы Steam, расширяющий стандартные возможности магазина и сообщества. Он объединяет в себе функционал множества инструментов, предоставляя пользователю расширенный контроль и доступ к дополнительной информации об играх, ценах, переводах, обзорах, активности друзей и многом другом.

Скрипт соединяет в себе функционал из:

Полное описание всех функций (для новых пользователей)

Основной функционал

Функции на странице игры

Индикаторы / Доп. обзоры / Монитор обзоров

Что делает:

  1. Отображает значки-индикаторы наличия русского языка (интерфейс, озвучка, субтитры) прямо на странице игры.
  2. Добавляет под стандартными обзорами блок с расширенной статистикой (загружается по щелчку или автоматически, если включена опция):
    • Тотальные: Все обзоры Steam (включая активации ключами).
    • Безкитайские: Обзоры за исключением написанных на китайском языке.
    • Русские: Только обзоры на русском языке.

[Пример индикаторов и дополнительных обзоров на странице игры]


  1. Модальные окна:
  • При щелчке по строке "Русские" открывается окно с актуальными русскоязычными обзорами для этой игры.

[Модальное окно с русскоязычными обзорами]


  • При щелчке по строке "Тотальные" открывается окно "Глобальный монитор обзоров". Этот инструмент позволяет собрать (по кнопке "Собрать") и визуализировать статистику обзоров по 27 языкам Steam. Он показывает таблицу с рейтингом языков по количеству обзоров и доле в общей массе, а также интерактивную круговую диаграмму для наглядного представления.

[Глобальный монитор обзоров с таблицей и диаграммой]

Важное замечание о зависимостях:

Отключение этого модуля приведет к автоматическому отключению или нарушению корректной работы модулей «Время прохождения (HLTB)», «Русификаторы (ZOG)» и «Время друзей / Глобальные достижения», так как они критически зависят от его функционала по отображению элементов на странице игры.

Время прохождения (HLTB)

Что делает:
Добавляет компактный блок с информацией о времени прохождения игры, полученной с популярного сайта HowLongToBeat.com.

Показывает среднее время для разных стилей:

  • Только основной сюжет.
  • Сюжет + дополнительные задания.
  • Полное прохождение (100%).
  • Усредненное время для всех стилей.

Рядом со временем указывается количество игроков, на чьих данных основана статистика.
Поиск игры в базе HLTB идет по названию, при неоднозначности предлагается выбор из похожих вариантов.

[Пример отображения времени прохождения с HowLongToBeat]
Русификаторы (ZOG)

Что делает:
Добавляет блок с информацией о наличии русификаторов для игры на сайте ZoneOfGames.ru.

В блоке отображается:

  • Название игры (ведет на страницу игры в базе ZOG).
  • Список доступных русификаторов. Каждая запись является ссылкой на соответствующий файл/страницу на ZOG.
  • Если переводы не найдены, выводится соответствующее сообщение.

Поиск происходит в реальном времени. Скрипт автоматически определяет название игры, выполняет поиск по алфавитному указателю на ZoneOfGames.ru и предлагает вам выбрать наиболее точное совпадение.

[Пример отображения информации о русификаторах с ZOG]
Время друзей / Глобальные достижения

Что делает:
Отображает блок с информацией о времени, которое ваши друзья провели в этой игре, а также о статистике глобальных достижений Steam.

Время друзей:

  • Максимальное время прохождения (И ник друга со ссылкой).
  • Среднее время прохождения (и указание количества друзей, по которым высчитывалось среднее).
  • Минимальное время прохождения.

Глобальные достижения:

  • Процент "платины" (процент получивших самое редкое достижение).
  • Средний прогресс выполнения всех достижений.

Данные загружаются при раскрытии блока.

[Пример отображения времени друзей и статистики достижений]
Индикатор раннего доступа

Что делает:
Показывает небольшую плашку над изображением игры с информацией о статусе раннего доступа (Early Access).

  • Если игра еще в раннем доступе: Отображается, сколько времени игра уже находится в нем
    (например, "В раннем доступе уже 1 год и 3 месяца").

[Пример индикатора для игры в активном раннем доступе]


  • Если игра вышла из раннего доступа: Отображается, сколько времени игра провела в нем до релиза
    (например, "Вышла спустя 2 года раннего доступа").

[Пример индикатора для игры, вышедшей из раннего доступа]

Расчет времени динамический. Использует даты со страницы Steam.
А также может подтягивать дату старта раннего доступа из собственной базы для вышедших игр, если Steam ее не показывает.

Анализатор цен

Что делает:
Добавляет кнопку "Анализатор цен" на страницу игры. Этот инструмент позволяет анализировать региональные цены двумя способами:
в рублях (по умолчанию) и в долларах США (переключаемый режим).

После нажатия кнопки "Сбор данных" в специальном окне, модуль выполняет следующее в зависимости от выбранного режима:

  • Режим Рублей (по умолчанию):
    • Определяет AppID текущей игры и запрашивает цены через официальное API Steam (IStoreBrowseService/GetItems) для множества регионов.
    • В качестве базы для расчета рекомендованной рублевой цены используется цена в США (USD).
    • Цены из всех регионов, включая Россию, конвертируются в рубли по актуальным обменным курсам для прямого сопоставления.
    • Производится ключевое сравнение: фактическая цена в российском Steam сопоставляется с официально рекомендованной Valve ценой для России. Отклонения подсвечиваются.
    • Отображается рейтинг российской цены среди всех проанализированных стран, позволяя увидеть её место от самой дешёвой к самой дорогой в рублевом эквиваленте.
  • Режим Долларов США (переключаемый):
    • Активируется кнопкой "USD" в окне анализатора. Интерфейс и названия валют переключаются на английский язык.
    • Цены всех регионов также запрашиваются через API Steam и конвертируются в доллары США.
    • Цена в США используется как базовый ориентир (100%) для сравнения с ценами других регионов.
    • Отображается процентное отклонение цен других регионов от цены в США.
    • Представляется общий рейтинг всех региональных цен в долларовом эквиваленте.
    • Этот режим полезен для оценки ценовой политики при общении с разработчиками/издателями.

Примечание: Если игра в США бесплатна или цена для нее не найдена, возможности анализа в обоих режимах могут быть ограничены. Вся собранная информация представляется в модальном окне.

Важная информация:

Каждый полный сбор данных подразумевает отправку ~41 запроса к серверам Steam (точное количество зависит от числа доступных регионов). Пожалуйста, используйте эту функцию обдуманно. Частое нажатие кнопки на разных играх в течение короткого периода времени может привести к временному ограничению доступа к API Steam (обычно на 5-15 минут).

 
[Пример работы анализатора региональных цен]
Поиск цен на Plati.Market

Что делает:
Добавляет кнопку "Plati" рядом с кнопкой "В желаемое" на странице игры.
Нажатие открывает полноэкранное окно для поиска предложений по этой игре на торговой площадке Plati.Market.

Возможности окна поиска:

  • Автозаполнение поиска названием текущей игры.
  • Ручной ввод и поиск.
  • Подсказки при вводе (API Plati).
  • Сортировка по цене, продажам, релевантности, названию, дате, рейтингу продавца и др.
  • Фильтрация по цене (RUR, USD, EUR, UAH), продажам, рейтингу, наличию плохих отзывов/возвратов, участию в скидках, дате добавления.
  • Исключение товаров по ключевым словам (панель справа).
  • Сохранение фильтров, сортировки, валюты и исключений.

Используются официальные API Plati.Market.

[Модальное окно поиска цен на Plati.Market]
Агрегатор цен (%)

Что делает:
Добавляет кнопку "%" рядом с кнопкой "В желаемое" на странице игры.
Нажатие открывает модальное окно с ценами на эту игру из различных цифровых магазинов.

Возможности окна агрегатора:

  • Отображение предложений из магазинов: SteamBuy, Playo, SteamPay, Gabestore, GamersBase, Igromagaz, GamesForFarm, Gamazavr, GameRay, KupiKod, KeysForGamers, Zaka-zaka, Buka, GGSEL, Plati.Market и текущей страницы Steam.
  • Сортировка по цене, проценту скидки, сумме скидки, названию.
  • Фильтрация по диапазону цен, проценту и сумме скидки, наличию скидки, названию (слова через ";"), магазинам.
  • Исключение товаров по ключевым словам.
  • Сохранение состояния фильтров, сортировки и исключений.
  • Экспорт и импорт списка исключений для удобного переноса и резервного копирования.
Список рекомендуемых исключений

Нажмите кнопку импорта (→) и вставьте в появившееся окно следующий текст:




онлайн,оффлайн,dlc,аккаунт,ps4,xbox,digi361,ibloodrue,mangarded,siparisapp,multimarket,trustytop,cyber steam shop,boostgame,cloud-gaming.store,promarket88,offline,-seyter-,paul fox,sirdjinn,аренда,4ilgames,keks12,damhubmarket,nt-store,ps5,@mediasoft,dofamine game,hactier,alz0n,top-games,solo29,nerd shop,1gamestore,tobeclosertoyou

Использует различные методы для получения цен (API, парсинг HTML).

[Пример агрегатора цен]
Доступность подарков (страница игры)

Что делает:
Добавляет кнопку "GIFT" в блок с кнопкой "В желаемое" на странице игры.

Нажатие открывает окно, где можно:

  • Выбрать регион друга из списка.
  • Нажать "Узнать", чтобы запросить цену игры в этом регионе.
  • Увидеть цену друга (сконвертированную в вашу валюту), процент разницы и вердикт (Можно подарить / Нельзя подарить), основанный на правилах Steam о разнице цен (±10%).

Использует те же механизмы получения цен и курсов валют, что и помощник для списка желаемого.

[Пример работы помощника подарков на странице игры]

Улучшения каталога Steam

Доп. инфо / Фильтры

Что делает:
Расширяет функционал страницы поиска по каталогу Steam (store.steampowered.com/search/).

При наведении:

  • Появляется всплывающая подсказка слева от строки игры с подробной информацией:
    издатели, разработчики, серия, отзывы (% и кол-во), статус раннего доступа, поддержка языков, первые 5 меток, краткое описание.

[Пример дополнительной информации об игре в каталоге]


Фильтры (панель справа):

  • Русский перевод:
    • Только текст: Игры с рус. интерфейсом/субтитрами (без озвучки).
    • Озвучка: Игры с русской озвучкой.
    • Без перевода: Игры без русского языка.

[Пример фильтрации по русскому переводу в каталоге]

  • DLC:
    • Только ваши DLC: Показывает только DLC для игр, которые есть в вашей библиотеке
      (сами DLC подсвечиваются фиолетовым фоном).

[Фильтр DLC]

Фильтры применяются динамически по мере получения данных от API.

Скрытие игр

Что делает:
Добавляет инструменты для массового скрытия неинтересующих игр прямо со страницы поиска по каталогу.

Элементы интерфейса:

  • Счетчик отображаемых игр (слева вверху).
  • Чекбокс слева от каждой игры (кроме уже купленных/скрытых/в желаемом) для отметки на скрытие.
  • Кнопка "Скрыть выбранное" (слева вверху).

Принцип работы:

  1. Отмечаете чекбоксами игры, которые хотите скрыть.
  2. Нажимаете "Скрыть выбранное".
  3. Скрипт добавляет эти игры в ваш официальный список игнорируемых в Steam и удаляет их элементы со страницы.

В отличие от стандартного механизма Steam, элементы полностью удаляются из DOM, что улучшает производительность при работе с большим количеством результатов.

Внимание: Рекомендуется использовать только при необходимости массового скрытия.
Для обычного просмотра каталога лучше отключать эту опцию.

[Пример системы скрытия игр в каталоге]

Сообщество и активность

Доп. инфо в ленте активности

Что делает:
Добавляет всплывающую подсказку при наведении на название игры в вашей ленте активности Steam (steamcommunity.com/my/home).

Подсказка содержит подробную информацию об игре, аналогичную той, что показывается в каталоге поиска:

  • Название и изображение-шапка.
  • Дата выхода.
  • Издатели, разработчики, серия игр.
  • Отзывы (% и кол-во).
  • Статус раннего доступа.
  • Поддержка русского и английского языков.
  • Первые 5 меток.
  • Краткое описание.

Данные загружаются через API Steam.

[Пример дополнительной информации в ленте активности Steam]
Рулетка Stelicas

Что делает:
Добавляет блок "Рулетка Stelicas" на страницу вашей активности Steam (steamcommunity.com/my/home). Позволяет загрузить CSV-файл, сгенерированный приложением Stelicas, применить к нему разнообразные фильтры и случайным образом выбрать игру из вашей коллекции.

Возможности:

  • Загрузка CSV-данных из Stelicas (содержащих информацию о ваших играх и коллекциях).
  • Система фильтрации: по категориям коллекций Stelicas, дате выхода, пользовательским тегам, поддержке русского языка (интерфейс, субтитры, озвучка), а также по диапазонам количества отзывов и общего рейтинга игры.
  • Анимированная рулетка для выбора случайной игры из отфильтрованного списка.
  • Возможность включить приоритет по отзывам и рейтингу, чтобы игры с лучшими показателями имели больше шансов на выпадение.
  • Переключение в режим просмотра всей отфильтрованной подборки игр в виде удобных карточек (изображение и название).
  • Отображение подробной информации о выбранной (или просматриваемой в подборке) игре: постер, название, рейтинг и количество отзывов, краткое описание, основные теги, точная дата выхода, разработчики/издатели, информация о поддержке русского языка.
  • Прямые ссылки на страницу игры в сообществе Steam и для её запуска через протокол steam://run/<AppID>.

Как пользоваться:

  • Подробная инструкция по подготовке CSV-файла и использованию всех функций рулетки доступна по нажатию на значок вопроса ? в правом верхнем углу окна самой рулетки.

Примечание: Качество работы и полнота информации в рулетке напрямую зависят от корректности и актуальности данных в предоставленном CSV-файле из Stelicas.

[Рулетка Stelicas]

Торговая площадка

Продажи предмета

Что делает:
Добавляет информационный блок на страницу предмета на торговой площадке Steam (steamcommunity.com/market/).

Блок содержит:

  • Таблицу с историей продаж по годам:
    • Общая сумма продаж за год (в рублях).
    • Примерная сумма, полученная разработчиком игры.
    • Примерная сумма, полученная Valve.
  • Итоговую сумму продаж за все время.
  • Итоговые суммы, полученные разработчиком и Valve.

Данные загружаются через API истории цен Steam.

[Пример информации об исторических продажах на торговой площадке]

Новости и список желаемого

Наблюдатель (Желаемое/Библиотека)

Что делает:
Отслеживает изменения в вашем списке желаемого Steam и в вашей библиотеке игр, отображает календарь релизов.

Основные функции:

  1. В правом верхнем углу страниц Steam появляется кнопка "Наблюдатель".
  2. Индикаторы статуса (Ж/Б): Показывают, как давно обновлялись данные для Желаемого и Библиотеки.
  3. Счетчик уведомлений: Показывает количество новых (непрочитанных) изменений.
  4. Панель уведомлений (по щелчку на кнопку):
    • Кнопка "Обновить" для ручного запуска проверки (использует Steam API).
    • Настройки (значок шестерёнки): Открывает выпадающее меню с опциями:
      • Список желаемого / Библиотека: Позволяют включать/отключать проверку для каждой из секций.
      • Перепроверять игры с русским: (Опция для Библиотеки). По умолчанию включена. Если её отключить, скрипт пропустит проверку игр, для которых уже известно о наличии любой русской локализации. Это значительно ускоряет повторные обновления.
      • ...только без полной локализации: (Доступна, если предыдущая опция отключена). Если включить, то из повторной проверки будут исключаться только игры с полной локализацией (интерфейс+озвучка+субтитры). Игры с частичным переводом продолжат проверяться.
    • Список изменений:
      • Список желаемого: Изменение даты выхода, статуса раннего доступа или русского языка.
      • Библиотека: Выход игры из раннего доступа, появление/изменение русского языка (и тип локализации).
    • Кнопки для отметки уведомления как прочитанного (конверт) или удаления (крестик).
    • Кнопка "Очистить" для удаления всех уведомлений.
    • Кнопка "Календарь".
    • Кнопка "Хранилище" для очистки сохраненных данных.

[Пример панели уведомлений Наблюдателя]


  1. Календарь релизов (по щелчку на кнопку "Календарь"):
    • Отображает игры из вашего списка желаемого в виде календаря по месяцам.
    • Показывает игры с точными датами выхода в будущем.
    • Для игр с примерной датой (месяц, квартал, год) отображается подсказка при наведении.
    • Позволяет подгружать следующие месяцы.

[Пример Календаря]


  1. Хранилище (по щелчку на кнопку "Хранилище"):
    • Позволяет очистить кэш дат/статусов для списка желаемого или для игр библиотеки.

[Пример Хранилища]

Требует авторизации. Обработка больших списков/библиотек может занять время. Используйте новые опции в настройках для ускорения сканирования библиотеки.

Доступность подарков (список желаемого)

Что делает:
Добавляет значок лупы. Позволяет определить, какие игры можно подарить друзьям в других регионах.

Основные функции:

  • Загружает игры из отображаемого списка желаемого и выводит их в виде информативных карточек с возможностью сортировки.
  • Активирует режим помощника подарков:
    • Вы выбираете регион вашего друга.
    • Скрипт запрашивает цены на игры из списка желаемого для выбранного региона.
    • Цены друга конвертируются в вашу валюту (используется API курсов валют).
    • Отображается разница в цене между вашим регионом и регионом друга
      (с цветовой индикацией: зелёный - можно дарить (разница до ±10%), красный - нельзя).
    • Доступен фильтр "Можно подарить", который показывает только те игры, у которых разница в цене до ±10% и которые Steam разрешает покупать в подарок.

Это помогает легко найти подходящие и экономически целесообразные подарки для друзей за границей.

Скорость загрузки данных зависит от размера списка желаемого.

[Пример работы помощника подарков для списка желаемого]
Фильтр новостей

Что делает:
Позволяет гибко управлять отображением новостей в новостном центре Steam (store.steampowered.com/news/), скрывая неинтересные материалы.

Основные возможности и использование:

  1. Выбор новостей для скрытия:
    • На каждой новости в правой части изображения появляется крупный квадратный чекбокс.
    • При установке галочки новость становится полупрозрачной ("мягкое" скрытие) и отмечается для последующего подтверждения скрытия. Повторный щелчок снимает отметку.
  2. Панель управления (справа вверху):
    • "Скрыть выбранные (X)": Нажатие этой кнопки перемещает все отмеченные (полупрозрачные) новости в постоянное хранилище.
      • Новость исчезает с экрана (или становится затемненной, если включен режим "Показать скрытое").
      • Счетчик в скобках показывает, сколько новостей сейчас выбрано.
    • "В хранилище: X": Эта надпись показывает общее количество новостей, находящихся в хранилище скрытых новостей.
    • "Отменить": Появляется после подтверждения скрытия и активна 15 секунд. Позволяет отменить последнее действие по добавлению новостей в хранилище.
    • "Показать скрытое" / "Спрятать скрытое": Переключатель.
      • Если выбрано "Показать скрытое", новости из вашего хранилища будут отображаться в ленте, но в затемненном виде.
      • В режиме "Спрятать скрытое" они полностью исчезают.
    • "Хранилище": Открывает модальное окно для управления списком постоянно скрытых новостей.

[Фильтр новостей]


  1. Панель "Хранилище скрытых новостей":
    • Отображает список всех новостей, добавленных в постоянное хранилище. Для каждой записи указывается название игры, заголовок новости и ее AppID.
    • "Вернуть": Кнопка напротив каждой записи позволяет удалить новость из хранилища и немедленно отобразить ее в ленте. Чекбокс на этой новости также снова станет активным.
    • "Очистить хранилище": Удаляет все новости из вашего списка постоянно скрытых. Требует подтверждения.
    • "Закрыть": Закрывает панель хранилища.

[Хранилище новостей]


Настройки

Как найти и использовать меню настроек U.S.E.

Для настройки отдельных модулей скрипта и их параметров используйте меню настроек U.S.E.
Оно находится в выпадающем меню вашего профиля Steam (в правом верхнем углу), как показано ниже:

[Расположение меню настроек U.S.E. в профиле Steam]

В открывшемся окне настроек вы сможете включать или отключать модули скрипта:

[Пример панели настроек Ultimate Steam Enhancer]

Также в меню настроек доступны следующие опции:

Авто-раскрытие HLTB

Если включено, блок с информацией о времени прохождения (HLTB) на странице игры будет автоматически раскрываться при загрузке страницы (если основной модуль HLTB включен).

Удобно, если вы всегда хотите видеть эту информацию без лишнего щелчка.

Авто-загрузка доп. обзоров

Если включено, блок с дополнительными обзорами (Тотальные, Безкитайские, Русские) на странице игры будет загружаться автоматически при загрузке страницы (если основной модуль 'Индикаторы/Обзоры' включен).

Экономит щелчок, если вам всегда нужна эта статистика.

Показ инфо об англ. языке

Функция для переводчиков и интересующихся. Если включено, в блоках дополнительной информации (в каталоге при наведении и в ленте активности при наведении) будет также отображаться информация о поддержке английского языка (интерфейс, озвучка, субтитры), аналогично русскому.

По умолчанию эта информация скрыта для экономии места.

 

Код скрипта на GreasyFork

Скрытый текст

 

 

 

 

Что нового в версии 2.0 (для тех, кто в курсе)

Версия 2.0.0

Юбилейное обновление знаменует собой новую эру для Ultimate Steam Enhancer.


Глобальное улучшение: Меню настроек U.S.E.

Теперь все опции доступны через удобное графическое меню.

  • Доступ: Меню настроек U.S.E. находится в выпадающем меню вашего профиля Steam (в правом верхнем углу).
  • Управление модулями: Легко включайте и отключайте отдельные модули скрипта.
  • Дополнительные опции: Настраивайте поведение конкретных функций, таких как автоматическое раскрытие блоков или загрузка данных.
[Изображение: Расположение меню настроек U.S.E. в профиле Steam]
[Изображение: Пример панели настроек Ultimate Steam Enhancer]

Это изменение делает скрипт значительно более дружелюбным для пользователей и упрощает его персонализацию.


Новые функции

Анализатор цен (Региональные цены Steam)

Что делает: Добавляет кнопку "Анализатор цен" на страницу игры. Этот мощный инструмент позволяет детально анализировать региональные цены игры в Steam двумя способами: в рублях (по умолчанию) и в долларах США (переключаемый режим).

После нажатия кнопки "Сбор данных" в специальном окне, модуль выполняет следующее:

Режим Рублей:

  • Определяет AppID текущей игры и запрашивает цены через официальное API Steam для 41 региона.
  • В качестве базы для расчета рекомендованной рублевой цены используется цена в США (USD).
  • Цены из всех регионов, включая Россию, конвертируются в рубли по актуальным обменным курсам для прямого сопоставления.
  • Производится ключевое сравнение: фактическая цена в российском Steam сопоставляется с официально рекомендованной Valve ценой для России. Отклонения подсвечиваются.
  • Отображается рейтинг российской цены среди всех проанализированных стран, позволяя увидеть её место от самой дешёвой к самой дорогой в рублевом эквиваленте.

Режим Долларов США:

  • Активируется кнопкой "USD" в окне анализатора. Интерфейс и названия валют переключаются на английский язык.
  • Цены всех регионов также запрашиваются через API Steam и конвертируются в доллары США.
  • Цена в США используется как базовый ориентир (100%) для сравнения с ценами других регионов.
  • Отображается процентное отклонение цен других регионов от цены в США.
  • Представляется общий рейтинг всех региональных цен в долларовом эквиваленте.
  • Этот режим полезен для оценки ценовой политики при общении с разработчиками/издателями.

Примечание: Если игра в США бесплатна или цена для нее не найдена, возможности анализа в обоих режимах могут быть ограничены.

⚠️ Важная информация:

Каждый полный сбор данных подразумевает отправку ~41 запроса к серверам Steam. Пожалуйста, используйте эту функцию обдуманно. Частое использование может привести к временному ограничению доступа к API Steam.

[Изображение: Пример работы анализатора региональных цен]
Агрегатор цен (%)

Что делает: Заменяет старый модуль "Цены (VGT)". Добавляет кнопку "%" рядом с кнопкой "В желаемое" на странице игры. Нажатие открывает модальное окно с ценами на эту игру из различных популярных цифровых магазинов.

Возможности окна агрегатора:

  • Отображение предложений из множества магазинов, включая: SteamBuy, Playo, SteamPay, Gabestore, GamersBase, Igromagaz, GamesForFarm, Gamazavr, GameRay, KupiKod, KeysForGamers, Zaka-zaka, Buka, GGSEL, Plati.Market и текущей страницы Steam.
  • Сортировка по цене, проценту скидки, сумме скидки, названию.
  • Фильтрация по диапазону цен, проценту и сумме скидки, наличию скидки, названию (слова через ";"), магазинам.
  • Исключение товаров по ключевым словам.
  • Сохранение состояния фильтров, сортировки и исключений между сессиями.
  • Экспорт и импорт списка исключений для удобного переноса и резервного копирования.
Список рекомендуемых исключений

Нажмите кнопку импорта () и вставьте в появившееся окно следующий текст:





онлайн,оффлайн,dlc,аккаунт,ps4,xbox,digi361,ibloodrue,mangarded,siparisapp,multimarket,trustytop,cyber steam shop,boostgame,cloud-gaming.store,promarket88,offline,-seyter-,paul fox,sirdjinn,аренда,4ilgames,keks12,damhubmarket,nt-store,ps5,@mediasoft,dofamine game,hactier,alz0n,top-games,solo29,nerd shop,1gamestore,tobeclosertoyou

Использует различные методы для получения цен (API, парсинг HTML) для предоставления наиболее полной картины.

[Изображение: Пример интерфейса агрегатора цен]
Доступность подарков (на странице игры)

Что делает: Добавляет кнопку "GIFT" в блок с кнопкой "В желаемое" на странице игры.

Нажатие открывает окно, где можно:

  • Выбрать регион друга из списка.
  • Нажать "Узнать", чтобы запросить цену игры в этом регионе.
  • Увидеть цену друга (сконвертированную в вашу валюту), процент разницы и вердикт (Можно подарить / Нельзя подарить), основанный на правилах Steam о разнице цен (обычно ±10-15%).

Использует те же механизмы получения цен и курсов валют, что и помощник для списка желаемого.

[Изображение: Пример работы помощника подарков на странице игры]
Рулетка Stelicas (Лента активности)

Что делает: Добавляет блок "Рулетка Stelicas" на страницу вашей активности Steam (steamcommunity.com/my/home). Позволяет загрузить CSV-файл, сгенерированный приложением Stelicas, применить к нему разнообразные фильтры и случайным образом выбрать игру из вашей коллекции.

Возможности:

  • Загрузка CSV-данных из Stelicas.
  • Система фильтрации: по категориям коллекций Stelicas, дате выхода, пользовательским тегам, поддержке русского языка (интерфейс, субтитры, озвучка), а также по диапазонам количества отзывов и общего рейтинга игры.
  • Анимированная рулетка для выбора случайной игры.
  • Возможность включить приоритет по отзывам и рейтингу.
  • Переключение в режим просмотра всей отфильтрованной подборки игр.
  • Отображение подробной информации о выбранной игре.
  • Прямые ссылки на страницу игры в Steam и для её запуска.

Подробная инструкция доступна по нажатию на значок вопроса ? в окне самой рулетки.

[Изображение: Пример модального окна Рулетки Stelicas с фильтрами]
Доступность подарков (Список желаемого)

Что делает: Добавляет значок лупы на страницу списка желаемого. Позволяет определить, какие игры из вашего вишлиста можно подарить друзьям в других регионах.

Основные функции:

  • Загружает игры из отображаемого списка желаемого и выводит их в виде информативных карточек с возможностью сортировки.
  • Активирует режим помощника подарков:
    • Вы выбираете регион вашего друга.
    • Скрипт запрашивает цены на игры для выбранного региона.
    • Цены друга конвертируются в вашу валюту.
    • Отображается разница в цене с цветовой индикацией (зелёный - можно дарить, красный - нельзя).
    • Доступен фильтр "Можно подарить".

Это помогает легко найти подходящие и экономически целесообразные подарки для друзей за границей.

[Изображение: Пример работы помощника подарков для списка желаемого]

Улучшения существующих функций

Индикаторы / Доп. обзоры / Монитор обзоров

Модуль информации об обзорах на странице игры был расширен:

  • Новинка: Глобальный монитор обзоров!
    • При щелчке по строке "Тотальные" открывается окно "Глобальный монитор обзоров".
    • Этот инструмент позволяет собрать (по кнопке "Собрать") и визуализировать статистику обзоров по 27 языкам Steam.
    • Отображается таблица с рейтингом языков по количеству обзоров и доле в общей массе.
    • Интерактивная круговая диаграмма для наглядного представления распределения обзоров по языкам.
[Изображение: Глобальный монитор обзоров с таблицей и диаграммой]

⚠️ Важное замечание о зависимостях:

Отключение этого модуля через новое меню настроек приведет к автоматическому отключению или нарушению корректной работы модулей «Время прохождения (HLTB)», «Русификаторы (ZOG)» и «Время друзей / Глобальные достижения», так как они критически зависят от его функционала по отображению элементов на странице игры.

Наблюдатель (Желаемое/Библиотека)

Модуль отслеживания был полностью переработан, получил новое имя "Наблюдатель" и значительно расширил свои возможности. Теперь он следит не только за списком желаемого, но и за вашей библиотекой, а также предлагает гибкие настройки для ускорения работы.

  • Комплексное отслеживание: "Наблюдатель" теперь отслеживает изменения как для Списка желаемого, так и для Библиотеки:
    • Для желаемого: изменение даты выхода, выход из раннего доступа, появление/изменение русского языка.
    • Для библиотеки: выход игры из раннего доступа, а также появление или изменение русской локализации (с точным указанием, что было добавлено или убрано: интерфейс, озвучка, субтитры).
  • Гибкие настройки сканирования: В панели уведомлений появился значок настроек, который открывает меню с новыми опциями:
    • Возможность включать или отключать проверку для желаемого и библиотеки по отдельности.
    • Оптимизация проверки библиотеки: Добавлены опции для значительного ускорения повторных сканирований. Можно отключить перепроверку игр, у которых уже есть любая русская локализация, или, для более детального контроля, перепроверять только те игры, у которых нет полной локализации.
  • Улучшенный интерфейс:
    • Раздельные индикаторы статуса (Ж/Б), показывающие, как давно обновлялись данные для Желаемого и Библиотеки.
    • Более информативные уведомления, четко разделенные по источнику (Желаемое/Библиотека) и типу изменения.
  • Управление хранилищем: Новая кнопка "Хранилище" позволяет вручную очистить кэш сохраненных данных для списка желаемого или библиотеки. Это полезно, если вы хотите принудительно запустить полное повторное сканирование.
     
[Изображение: Пример панели уведомлений Наблюдателя]
 
[Изображение: Пример окна управления хранилищем Наблюдателя]
Фильтр новостей

Модуль скрытия новостей в новостном центре получил следующие улучшения:

  • "Мягкое" скрытие: При установке галочки новость сначала становится полупрозрачной, отмечаясь для последующего подтверждения скрытия.
  • Панель управления:
    • Кнопка "Скрыть выбранные (X)" с счетчиком.
    • Индикатор "В хранилище: X" показывает общее количество скрытых новостей.
    • Кнопка "Отменить" для отмены последнего действия по скрытию (активна 6 секунд).
    • Переключатель "Показать скрытое" / "Спрятать скрытое" для временного отображения скрытых новостей в затемненном виде.
  • Новинка: Панель "Хранилище скрытых новостей"!
    • Открывается кнопкой "Хранилище" из панели управления.
    • Отображает список всех постоянно скрытых новостей (название игры, заголовок, AppID).
    • Позволяет выборочно "Вернуть" новость из хранилища.
    • Кнопка "Очистить хранилище" для полного удаления всех скрытых новостей (требует подтверждения).
[Изображение: Пример интерфейса фильтра новостей]
[Изображение: Пример интерфейса хранилища новостей]
Русификаторы (ZOG)

Модуль поиска русификаторов был кардинально переработан и больше не использует устаревающую базу данных.

  • Поиск в реальном времени: Вместо статической базы данных теперь скрипт выполняет поиск напрямую на сайте ZoneOfGames.ru в момент вашего запроса, гарантируя максимальную актуальность информации.
  • Выбор из совпадений: Если найдено несколько похожих игр, скрипт предложит вам список для выбора. Это исключает ошибки при поиске игр с похожими названиями (например, 'Half-Life' и 'Half-Life 2') и обеспечивает более высокую точность.
[Изображение: Пример отображения информации о русификаторах с ZOG]
Доп. инфо в ленте активности

Модуль, отображающий информацию об играх в ленте активности, был улучшен для более точного отображения изображений.

  • Точные изображения: Вместо использования стандартного имени файла header.jpg, скрипт теперь запрашивает у API Steam точное имя файла обложки игры. Это решает проблему, когда для некоторых игр отображалась неверная или отсутствующая картинка.
  • Повышенная надежность: Новый метод гарантирует, что во всплывающей подсказке будет показано именно то изображение, которое разработчик установил в качестве основного для страницы в магазине.

Технические изменения

  • Новые библиотеки:
    • Добавлена библиотека Chart.js для построения диаграмм в "Глобальном мониторе обзоров".
    • Добавлен плагин chartjs-plugin-datalabels для отображения меток данных на диаграммах.
  • Обновлены директивы @connect:
    • Добавлено множество новых доменов для работы "Агрегатора цен (%)", "Анализатора цен" и "Помощников подарков".
    • Удален домен vgtimes.ru в связи с заменой соответствующего модуля.
  • Общая оптимизация кода и улучшение стабильности работы существующих модулей.

Удаленные функции

  • Агрегатор цен (VGTimes): Модуль был полностью удален и заменен новым, более функциональным модулем "Агрегатор цен (%)".
  • Отслеживание вишлиста (wishlistTracker): Старый модуль был заменен на новый, более комплексный модуль "Наблюдатель (Желаемое/Библиотека)".
Скрытый текст

Версия 1.9.4 - Подсветка и фильтрация DLC для ваших игр

  • Основные изменения
    • Подсветка DLC для игр в вашей библиотеке
      • В каталоге поиска теперь подсвечиваются DLC для игр, которые уже есть в вашей библиотеке Steam (фиолетовый фон).
    • Новый фильтр "Только ваши DLC"
      • Добавлена возможность фильтрации, чтобы показывать только DLC для игр из вашей библиотеки.

MqjuXoD.png

Скрытый текст

Версия 1.9.3 - Хотфикс индикатора раннего доступа

Исправлено

  1. Отображение индикатора поверх других элементов из-за слишком высокого z-индекса
Скрытый текст

Версия 1.9.2 - Улучшение индикатора раннего доступа

Основные улучшения

  1. Расширенная база данных дат: 
    • Интеграция внешнего источника данных для определения начала раннего доступа, даже если дата не указана на странице игры.
    • Автоматическое кэширование данных на 6 месяцев для оптимизации производительности. 
      • Для активного раннего доступа: время с момента запуска до текущей даты.
      • Для вышедших из раннего доступа игр: период от раннего доступа до официального выхода.
  2. Улучшенная обработка сценариев
    • Отображение "срок неизвестен", если дата выхода в ранний доступ больше, чем дат выхода из раннего доступа.

Данное обновление связано с написанием блога “Сколько лет, сколько зим: Хроники раннего доступа”, в рамках разбора которого обнаружилось, что у нескольких тысяч игр отсутствуют метаданные о нахождении в раннем доступе, из-за чего скрипт, опирающийся на базы данных Steam, не выводил индикатор у этих игр. В связи с этим был проведён сбор дополнительной информации, после чего все обнаруженные игры были вынесены во внешнюю базу, по которой скрипт проверяет игры. 

Скрытый текст

Версия 1.9.1 - Индикатор раннего доступа

Новые функции

  1. Динамический расчет продолжительности раннего доступа: 
    • На страницу игр добавлен новый блок с динамическим расчетом продолжительности нахождения игры в раннем доступе: 
      • Для активного раннего доступа: время с момента запуска до текущей даты.
      • Для вышедших из раннего доступа игр: период от раннего доступа до официального выхода.

6iGlcTf.png

SPzJrpi.png

Скрытый текст

Версия 1.9 - Агрегатор цен

Новые функции

  1. Добавлен под-скрипт "Агрегатор цен” 
    • На странице игры отображается кнопка "Цены (VGT)" (находится в блоке с "в желаемое", "подписаться" и "скрыть") для отображения цен из магазинов.
    • Группировкой предложений по магазинам
      • Группировкой предложений по магазинам
      • Сортировкой магазинов по минимальной цене
      • Возможностью загрузить последующие результаты (пагинация по 40 позиций)
    • Реализована система распознавания игр через:
      • Прямое совпадение Steam AppID
      • Нормализацию названий и алгоритм нечёткого поиска
      • Ручной выбор из списка возможных совпадений  
    • Система использует актуальную базу данных VGTimes на 11.02.2024  (поэтому игры, вышедшие, после этой даты могут не находиться)

Изначально игры выводятся по релятивности, т.е. сочетание цены и соответствия запросу, т.к. иногда в агрегаторе в цены игры могут попадать цены на DLC и другие избыточные вещи (сейчас они обычно оказываются на последующих страницах.

Важно!

  • Если скрипт не может найти игру в базе VGT по Steam AppId, то он начинает искать по имени в довольно обширной базе.
  • Если идеальное совпадение имени обнаружено, то поиск займёт пару секунд.
  • Если идеальное совпадение не обнаружится, то скрипт соберёт все игры, названия которых как-то совпадают, это уже дольше.
  • Если скрипт затрудняется найти что-то похожее - поиск займёт ещё дольше. В таком случае страница может подвиснуть на 5-10 секунд).
Скрытый текст

7cJzB8a.png

fNRnaHw.png

Скрытый текст

Обновил Ultimate Steam Enhancer.

Версия 1.8 - Время друзей и достижения

Новые функции

  1. Добавлен под-скрипт "Время друзей & Достижения” 
    • Новый информационный блок на страницах игр с аналитикой времени игры друзей.
    • Отображение максимального, среднего и минимального времени прохождения среди друзей.
    • Интеграция статистики глобальных достижений с расчётом платины и среднего прогресса.

9TaMCbZ.png

Скрытый текст

Версия 1.7.4 -  Информация о метках в подсказках

Новые функции

  1. Добавлено отображение Steam-меток в подсказках 
    • Реализована интеграция с внешней базой русских названий тегов через GitHub Gist.
    • Всплывающие подсказки показывают до 5 основных тегов игры (Т.к. Steam нередко ограничивает их до трёх и меньше в поиске по каталогу).
    • Добавлена система кэширования тегов на 31 день для уменьшения количества запросов.

U7DYIvJ.png

xE75iU8.png

Скрытый текст

Версия 1.7.2 - Хотфикс

  1. Исправлен конфликт тултипов при включенной функции отслеживания вишлиста:
    • Переработана система именования CSS-классов (Добавлен уникальный префикс `wt-` к критическим элементам). Переименованы:  
      • `.tooltip` → `.wt-tooltip`
      • `.notification-item` → `.wt-notification-item`
      • `.panel-header` → `.wt-panel-header`
  • Исправлены селекторы в JavaScript для новых имен классов

0HzovQo.png

Скрытый текст

Версия 1.7.1 - Исправления

Исправленные проблемы:

  1. Некорректное позиционирование панели в виджетах
    • Добавлена проверка на основной документ перед вставкой элементов
    • Исключено встраивание в iframe и вложенные body

    eJi8L99.png

     

  2. Дублирующее открытие ссылок в календаре

    • Удален лишний обработчик щелчка для игр
    • Оставлена только нативная обработка ссылок через тег <a>
  3. Взаимодействие с фоном при открытом календаре

    • Модальное окно теперь закрывается при щелчке вне его области
    • Блокировка нежелательных действий с элементами страницы под модалкой
    • Добавлена защита от случайных переходов по ссылкам при закрытии

Технические изменения:

  • Использование нативного DOM API для добавления элементов
  • Оптимизировано определение контекста выполнения скрипта
  • Улучшена изоляция стилей через проверку области видимости
  • Добавлена глобальная обработка щелчков для модальных окон

Все прошлые исправления загоню в changelog в посте со скриптом.

Скрытый текст

Обновил Ultimate Steam Enhancer.

Версия 1.7 — Добавлен календарь релизов из списка желаемого

  • Модальное окно с помесячной разбивкой
  • Интерактивная сетка дней с предстоящими релизами
  • Визуальная индикация игр с точными/приблизительными датами
  • Поддержка разных форматов дат (квартал, год, месяц)
  • Динамическая подгрузка месяцев (пагинация по 3 месяца)
  • Подсказки с детализацией для приблизительных дат
  • Прямые ссылки на страницы игр из календаря

Bv5Be9Y.png

Скрытый текст

Когда HLTB отключался, ZOG терял референсную точку и падал в позицию (0,0), так как не было фолбэка на другие DOM-элементы. По сути, ZOG-блок вел себя как поезд, перед которым убрали рельсы.
 

Ключевые изменения:

  • Внедрил многоуровневую систему привязки позиции ZOG:
    • Первичная привязка к HLTB через ResizeObserver (если активен)
    • Фолбэк на блок русификаторов russianIndicators
    • Крайний случай - жесткая фиксация относительно gameHeaderImageCtn
  • Улучшил обработку мутаций через комбинацию Observers:
    
    
    const generalObserver = new MutationObserver((mutations) => {
      mutations.forEach(mutation => {
        if (mutation.type === 'childList') {
          updatePosition(); // Реакция на изменение структуры
          initObservers();  // Реинициализация при динамических изменениях
        }
      });
    });

     

Если вкратце:
Теперь, независимо от состояния gamePage/hltbData/zogInfo,  HLTB и ZOG должны правильно позиционироваться. 

Скрытый текст

Появился новый функционал:

9. Отслеживание изменений дат релиза по вашему списку желаемого Steam:

  • В правом верхнем углу страниц Steam появляется кнопка "Отслеживание вишлиста" со счетчиком непрочитанных уведомлений и индикатором статуса актуальности данных:
    • ОК (До 24 ч.)
    • ОК? (До 48 ч.)
    • ! (До 72 ч.)
    • !! (До 96 ч.)
    • !!! (Более 96 ч.)
    • ??? (Критическое устаревание, ошибка или отсутствие данных [при первом запуске]).
      • (При наведении курсора на индикатор вы увидите точные цифры).
  • При нажатии на кнопку выплывает интерфейс, где есть отдельная кнопка “Обновить” , с помощью которых осуществляется запрос актуальных данных через Steam API.
  • При изменении даты релиза игры в панели появляется карточка с:
    • Изображением и названием игры (кликабельная ссылка на страницу).
    • Старой и новой датами релиза.
    • Временем обнаружения изменения.
    • Кнопками для отметки прочтения и удаления каждого уведомления.
      • (Также присутствует кнопка очистки уведомлений).
         
  • Скрипт поддерживает все форматы: точные (например, "15.04.2025") и относительные (Coming Soon, TBA, кварталы, месяцы, годы) даты.
  • Скрипт получает список appID из вашей Userdata, которая доступна только вам. 
  • Запросы выполняются батчами по 200 appID к IStoreBrowseService/GetItems.
     
  • Важно!
    • Поскольку скрипт работает с userdata, он не будет работать, если вы не находитесь в своём аккаунте.
    • Обработка гигантских списков желаемого может занять время (делите размер списка желаемого на 200. 10000 игр = 50 запросов).
    • В скрипте установлено ограничение в сохранение 5000 уведомлений. Но сомневаюсь, что кому-то понадобится больше.
Скрытый текст

В части поиска по имени при получении информация о наличии переводов на ZOG:

  • Теперь алгоритм нормализации названий стал жестче:
    • Нормализует Unicode (NFD -> удаляет диакритику)
    • Выпиливает торговые марки (™®©) и стандартизирует апострофы
    • Фильтрует edition-based суффиксы через regexp: /ultimate|definitive|edition/i и т.д.
    • Конвертирует римские цифры в арабские (VII -> 7) хардкодным маппингом
    • Удаляет артикли (the/a/an) из начала строки
       
  • В findPossibleMatches появились триггеры contains помимо startsWith, плюс добавилась композитная сортировка:
    • сначала strict prefix match,
    • потом substring inclusion,
    • потом уже по проценту схожести через алгоритм Левенштейна.
  • Теперь обработка названия игры идет через пайплайн:
    • rawName → normalizeTitle → removeEditionWords → processRomanNumerals → processArticles
       
  • Для Steam API-ответов добавился recursive processing — если после очистки английского названия не находится матч, идет обращение к possibleMatches с модифицированными levenshteinDistance thresholds.
     
  • Теперь должно лучше детектить игры с разными изданиями и переводами названий.
Скрытый текст

Улучшены:

  • Информация о времени прохождения игры с HLTB
  • Информация о наличии переводов на ZOG (ZoneOfGames) 

Поиск по имени является основным методом нахождения игры на HLTB и вспомогательным для поиска игры на ZOG.
Данный метод был улучшен, теперь:

  • Он осуществляет более тщательный поиск с учётом возможных опечаток, укорачиваний и удлинений названий.
  • Предоставляет возможность выбрать, какое из похожих названий подходит вам больше всего
Скрытый текст

Появился функционал на странице игры:

3. Информация о наличии переводов на ZOG (ZoneOfGames):

  • На странице игры отображается блок с информацией о наличии русификаторов (и не только) на ZoneOfGames. Включает:
    • Название игры [и ссылку на неё в базе ZOG].
    • Информацию о наличии перевода (языке, типе, размере) или его отсутствии [со ссылками на на него в базе ZOG] 
       
  • Важно!
    • Скрипт опирается на базу, собранную 05.02.2025. Поэтому информация в нём актуальна на этот день. Если я обновлю базу — скрипт автоматически её подтянет.
    • Скрипт ищет информацию базе двумя способами:
      • По App ID игры.
        (Если в базе ZOG указан Steam App ID игры, то вы увидите, есть ли на неё перевод.)
      • По названию игры.
        (Если в базе ZOG содержится название игры из Steam).
    • Оба способа не гарантируют 100% получение информации о наличии переводов, поскольку:
      • Не у всех игр в базе указан Steam App ID.
      • Не все названия в базе совпадают с названиями игр в Steam. 
Скрытый текст

Переработал логику в фильтрах по русскому переводу:

uMoyAmL.png

Теперь работает так:

  • Если вы нажимаете “только текст”, то показывает игры с переводом субтитров/интерфейса, но без озвучки.
  • Не изменилось:
    • Если вы нажимаете озвучка — показывает все игры с озвучкой.
    • Если вы нажимаете без перевода — показывает игры без перевода.
Скрытый текст

Дополнился функционал при поиске по каталогу:

3. Дополнительная информация об играх и фильтрация по русскому языку:

  • Обновление:
    • В каталоге поиска справа появилось меню “Русский перевод”:
      • Русский перевод (оставляет игры с русским интерфейсом/озвучкой/субтитрами)
      • Русская озвучка (оставляет игры, где обязательно есть русская озвучка)
      • Без русского (оставляет игры, где нет русского)
Скрытый текст

Добавил седьмую функцию.

7. Дополнительная информация при наведении на игру в ленте вашей активности Steam:

  • При наведении мыши слева появляется всплывающая подсказка с детальной информацией о игре.
  • Включает: название, изображение, дату выхода, издателей, разработчиков, серию игр, отзывы, информацию о раннем доступе, поддержку русского [и английского] языка и краткое описание.

 

 

 

 

Изменено пользователем 0wn3df1x
  • Лайк (+1) 1
  • Спасибо (+1) 2
  • +1 1

Поделиться сообщением


Ссылка на сообщение

SteamDB - Sales; Ultimate Enhancer || Tamper Monkey

SteamDB - Sales; Ultimate Enhancer - это расширенный пользовательский скрипт для раздела скидок на SteamDB, который добавляет множество полезных функций для более комфортного использования платформы. Скрипт объединяет в себе функционал нескольких популярных инструментов, предоставляя пользователю расширенные возможности для работы с играми, фильтрами и конвертацией валют.

Скрипт соединяет в себе функционал из:

Функционал скрипта:

Скрытый текст

1. Расширенные фильтры:

  • Русский перевод:
    • Фильтрация по наличию только текстового перевода (интерфейс/субтитры).
    • Фильтрация игр по наличию русской озвучки.
    • Фильтрация игр без русского перевода.
  • Списки (подходят для просмотра недоступных в России игр):
    • Сохранение кастомных списков игр (Список 1/Список 2).
    • Фильтрация уникального по пересечению списков.
  • Фильтр по дате начала распродажи (календарь).
  • Анализ РРЦ (Рекомендованной Региональной Цены) для РФ:
    • При выборе российской валюты (RUB) на SteamDB, скрипт позволяет анализировать цены игр относительно рекомендованных Steam цен для российского региона.
    • Слева от названия игры отображается индикатор:
      • < РРЦ (синий): Текущая полная цена игры в рублях ниже рекомендованной Steam. Отображается процент и сумма разницы.
      • = РРЦ (зеленый): Текущая полная цена игры в рублях соответствует рекомендованной Steam.
      • > РРЦ (красный): Текущая полная цена игры в рублях выше рекомендованной Steam. Отображается процент и сумма разницы.
      • Нет данных РРЦ: Если не удалось получить данные о цене в USD для расчета.
    • Добавлены кнопки для фильтрации игр по этому критерию: "< РРЦ", "= РРЦ", "> РРЦ".
    • Примечание: Эта функция требует получения начальных цен как для российского, так и для американского региона Steam через API, что может увеличить время обработки. РРЦ рассчитывается на основе текущей полной (не скидочной) цены игры в долларах США и таблицы рекомендованных цен Steam.
  • (Новое!) Фильтры по типу скидок и проценту от исторического минимума (ATL):
    Стандартный блок фильтров скидок SteamDB заменен на более продвинутый:
    • Фильтры по типу исторической цены: Позволяют отображать или скрывать игры на основе цвета их скидки на SteamDB (синий - новый ист. минимум, зеленый - повтор мин. цены, фиолетовый - мин. за 2 года).
    • Фильтры по процентам в историческом минимуме (ATL): Сравнивают текущий процент скидки с процентом скидки, который был при достижении игрой All-Time Low цены. Позволяют отфильтровать игры, которые сейчас выгоднее/так же/менее выгодны по проценту скидки, чем были при своем ATL.
      • % < Минимума: Текущий % скидки > Исторического % ATL (выгоднее).
      • % = Минимуму: Текущий % скидки = Историческому % ATL.
      • % > Минимума: Текущий % скидки < Исторического % ATL (менее выгодно по % скидки).
Скрытый текст

2. Конвертер валют:

  • Возможность ручной настройки курса (по умолчанию: 1 = 0.19, задано для перевода тенге в рубли; можно вбить любой курс, в зависимости от используемой вами валюты).
Скрытый текст

3. Расширенная информация

  • При наведении на игру отображается всплывающая подсказка с:
    • Серией игр (франшизой)
    • Процентом положительных отзывов
    • Статусом Early Access (ранний доступ)
    • Подробной информацией о языковой поддержке (русский [и английский])
    • Кратким описанием игры
  • Индикатор РРЦ (РФ): Как описано выше, слева от названия каждой игры (при активной российской валюте) отображается информация о соответствии цены рекомендованной Steam.
  • (Новое!) Визуализация процента скидки ATL: Текст "at -X%" в информации об All-Time Low подсвечивается цветом, показывая, насколько текущий процент скидки выгоден по сравнению с историческим процентом ATL.
    • Синий: Текущий % скидки > Исторического % ATL.
    • Зеленый: Текущий % скидки = Историческому % ATL.
    • Фиолетовый: Текущий % скидки < Исторического % ATL.
Скрытый текст

4. (Новое!) Калькулятор желаемого

  • Активируется кнопкой "Высчитать" на панели скрипта, если на SteamDB выбран фильтр "Your wishlist" в "Filter by type".
  • Собирает данные о ценах для игр из вашего списка желаемого.
  • Отображает в модальном окне таблицу с AppID, названием, текущей скидкой, текущей ценой, ~полной ценой, All-time Low, 2-year Low и лучшей ценой для расчета.
  • Позволяет сортировать таблицу по любому столбцу.
  • Рассчитывает и показывает итоговые суммы для покупки всех игр по ~полным ценам и по лучшим доступным ценам.

Инструкция по установке:

  1. Установите расширение для пользовательских скриптов (например, Tampermonkey или Greasemonkey)
  2. Создайте новый пользовательский скрипт и вставьте код

Инструкция по использованию:

Скрытый текст
  1. Зайдите на страницу Sales на SteamDB (или любую другую, где работает скрипт, например, Most Followed или ваш Wishlist при активном соответствующем фильтре на SteamDB).
  2. Проставьте нужную валюту и основные фильтры SteamDB, если необходимо.
    • Рекомендуется установить количество обзоров >100 и рейтинг больше 70 или 80.
    • Учтите, что чем больше игр отображается, тем больше запросов будет отправлено к Steam для получения данных (в одном запросе содержится до 200 игр).
    • По внутренним ограничениям SteamDB система не может вывести больше 10 тысяч игр.
  3. Нажмите кнопку "Обработать игры" (или "Высчитать" для калькулятора желаемого) в панели скрипта.
    • Скрипт автоматически выберет "All (slow)" в выпадающем списке "entries per page" и покажет таймер обратного отсчета до начала сбора данных. Это необходимо для загрузки всех игр на страницу.
    • Дождитесь завершения обработки. Статус будет отображаться на кнопке и в индикаторе.
    • Если выбрана российская валюта (для анализа РРЦ), сбор данных будет проходить в два этапа (RU и US цены), что может занять больше времени.
    • Для других валют (или если анализ РРЦ не требуется/невозможен) сбор данных пройдет в один этап.
Скрытый текст
  • Русский перевод:
    • Если вы хотите оставить игры только с текстовым переводом, выберите "Только текст".
    • Если хотите оставить игры с озвучкой, выберите "Озвучка".
    • Если хотите оставить игры без русского перевода, используйте кнопку "Без перевода".
  • Фильтр по дате:
    • Если вы хотите оставить игры, скидка на которые появилась после определённой даты, нажмите по значку календаря, выберите нужную дату и нажмите "Фильтр по дате".
  • Фильтр списков (для недоступных игр):
    1. Выберите валюту первого региона и примените, чтобы страница обновилась.
    2. Нажмите на кнопку "Обработать игры". Скрипт автоматически выберет "All entries" и после загрузки и обработки данных, нажмите кнопку "Список 1" — вы получите уведомление, что список сохранён.
    3. Выберите валюту второго региона и примените, чтобы страница обновилась.
    4. Нажмите на кнопку "Обработать игры". После загрузки и обработки, нажмите кнопку "Список 2".
    5. Нажмите на кнопку "Фильтр списков", чтобы скрыть игры, присутствующие в обоих списках, и оставить только уникальные для текущего региона игры.
  • Фильтр РРЦ (РФ):
    • Этот фильтр активен только при выборе российской валюты.
    • Нажмите на кнопки "< РРЦ", "= РРЦ" или "> РРЦ" для отображения игр, чья цена ниже, соответствует или выше рекомендованной Steam цены соответственно. Можно выбрать несколько критериев одновременно.
    • Повторное нажатие на кнопку снимает соответствующий фильтр.
  • (Новое!) Фильтры по скидкам SteamDB (заменяют стандартные):
    • Используйте чекбоксы в разделе "Фильтры по скидкам" на панели SteamDB (справа) для выбора нужных критериев.
    • Фильтры по типу исторической цены: Отмечайте чекбоксы "Показать" или "Скрыть" для категорий "Ист. минимум", "Повтор мин. цены", "Мин. за 2 года".
    • Фильтры по процентам в ATL: Аналогично, используйте чекбоксы "Показать" или "Скрыть" для категорий "% < Минимума", "% = Минимуму", "% > Минимума".
    • Фильтры применяются немедленно.
Скрытый текст
  • Введите нужный вам курс в соответствующее поле.
  • Нажмите "Конвертировать".
  • Цены в таблице будут пересчитаны. Рекомендуется производить конвертацию до применения других фильтров, так как скрипт конвертирует цены только у тех игр, которые отображаются в данный момент.
Скрытый текст
  • Для получения дополнительной информации об игре наведите на неё курсор — подсказка выведется справа.
  • Индикатор РРЦ (РФ): После успешной обработки игр (при активной российской валюте) слева от названия каждой игры появится индикатор сравнения текущей цены с рекомендованной Steam.
  • (Новое!) Визуализация процента скидки ATL: В информации об All-Time Low (под названием игры), текст "at -X%" будет подсвечен цветом, указывающим на выгодность текущего процента скидки по сравнению с историческим процентом ATL.
  • Если вы хотите включить отображение информации об английском языке в игре, в настройке скрипта (в коде) проставьте true в:

scriptsConfig.toggleEnglishLangInfo: false

 

Скриншоты:

Скрытый текст

Игры, доступные в Казахстане и недоступные в России, с русским текстовым переводом, скидки на которые появились не раньше 02.06.2025

BNZCExN.png

 


Игры, доступные в русском регионе, цена на которые ниже рекомендуемых региональных Valve цен, с русской озвучкой и ценой, которая соответствует прошлому историческому минимуму

hcu3kck.png

 


Интерфейс калькулятора желаемого

s53CiaE.png

 

Для облегчения страницы форума, залил скрипт на внешний ресурс:

Код скрипта на GreasyFork

Скрытый текст

 

 

 

 

Скрытый текст

Версия 1.3

  • Автоматический выбор "All entries":
    • Скрипт теперь автоматически выбирает "All (slow)" в выпадающем списке количества записей на странице и ожидает загрузки перед началом обработки. Больше не нужно делать это вручную. Появится таймер обратного отсчета, информирующий о процессе.
  • Калькулятор желаемого:
    • Добавлена новая кнопка "Высчитать" в панели скрипта, когда активен фильтр "Your wishlist" в "Filter by type" на SteamDB.
    • Эта функция собирает данные о ценах (текущая, исторический минимум, минимум за 2 года) для игр из вашего списка желаемого и отображает их в таблице в модальном окне.
    • Таблица включает: AppID, Название, Текущую скидку, Текущую цену, ~Полную цену (расчетную), All-time Low, 2-year Low и Цену для расчета (лучшую из доступных).
    • Поддерживается сортировка по любому столбцу.
    • Также отображаются итоговые суммы: если купить все по ~полным ценам и если купить все по лучшим доступным ценам.
  • Расширенные фильтры по скидкам (заменяют стандартные фильтры SteamDB):
    Скрытый текст

    Стандартный блок фильтров скидок на SteamDB был заменен на более продвинутый, предоставляемый скриптом. Он включает две категории:

    1. Фильтры по типу исторической цены (абсолютной):
      • Ист. минимум (синий цвет скидки на SteamDB): Показать/Скрыть игры, цена на которые является новым историческим минимумом.
      • Повтор мин. цены (зеленый цвет скидки): Показать/Скрыть игры, цена на которые повторяет предыдущий исторический минимум.
      • Мин. за 2 года (фиолетовый цвет скидки): Показать/Скрыть игры, цена на которые является минимальной за последние два года (но не историческим минимумом).
    2. Фильтры по процентам в историческом минимуме (ATL):
      Эта группа фильтров анализирует соотношение текущего процента скидки игры и процента скидки, который был у нее при достижении All-Time Low (ATL) цены.
      • % < Минимума (синий): Показать/Скрыть игры, где текущий процент скидки выше, чем процент скидки при ATL (т.е. игра сейчас выгоднее, чем была при ATL по проценту).
      • % = Минимуму (зеленый): Показать/Скрыть игры, где текущий процент скидки равен проценту скидки при ATL.
      • % > Минимума (фиолетовый): Показать/Скрыть игры, где текущий процент скидки ниже, чем процент скидки при ATL (т.е. игра сейчас менее выгодна по проценту, чем была при ATL, даже если абсолютная цена ATL такая же или ниже).

    Для каждого фильтра есть две опции: "Показать" (оставляет только соответствующие игры) и "Скрыть" (исключает их). Если активен фильтр "Показать" для какого-либо типа, то соответствующий фильтр "Скрыть" автоматически деактивируется, и наоборот.

     

  • Визуализация процента скидки ATL:
    • Текст вида "at -X%" в дополнительной информации под названием игры (где указан All-Time Low) теперь подсвечивается цветом в зависимости от того, как текущий процент скидки соотносится с этим историческим процентом скидки:
      • Синий:Текущий % скидки > Исторического % ATL (выгоднее).
      • Зеленый: Текущий % скидки = Историческому % ATL.
      • Фиолетовый: Текущий % скидки < Исторического % ATL (менее выгодно по % скидки).
  • Обновлен интерфейс панели управления скрипта:
    • Улучшено расположение элементов.
    • Обновлены тексты статусов и кнопок для лучшего информирования пользователя.
  • Изменение цветовой схемы для индикатора РРЦ:
    • = РРЦ (соответствует) теперь зеленый (ранее был синий).
    • < РРЦ (дешевле) теперь синий (ранее был зеленый).
    • > РРЦ (дороже) остался красным.
Скрытый текст

Игры, доступные в Казахстане и недоступные в России, с русским текстовым переводом, скидки на которые появились не раньше 02.06.2025

BNZCExN.png

 


Игры, доступные в русском регионе, цена на которые ниже рекомендуемых региональных Valve цен, с русской озвучкой и ценой, которая соответствует прошлому историческому минимуму

hcu3kck.png

 


Интерфейс калькулятора желаемого

s53CiaE.png

Скрытый текст

Версия 1.2 - Анализ РРЦ

  • Новая функция: Анализ РРЦ (Рекомендованной Региональной Цены) для РФ.
    • Предыдущая система "Рангов цен (RU)", основанная на статичной базе данных из Gist, была удалена. Статичная база быстро устаревала и требовала ручного обновления, что делало ее неэффективной.
    • Взамен добавлена динамическая система анализа РРЦ. Если на SteamDB выбрана российская валюта (RUB), скрипт теперь:
      1. При нажатии "Обработать игры" получает начальные (полные, без скидок) цены игр как для российского региона (в рублях), так и для американского региона (в долларах США) через Steam API.
      2. Рассчитывает рекомендованную цену в рублях на основе цены в долларах США, используя официальную сетку ценообразования Steam.
      3. Отображает слева от названия игры индикатор, показывающий, ниже ли текущая рублевая цена, равна или выше рекомендованной (< РРЦ, = РРЦ, > РРЦ), а также разницу в процентах и рублях.
      4. Позволяет фильтровать игры по этим трем категориям РРЦ.
    • Этот функционал активен только при выборе российской валюты на SteamDB. Для других валют скрипт работает в прежнем режиме (без анализа РРЦ).

ngx3juW.png

Скрытый текст

Версия 1.1 - Ранги и выгода для РФ

  • Главное нововведение:

    • Интеграция данных о выгодности цен в российском регионе Steam
      (На основе данных исследования, собранных 3 апреля 2025 года для ~80 тыс. игр)

      • Новый фильтр "Ранги цен (RU):" Позволяет легко отфильтровать игры по их ценовому рангу в России (например, показать только игры, где цена самая низкая в мире (ранг 1) или входит в топ-N).

      • Визуальный индикатор: Слева от каждой игры теперь отображаются 5 ключевых показателей:
                1.  Ранг цены РФ (1-39).
                2.  % разница с ближайшим конкурентом.
                3.  Руб. разница с ближайшим конкурентом.
                4.  % разница со средней ценой.
                5.  Руб. разница со средней ценой.

        • Для игр, вышедших после 3 апреля 2025, будет указано "Нет данных".

      • Доп. информация:
        • Данные о рангах подгружаются с Gist и кэшируются для быстрой загрузки. Добавлен индикатор статуса загрузки этих данных.

g9LG5JT.jpeg

 

 

Изменено пользователем 0wn3df1x
  • Лайк (+1) 1

Поделиться сообщением


Ссылка на сообщение

SteamDB - Sales; Улучшатор || Tamper Monkey — функционал перемещён в SteamDB - Sales; Ultimate Enhancer.

Скрытый текст

 

Обновление: Объединение и улучшение скриптов для раздела Sales на SteamDB. || TamperMonkey

Скрипты “SteamDB - Sales; Фильтр Недоступных” и “Steam DB — Sales; конвертация тенге в рубли” под TamperMonkey были объединены и улучшены:

  1. В рамках “фильтра недоступных” было добавлено поле с календарём для выборы даты в формате “день.месяц.год” и кнопка “отсеять по дате”. В чём польза этой функции? Периодически в Steam появляются новые скидки, но функционал SteamDB не позволяет выставить дату появления скидок. А значит, если вы отфильтруете таблицу по размеру скидок, то увидите не только новые, но и кучу старых. Данная фунция это исправляет и позволяет оставить только скидки, появившиеся после заданной даты.
  2. В рамках “конвертации” произошло расширение списка валют. Теперь конвертировать можно не только тенге. но и любую представленную на SteamDB валюту. Требуется только вбить курс валюты по отношению к вашей и нажать “конвертировать”. 
  3. Теперь оба скрипта объединены в “SteamDB - Sales; Улучшатор”.

Предназначение скрипта: На SteamDB есть раздел Sales, который позволяет посмотреть и отфильтровать актуальные скидки. Если кто-то смотрит цены для Казахстана или США, то ему необходимо каждый раз в голове конвертировать их в свою валюту. Данный скрипт позволяет задать курс и конвертировать любую валюту в другую валюту. Также данный скрипт позволяет увидеть, какие игры на распродаже недоступны в каком-то регионе. Ещё он позволяет увидеть скидки, появившиеся после заданной даты.

gkujek0.png
 

Скрытый текст


// ==UserScript==
// @name         SteamDB - Sales; Улучшатор
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  Добавляет списки для фильтрации, фильтр по дате начала скидки и конвертацию валют
// @author       0wn3df1x
// @match        https://steamdb.info/sales/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Создание кнопки
    function createButton(text, onClick) {
        const button = document.createElement('button');
        button.textContent = text;
        button.style.margin = '5px';
        button.addEventListener('click', onClick);
        button.classList.add('steam-button'); // Добавляем класс для стилизации
        return button;
    }

    // Сбор всех ID
    function getAppIds() {
        const appIds = [];
        const rows = document.querySelectorAll('tr.app');
        rows.forEach(row => {
            const appId = row.getAttribute('data-appid');
            if (appId) {
                appIds.push(appId);
            }
        });
        return appIds;
    }

    // Функция для скрытия строк, которые есть в обоих списках
    function filterTable() {
        const list1 = JSON.parse(localStorage.getItem('list1') || '[]');
        const list2 = JSON.parse(localStorage.getItem('list2') || '[]');
        const commonAppIds = list1.filter(id => list2.includes(id));

        const rows = document.querySelectorAll('tr.app');
        rows.forEach(row => {
            const appId = row.getAttribute('data-appid');
            if (commonAppIds.includes(appId)) {
                row.style.display = 'none';
            } else {
                row.style.display = '';
            }
        });
    }

    // Функция для фильтрации по дате начала скидки
    function filterByDate() {
        const selectedDate = document.getElementById('datePicker').value;
        if (!selectedDate) {
            alert('Пожалуйста, выберите дату.');
            return;
        }

        const selectedTimestamp = new Date(selectedDate).getTime() / 1000;

        const rows = document.querySelectorAll('tr.app');
        rows.forEach(row => {
            const timeagoElements = row.querySelectorAll('td.timeago.dt-type-numeric');
            let startTimestamp;
            if (timeagoElements.length > 1) {
                startTimestamp = parseInt(timeagoElements[1].getAttribute('data-sort'), 10);
            } else if (timeagoElements.length === 1) {
                startTimestamp = parseInt(timeagoElements[0].getAttribute('data-sort'), 10);
            } else {
                startTimestamp = 0;
            }

            if (startTimestamp < selectedTimestamp) {
                row.style.display = 'none';
            } else {
                row.style.display = '';
            }
        });
    }

    // Стандартный курс валюты к выбранной валюте
    const defaultExchangeRate = 1;

    // Функция для конвертации цены из любой валюты в выбранную валюту
    function convertPrice(priceInSourceCurrency, exchangeRate) {
        const priceInTargetCurrency = priceInSourceCurrency * exchangeRate;
        return priceInTargetCurrency.toFixed(2); // Округляем до двух знаков после запятой
    }

    // Функция для добавления кнопки и текстового поля
    function addConvertButtonAndInput() {
        const headerSubtitle = document.querySelector('h2.header-subtitle');
        if (headerSubtitle) {
            const container = document.createElement('div');
            container.classList.add('convert-container');

            const input = document.createElement('input');
            input.type = 'text';
            input.value = defaultExchangeRate;
            input.classList.add('exchange-rate-input');

            const button = document.createElement('button');
            button.textContent = 'Конвертировать';
            button.classList.add('convert-button');
            button.addEventListener('click', () => {
                const exchangeRate = parseFloat(input.value);
                if (!isNaN(exchangeRate)) {
                    convertPrices(exchangeRate);
                }
            });

            container.appendChild(input);
            container.appendChild(button);
            headerSubtitle.parentNode.insertBefore(container, headerSubtitle.nextSibling);
        }
    }

    // Функция для конвертации цен
    function convertPrices(exchangeRate) {
        const appRows = document.querySelectorAll('tr.app');

        appRows.forEach(row => {
            const priceElements = row.querySelectorAll('td.dt-type-numeric');
            if (priceElements.length >= 3) {
                const priceElement = priceElements[2]; // Третий элемент (индексация с 0)
                const priceText = priceElement.textContent.trim();

                // Проверка на Peruvian sol
                if (priceText.includes('S/.')) {
                    const priceMatch = priceText.match(/S\/\.([0-9,.]+)/);
                    if (priceMatch) {
                        const priceInSourceCurrency = parseFloat(priceMatch[1].replace(',', '.'));
                        if (!isNaN(priceInSourceCurrency)) {
                            const priceInTargetCurrency = convertPrice(priceInSourceCurrency, exchangeRate);
                            priceElement.textContent = priceInTargetCurrency;
                        }
                    }
                } else {
                    const priceMatch = priceText.match(/([0-9,.]+)/); // Упрощенное регулярное выражение
                    if (priceMatch) {
                        const priceInSourceCurrency = parseFloat(priceMatch[1].replace(',', '.'));
                        if (!isNaN(priceInSourceCurrency)) {
                            const priceInTargetCurrency = convertPrice(priceInSourceCurrency, exchangeRate);
                            priceElement.textContent = priceInTargetCurrency;
                        }
                    }
                }
            }
        });
    }

    // Добавление кнопок и элементов управления
    const headerSubtitle = document.querySelector('h2.header-subtitle');
    if (headerSubtitle) {
        const list1Button = createButton('Список 1', () => {
            const appIds = getAppIds();
            localStorage.setItem('list1', JSON.stringify(appIds));
            alert('Список 1 сохранен');
        });
        headerSubtitle.appendChild(list1Button);

        const list2Button = createButton('Список 2', () => {
            const appIds = getAppIds();
            localStorage.setItem('list2', JSON.stringify(appIds));
            alert('Список 2 сохранен');
        });
        headerSubtitle.appendChild(list2Button);

        const filterButton = createButton('Фильтр', () => {
            filterTable();
            alert('Таблица отфильтрована');
        });
        headerSubtitle.appendChild(filterButton);

        const datePicker = document.createElement('input');
        datePicker.type = 'date';
        datePicker.id = 'datePicker';
        datePicker.style.margin = '5px';
        headerSubtitle.appendChild(datePicker);

        const filterByDateButton = createButton('Отсеять по дате', () => {
            filterByDate();
            alert('Таблица отфильтрована по дате');
        });
        headerSubtitle.appendChild(filterByDateButton);

        addConvertButtonAndInput();
    }

    // Добавление стилей CSS
    const style = document.createElement('style');
    style.textContent = `
        .steam-button {
            background-color: #171a21;
            color: #c7d5e0;
            border: 1px solid #616c77;
            padding: 5px 10px;
            border-radius: 3px;
            transition: background-color 0.3s, color 0.3s;
        }
        .steam-button:hover {
            background-color: #616c77;
            color: #ffffff;
        }
        .convert-container {
            display: flex;
            align-items: center;
        }
        .exchange-rate-input {
            margin-right: 10px;
            padding: 5px;
            border: 1px solid #4a698a;
            border-radius: 3px;
        }
        .convert-button {
            background-color: #17202f;
            color: #acdbf5;
            border: 1px solid #4a698a;
            padding: 5px 10px;
            border-radius: 3px;
            cursor: pointer;
            transition: background-color 0.3s, color 0.3s;
        }
        .convert-button:hover {
            background-color: #4a698a;
            color: #ffffff;
        }
    `;
    document.head.appendChild(style);
})();

 

Скрытый текст
  1. Заходим на страницу Sales на SteamDB.
  2. Проставляем нужную нам валюту и нужные нам фильтры, если необходимо.
  3. В выпадающем списке entries per page выбираем All (slow) и ждём, пока все предложения прогрузятся.
    siKdGDV.png
  4. Вбиваем нужный курс в текстовое поле и нажимаем “конвертировать”. Вуаля:
    YZ3ilwG.png

P.S. В поле const defaultExchangeRate = скрипта находится базовый курс. Можете заменить его на курс актуальный для вас валюты, чтобы при обновлении страницы видеть его.

Скрытый текст
  1. Заходим на страницу Sales на SteamDB.
  2. Проставляем валюту “рубли” (или любую другую) и нужные нам фильтры, если необходимо.
  3. В выпадающем списке entries per page выбираем All (slow) и ждём, пока все предложения прогрузятся.siKdGDV.png
  4. Жмём на кнопку “Список 1” и получаем уведомление, что список сохранён.
    UA0OQ9J.png
     
  5. Проставляем валюту. Снова в выпадающем списке entries per page выбираем All (slow) и ждём, пока все предложения прогрузятся. После чего жмём кнопку “Список 2”, чтобы сохранить все игры из казахского региона.
  6. После этого можем нажать кнопку фильтр. Сперва вам покажется, что всё чуть подвисло и съехало, но затем все позиции прогрузятся. Останутся только игры, которых нет в одном из списков.
    3xur1Ex.png
     
  7. Обновление (16.07.2024): Вы можете выбрать дату, нажав на символ календаря или просто вбив её вручную, а затем нажать “отсеять по дате”, чтобы вам были показаны игры, скидка на которые появилась после этой даты.
    (Важно, чтобы entries per page стояла на “All (slow)”
    kOp58sS.png

P.S. Скрипт прекрасно работает с конвертацией тенге, так что можно применять их одновременно.
P.S.2: Если после этого вы выберете рубли, снова проставите “All (Slow)” и снова нажмете фильтр — увидите игры, которые почему-то не показываются для Казахстана.

4utM6ip.png

Как вы можете понять из обеих инструкций, главное в выпадающем списке entries per page выбираем All (slow).
Всё универсально и доступно.

 

 

Изменено пользователем 0wn3df1x
  • Лайк (+1) 1

Поделиться сообщением


Ссылка на сообщение

Обновление: Улучшение парсера игр с сайта GamesForFarm. || TamperMonkey

Когда вы запустите скрипт впервые — он сохранит данные о текущем наборе игр в LocalStorage.
При следующем запуске скрипта вы получите две таблицы вместо одной.

  • В первой таблице, как и раньше, будут перечислены все игры, их наличие в вашем списке желаемого и на вашем аккаунте.
  • Во второй таблице будут указаны только игры, которые были добавлены, удалены и цены на которые изменились:
     
Steam ID Название игры Цена Размер скидки rgWishlist rgOwnedApps Адрес картинки Инфо
3154130 Space Memory: Animals 1,99 -94%     https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/3154130/ Цена изменилась с 4,99 на 1,99
647830 LEGO® Marvel Super Heroes 2 229,9 -88%     https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/647830/ Удалили



Предназначение скрипта:
Сайт GamesForFarm ориентирован на людей, которые держат фермы в Steam, т.е. закупают оптом дешёвые игры, чтобы выбивать на них карточки, продавать их и так далее. Соответственно, владельцы сайта ищут оптовые партии ключей на зарубежных площадках, а затем продают их в России с небольшой наценкой. Большая часть игр на этом сайте является откровенным мусором, но иногда можно дёшево урвать хорошие проекты (в основном они попадают на сайт из бандлов). Полезность скрипта в том, что он позволяет получить весь каталог игр сайта GamesForFarm в табличном виде. Также он позволяет узнать, есть ли в каталоге игры из вашего списка желаемого и пометить игры, которые у вас уже есть. 

Спойлер

// ==UserScript==
// @name         GamesForFarm - Парсер
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Получает все игры в GamesForFarm и ищет имеющиеся и желаемые
// @author       0wn3df1x
// @match        https://gamesforfarm.com*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Добавляем стили
    const style = document.createElement('style');
    style.textContent = `
        .load-button {
            background-color: #171a21;
            color: #acb2b8;
            border: 1px solid #171a21;
            padding: 10px 20px;
            font-size: 16px;
            cursor: pointer;
            transition: background-color 0.3s, color 0.3s;
            margin-left: 10px;
            margin-top: 10px;
        }
        .load-button:hover {
            background-color: #66c0f4;
            color: #171a21;
        }
        .json-input {
            background-color: #171a21;
            color: #acb2b8;
            border: 1px solid #171a21;
            padding: 10px;
            font-size: 14px;
            width: 300px;
            height: 100px;
            margin-left: 10px;
            margin-top: 10px;
            resize: none;
        }
        .data-link {
            margin-left: 10px;
            margin-top: 10px;
            color: #66c0f4;
            text-decoration: none;
        }
        .data-link:hover {
            text-decoration: underline;
        }
    `;
    document.head.appendChild(style);

    // Добавляем кнопку "Загрузить таблицу" и текстовое поле под <div class="main__title">Каталог</div>
    const mainTitle = document.querySelector('.main__title');
    if (mainTitle) {
        const loadButton = document.createElement('button');
        loadButton.textContent = 'Загрузить таблицу';
        loadButton.className = 'load-button';

        const jsonInput = document.createElement('textarea');
        jsonInput.className = 'json-input';
        jsonInput.placeholder = 'Вставьте JSON сюда';

        const dataLink = document.createElement('a');
        dataLink.href = 'https://store.steampowered.com/dynamicstore/userdata/';
        dataLink.target = '_blank'; // Открывает ссылку в новой вкладке
        dataLink.textContent = 'Получить данные своей UserData в Steam';
        dataLink.className = 'data-link';

        mainTitle.parentNode.insertBefore(loadButton, mainTitle.nextSibling);
        mainTitle.parentNode.insertBefore(jsonInput, loadButton.nextSibling);
        mainTitle.parentNode.insertBefore(dataLink, jsonInput.nextSibling);

        // Обработчик нажатия на кнопку "Загрузить таблицу"
        loadButton.addEventListener('click', function() {
            let jsonData = {};
            try {
                if (jsonInput.value.trim() !== '') {
                    jsonData = JSON.parse(jsonInput.value);
                }
            } catch (e) {
                alert('Неверный формат JSON');
                return;
            }

            const rgWishlist = new Set(jsonData.rgWishlist || []);
            const rgOwnedApps = new Set(jsonData.rgOwnedApps || []);

            // Находим кнопку и нажимаем на неё
            const button = document.querySelector('.btn-reset.product__more-button.fn_product_more.ddd3');
            if (button) {
                button.click();
            }

            // Ждём 5 секунд (увеличим время ожидания)
            setTimeout(() => {
                // Создаем массив для хранения данных всех игр
                const gamesData = [];

                // Находим все элементы с классом product__item внутри элемента с id gamesCatalog
                const productItems = document.querySelectorAll('#gamesCatalog .product__item');

                productItems.forEach(item => {
                    // Извлекаем Steam ID
                    const steamLink = item.querySelector('a[href*="store.steampowered.com/app/"]');
                    let steamId = '-';
                    if (steamLink) {
                        const href = steamLink.getAttribute('href');
                        const match = href.match(/\/app\/(\d+)\//);
                        if (match && match[1]) {
                            steamId = match[1];
                        }
                    }

                    // Пропускаем элемент, если нет Steam ID
                    if (steamId === '-') {
                        return;
                    }

                    // Извлекаем цену
                    const priceElement = item.querySelector('.product__box-price');
                    let price = '-';
                    if (priceElement) {
                        price = priceElement.textContent.trim();
                        price = formatPrice(price);
                    }

                    // Извлекаем адрес картинки
                    const imageElement = item.querySelector('.product__box-image img');
                    let imageUrl = '-';
                    if (imageElement) {
                        imageUrl = imageElement.getAttribute('data-src');
                    }

                    // Извлекаем название игры
                    const titleElement = item.querySelector('.product__box-title a');
                    let gameTitle = '-';
                    if (titleElement) {
                        gameTitle = titleElement.textContent.trim();
                    }

                    // Извлекаем размер скидки
                    const discountElement = item.querySelector('.product__box-prop.prop—discount');
                    let discount = '-';
                    if (discountElement) {
                        discount = discountElement.textContent.trim();
                    }

                    // Проверяем наличие Steam ID в списках
                    const inWishlist = rgWishlist.has(parseInt(steamId)) ? '[VVV]' : '';
                    const inOwnedApps = rgOwnedApps.has(parseInt(steamId)) ? '[VVV]' : '';

                    // Добавляем данные в массив
                    gamesData.push({
                        steamId,
                        price,
                        imageUrl,
                        gameTitle,
                        discount,
                        inWishlist,
                        inOwnedApps
                    });
                });

                // Сортируем массив сначала по наличию в rgWishlist, затем по цене
                gamesData.sort((a, b) => {
                    if (a.inWishlist && !b.inWishlist) return -1;
                    if (!a.inWishlist && b.inWishlist) return 1;
                    return parseFloat(b.price.replace(',', '.')) - parseFloat(a.price.replace(',', '.'));
                });

                // Создаем новое окно для вывода результатов
                const newWindow = window.open("", "_blank", "width=600,height=400");
                newWindow.document.write('<html><head><title>Результаты</title></head><body><table border="1" id="gamesTable"><tr><th>Steam ID</th><th>Название игры</th><th>Цена</th><th>Размер скидки</th>' + (jsonData.rgWishlist ? '<th>rgWishlist</th>' : '') + (jsonData.rgOwnedApps ? '<th>rgOwnedApps</th>' : '') + '<th>Адрес картинки</th></tr>');

                gamesData.forEach(game => {
                    newWindow.document.write(`<tr><td>${game.steamId}</td><td>${game.gameTitle}</td><td>${game.price}</td><td>${game.discount}</td>${jsonData.rgWishlist ? `<td>${game.inWishlist}</td>` : ''}${jsonData.rgOwnedApps ? `<td>${game.inOwnedApps}</td>` : ''}<td>${game.imageUrl}</td></tr>`);
                });

                newWindow.document.write('</table></body></html>');
                newWindow.document.close();

                // Добавляем функциональность сортировки таблицы по клику на заголовки
                const table = newWindow.document.getElementById('gamesTable');
                if (table) {
                    const headers = table.getElementsByTagName('th');
                    for (let i = 0; i < headers.length; i++) {
                        headers[i].addEventListener('click', () => {
                            sortTable(table, i);
                        });
                    }
                }
            }, 5000); // Ожидание 5 секунд
        });
    }

    // Функция для форматирования цены
    function formatPrice(price) {
        return price.replace('.', ',').replace('₽', '').trim();
    }

    // Функция для сортировки таблицы
    function sortTable(table, columnIndex) {
        const rows = Array.from(table.rows).slice(1);
        const isNumeric = columnIndex === 0 || columnIndex === 2; // Steam ID и Цена

        rows.sort((a, b) => {
            const aValue = a.cells[columnIndex].textContent;
            const bValue = b.cells[columnIndex].textContent;

            if (isNumeric) {
                return parseFloat(aValue.replace(',', '.')) - parseFloat(bValue.replace(',', '.'));
            } else {
                return aValue.localeCompare(bValue);
            }
        });

        // Определяем направление сортировки
        const header = table.rows[0].cells[columnIndex];
        const sortDirection = header.getAttribute('data-sort') || 'asc';
        if (sortDirection === 'asc') {
            rows.reverse();
            header.setAttribute('data-sort', 'desc');
        } else {
            header.setAttribute('data-sort', 'asc');
        }

        // Очищаем таблицу и добавляем отсортированные строки
        while (table.rows.length > 1) {
            table.deleteRow(1);
        }

        rows.forEach(row => table.appendChild(row));
    }
})();
Спойлер

0. Если вы хотите узнать, какие игры, продающиеся на сайте, есть у вас в списке желаемого и на аккаунте, то читайте пункты 0-4, если вы хотите просто получить таблицу имеющихся на сайте игры, то можете перейти к пункту 5.

Итак. Что касается списка имеющихся игр и списка желаемого — они содержатся в юзердате. Если вы не пользуетесь Steam в браузере и не вошли в аккаунт со своего браузера — надо войти, т.к. Userdata пользователя доступна только ему самому. После установки скрипта вы увидите на странице магазина три новых элемента:
sS8iaNW.png

1. Щёлкнув по первому элементу — вы окажетесь на странице со своей Userdata
Там хранится информация о вашем списке желаемого, имеющихся играх и тех играх, которые вы решили скрыть в магазине. Также там есть информация о рекомендуемых жанрах и кураторах. Но ничего конфиденциального — если есть желание, можете проверить.
wP5cmnD.png

2. Нужно скопировать данные своей Userdata и вставить в текстовое окно.
fuv8fee.png

3. После чего щёлкнуть “Загрузить таблицу”. Через 5 секунд откроется новое окно, в котором вы увидите:
50NfDjF.png

В самом верху списка идут игры, которые есть у вас в списке желаемого. Упорядочены по убыванию цены. Затем идут все остальные игры аналогично по убыванию цены. Можно щёлкать по заголовкам, чтобы сортировать данные.

Когда вы запустите скрипт больше двух раз, то у вас откроются ещё одна таблица, в которой будут содержаться данные только об играх, которые добавились, были удалены или цена на которые изменилась:

Steam ID Название игры Цена Размер скидки rgWishlist rgOwnedApps Адрес картинки Инфо
3154130 Space Memory: Animals 1,99 -94%     https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/3154130/header.jpg?t=1724396439 Цена изменилась с 4,99 на 1,99
647830 LEGO® Marvel Super Heroes 2 229,9 -88%     https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/647830/header.jpg?t=1635187803 Удалили


4. Вы можете скопировать данные и вставить их на Google-таблицу. После чего можете использовать формулу Image, чтобы отобразить картинки, будет выглядеть так:
GNJ4KRK.png

5. Если вы не вставите Json и просто нажмёте кнопку “Загрузить таблицу”, то таблица всё равно откроется в новом окне, просто в ней не будет информации об играх, которые есть у вас на аккаунте и в списке желаемого. Таблицу также можно будет упорядочить, щёлкая по заголовкам и аналогично можно будет скопировать в Google-таблицы для дальнейшей работы.

Изменено пользователем 0wn3df1x
  • Лайк (+1) 1
  • Спасибо (+1) 1

Поделиться сообщением


Ссылка на сообщение

Скрипт для улучшения поиска на Plati market || Tamper Monkey — функционал перемещён в Plati.Market; Ultimate Enhancer.

Скрытый текст

 

Сейчас, когда вы вбиваете название какой-то игры в поиске на плати, — вам хаотично выдаёт товары с разной ценой на нескольких страницах. Данный скрипт призван это исправить.

При использовании скрипта на первой странице результатов поиска появится кнопка “отсортировать”.

При нажатии скрипт пройдёт по всем страницам и сохранит данные в локальное хранилище, после чего восстановит их на последней странице и отсортирует по возрастанию цены.

Демонстрация:


UPD: Добавил кнопку “Скрыть туфту”, которая появляется после сортировки.
Данную кнопку можно персонализировать в этом фрагменте кода:


    // Функция для скрытия аккаунтов и других элементов
    function hideAccounts() {
        const keywords = ['аккаунт', 'офлайн', 'оффлайн', 'xbox', 'egs', 'PS5', 'PS4', 'Epic Games'];

Можете убирать или добавлять ключевые слова. При нажатии кнопки элементы, в которых были эти ключевые слова, исчезнут. 

UPD 2: Также в качестве keyword можно указать название продавца в магазине, если вы знаете, что он продаёт то, что вас не интересует, или он просто вам не нравится (обновил скрипт ещё раз).

 

Изменено пользователем 0wn3df1x
  • Лайк (+1) 1
  • Спасибо (+1) 2

Поделиться сообщением


Ссылка на сообщение

SteamMarket — Казначей || Tamper Monkey — функционал перемещён в Ultimate Steam Enhancer.

Скрытый текст

Данный скрипт предназначен для торговой площадки Steam и добавляет информацию о продажах предмета.
Предназначен для тех, кто любит считать чужие деньги и офигевать с сумм с продажи пикселей.

Скрытый текст


// ==UserScript==
// @name         SteamMarket — Казначей
// @namespace    https://steamcommunity.com/market/
// @version      1.0
// @description  Выводит информацию о продажах предмета на торговой площадке Steam
// @author       0wn3df1x
// @match        https://steamcommunity.com/market/listings/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Функция для получения информации о продажах
    async function fetchSalesInfo() {
        const urlParts = window.location.pathname.split('/');
        const appId = urlParts[3];
        const marketHashName = decodeURIComponent(urlParts[4]);
        const apiUrl = `https://steamcommunity.com/market/pricehistory/?appid=${appId}&market_hash_name=${marketHashName}`;

        try {
            const response = await fetch(apiUrl);
            const data = await response.json();

            if (data.success) {
                const salesData = data.prices;
                const yearlySales = {};
                let totalSales = 0;

                salesData.forEach(sale => {
                    const date = sale[0];
                    const price = parseFloat(sale[1]);
                    const quantity = parseInt(sale[2]);
                    const year = date.split(' ')[2];

                    const totalForDay = price * quantity;

                    if (!yearlySales[year]) {
                        yearlySales[year] = { total: 0, commission: 0, developerShare: 0, valveShare: 0 };
                    }

                    yearlySales[year].total += totalForDay;
                    totalSales += totalForDay;
                });

                // Рассчитываем комиссию и доли для каждого года
                for (const year in yearlySales) {
                    const commission = yearlySales[year].total * 0.13;
                    const developerShare = commission * 0.6667;
                    const valveShare = commission * 0.3333;

                    yearlySales[year].commission = commission;
                    yearlySales[year].developerShare = developerShare;
                    yearlySales[year].valveShare = valveShare;
                }

                displaySalesInfo(yearlySales, totalSales);
            } else {
                console.error('Не удалось получить информацию о продажах.');
            }
        } catch (error) {
            console.error('Ошибка при получении данных:', error);
        }
    }

    // Функция для отображения информации о продажах
    function displaySalesInfo(yearlySales, totalSales) {
        const salesInfoContainer = document.createElement('div');
        salesInfoContainer.style.marginTop = '20px';
        salesInfoContainer.style.padding = '10px';
        salesInfoContainer.style.border = '1px solid #4a4a4a';
        salesInfoContainer.style.backgroundColor = '#1b2838';
        salesInfoContainer.style.borderRadius = '4px';
        salesInfoContainer.style.boxShadow = '0 1px 3px rgba(0, 0, 0, 0.5)';
        salesInfoContainer.style.color = '#c7d5e0';

        const spoilerHeader = document.createElement('div');
        spoilerHeader.style.cursor = 'pointer';
        spoilerHeader.style.padding = '10px';
        spoilerHeader.style.backgroundColor = '#171a21';
        spoilerHeader.style.borderRadius = '4px 4px 0 0';
        spoilerHeader.style.color = '#c7d5e0';
        spoilerHeader.style.fontWeight = 'bold';
        spoilerHeader.style.fontFamily = '"Motiva Sans", sans-serif';
        spoilerHeader.style.fontSize = '16px';
        spoilerHeader.style.display = 'flex';
        spoilerHeader.style.alignItems = 'center';
        spoilerHeader.style.justifyContent = 'space-between';
        spoilerHeader.innerHTML = 'Информация о продажах <span style="font-size: 12px; transform: rotate(0deg); transition: transform 0.3s ease;">&#9660;</span>';

        spoilerHeader.addEventListener('click', () => {
            const content = spoilerHeader.nextElementSibling;
            content.style.display = content.style.display === 'none' ? 'block' : 'none';
            const arrow = spoilerHeader.querySelector('span');
            arrow.style.transform = content.style.display === 'none' ? 'rotate(0deg)' : 'rotate(180deg)';
        });

        const spoilerContent = document.createElement('div');
        spoilerContent.style.display = 'none';
        spoilerContent.style.padding = '10px';
        spoilerContent.style.borderTop = '1px solid #4a4a4a';

        const yearlySalesTable = document.createElement('table');
        yearlySalesTable.style.width = '100%';
        yearlySalesTable.style.borderCollapse = 'collapse';
        yearlySalesTable.style.marginBottom = '20px';
        yearlySalesTable.style.fontFamily = '"Motiva Sans", sans-serif';
        yearlySalesTable.style.fontSize = '14px';

        const yearlySalesHeader = document.createElement('tr');
        yearlySalesHeader.innerHTML = '<th style="padding: 8px; text-align: left; border-bottom: 2px solid #4a4a4a; background-color: #171a21; color: #c7d5e0;">Год</th><th style="padding: 8px; text-align: left; border-bottom: 2px solid #4a4a4a; background-color: #171a21; color: #c7d5e0;">Сумма продаж за год</th><th style="padding: 8px; text-align: left; border-bottom: 2px solid #4a4a4a; background-color: #171a21; color: #c7d5e0;">Ушло разработчику</th><th style="padding: 8px; text-align: left; border-bottom: 2px solid #4a4a4a; background-color: #171a21; color: #c7d5e0;">Ушло Valve</th>';
        yearlySalesTable.appendChild(yearlySalesHeader);

        for (const year in yearlySales) {
            const row = document.createElement('tr');
            row.innerHTML = `<td style="padding: 8px; border-bottom: 1px solid #4a4a4a; background-color: #1b2838; color: #c7d5e0;">${year}</td><td style="padding: 8px; border-bottom: 1px solid #4a4a4a; background-color: #1b2838; color: #c7d5e0;">${yearlySales[year].total.toLocaleString('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} руб.</td><td style="padding: 8px; border-bottom: 1px solid #4a4a4a; background-color: #1b2838; color: #c7d5e0;">${yearlySales[year].developerShare.toLocaleString('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} руб.</td><td style="padding: 8px; border-bottom: 1px solid #4a4a4a; background-color: #1b2838; color: #c7d5e0;">${yearlySales[year].valveShare.toLocaleString('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} руб.</td>`;
            yearlySalesTable.appendChild(row);
        }

        const totalSalesParagraph = document.createElement('p');
        totalSalesParagraph.textContent = `Сумма продаж за всё время: ${totalSales.toLocaleString('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} руб.`;
        totalSalesParagraph.style.fontWeight = 'bold';
        totalSalesParagraph.style.fontSize = '16px';
        totalSalesParagraph.style.color = '#c7d5e0';
        totalSalesParagraph.style.fontFamily = '"Motiva Sans", sans-serif';

        const commission = totalSales * 0.13;
        const developerShare = commission * 0.6667;
        const valveShare = commission * 0.3333;

        const developerShareParagraph = document.createElement('p');
        developerShareParagraph.textContent = `Ушло разработчику: ${developerShare.toLocaleString('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} руб.`;
        developerShareParagraph.style.fontSize = '14px';
        developerShareParagraph.style.color = '#c7d5e0';
        developerShareParagraph.style.fontFamily = '"Motiva Sans", sans-serif';

        const valveShareParagraph = document.createElement('p');
        valveShareParagraph.textContent = `Ушло Valve: ${valveShare.toLocaleString('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} руб.`;
        valveShareParagraph.style.fontSize = '14px';
        valveShareParagraph.style.color = '#c7d5e0';
        valveShareParagraph.style.fontFamily = '"Motiva Sans", sans-serif';

        spoilerContent.appendChild(yearlySalesTable);
        spoilerContent.appendChild(totalSalesParagraph);
        spoilerContent.appendChild(developerShareParagraph);
        spoilerContent.appendChild(valveShareParagraph);

        salesInfoContainer.appendChild(spoilerHeader);
        salesInfoContainer.appendChild(spoilerContent);

        const marketHeaderBg = document.querySelector('.market_header_bg');
        if (marketHeaderBg) {
            marketHeaderBg.parentNode.insertBefore(salesInfoContainer, marketHeaderBg.nextSibling);
        }
    }

    // Вызываем функцию fetchSalesInfo через несколько секунд после загрузки страницы
    setTimeout(fetchSalesInfo, 100);
})();

 


Со скриптом на страницах предметов появится спойлер “Информация о продажах”:

QW7qsp1.png

При раскрытии спойлера показываются следующие данные:ZPnzyNH.png

 

Изменено пользователем 0wn3df1x
  • Лайк (+1) 1

Поделиться сообщением


Ссылка на сообщение

Скрипт для сбора игр с The Adventure Games - Point and Click Database. || Python

nl9MjCy.png


Предназначение скрипта:

Скрипт собирает базовую информацию об играх с базы данных адвенчур:

  • Ссылку на игру в Adventure Database.
  • Название игры.
  • Год выхода игры.
  • Ссылку на страницу игры в Steam (если указана).
  • Точную дату выхода игры (если указана).
    UPD: 14.10.2024
  • Описание игры
  • Изображение игры
  • Автор игры
  • Издатель игры
Скрытый текст

aBLrAVI.png

Скрытый текст

Инструкция:

  1. Если у вас нет python и для вас это что-то далёкое, то вот готовая сборка программы.
    Скачиваете и запускаете, после чего можете перейти к пункту 7.
     
  2. Допустим, третий python у вас есть.
  3. В консоли перейдите в папку, где будет лежать программа.
  4. В папке создайте файл requirements.txt и напишите в нём
    
    requests
    beautifulsoup4

     

  5. Пропишите в консоли
    
    pip install -r requirements.txt

     

  6. Создайте файл start.py и поместите в него основной код скрипта
    Скрытый текст
    
    
    import requests
    from bs4 import BeautifulSoup
    import csv
    from datetime import datetime
    import threading
    import tkinter as tk
    from tkinter import messagebox
    from concurrent.futures import ThreadPoolExecutor
    import re
    import json
    import html
    
    # Глобальные переменные для отслеживания прогресса
    total_pages = 0
    processed_pages = 0
    total_games = 0
    processed_games = 0
    
    def fetch_page(url):
        response = requests.get(url)
        response.raise_for_status()
        return response.text
    
    def parse_games_from_page(html):
        soup = BeautifulSoup(html, 'html.parser')
        games = []
        for card in soup.find_all('div', class_='card'):
            year_tag = card.find('span', class_='new_item')
            if year_tag:
                year = int(year_tag.text.strip())
                game_link = card.find('a')['href']
                game_name = card.find('h3').text.strip()
                games.append((year, game_link, game_name))
        return games
    
    def clean_json_string(json_str):
        # Удаляем недопустимые управляющие символы
        json_str = re.sub(r'[\x00-\x1f\x7f-\x9f]', '', json_str)
        return json_str
    
    def parse_game_details(html_content):
        soup = BeautifulSoup(html_content, 'html.parser')
        
        # Извлечение данных из JSON-LD блоков
        json_ld_blocks = soup.find_all('script', type='application/ld+json')
        game_data = {}
        
        for block in json_ld_blocks:
            try:
                cleaned_json_str = clean_json_string(block.string)
                data = json.loads(cleaned_json_str)
                if data.get("@type") == "VideoGame":
                    if 'author' in data:
                        author = data['author']['name']
                        game_data['author'] = ', '.join(author) if isinstance(author, list) else author
                    if 'publisher' in data:
                        publisher = data['publisher']['name']
                        game_data['publisher'] = ', '.join(publisher) if isinstance(publisher, list) else publisher
                elif data.get("@type") == "Game":
                    game_data['description'] = data.get('description', '')
                    game_data['image'] = data.get('image', '')
            except json.JSONDecodeError as e:
                print(f"Error decoding JSON: {e}")
                continue
        
        # Преобразуем HTML-сущности в нормальный текст только в нужных полях
        game_data['description'] = html.unescape(game_data.get('description', ''))
        game_data['author'] = html.unescape(game_data.get('author', ''))
        game_data['publisher'] = html.unescape(game_data.get('publisher', ''))
        
        release_info = soup.find('div', class_='gray', style=lambda value: value and 'padding-left: 20px;' in value)
        
        release_date = None
        if release_info:
            release_text = release_info.find('span', class_='cat_label_item')
            if release_text:
                date_span = release_text.find('span')
                if date_span:
                    release_date_str = date_span.text.strip()
                    if re.match(r'Q\d \d{4}', release_date_str):
                        release_date = release_date_str
                    elif re.match(r'[A-Za-z]+ \d{4}', release_date_str):
                        release_date = release_date_str
                    else:
                        try:
                            release_date = datetime.strptime(release_date_str, '%B %d, %Y').strftime('%d.%m.%Y')
                        except ValueError:
                            pass
        
        # Ищем ссылку на Steam по наличию вложенного тега <span> с текстом "Steam Store"
        steam_link = soup.find('a', href=lambda value: value and 'store.steampowered.com' in value)
        if steam_link:
            steam_store_span = steam_link.find('span', string=re.compile(r'Steam Store', re.IGNORECASE))
            if steam_store_span:
                steam_url = steam_link['href']
            else:
                steam_url = None
        else:
            steam_url = None
    
        # Если ссылка не найдена, ищем внутри блока с классом `bodytext`
        if not steam_url:
            # Ищем все div с классом `store_box` на странице
            store_boxes = soup.find_all('div', class_='store_box')
            for store_box in store_boxes:
                # Ищем ссылку на Steam внутри каждого блока `store_box`
                steam_link = store_box.find('a', href=lambda value: value and 'store.steampowered.com' in value)
                
                if steam_link:
                    # Проверяем, не является ли ссылка на демо-версию (исключаем упоминания "DEMO")
                    if 'demo' not in steam_link.text.lower() and 'дема' not in steam_link.text.lower():
                        steam_url = steam_link['href']
                        break  # Прекращаем поиск, если нашли подходящую ссылку
    
        # Если ссылка все еще не найдена, возможно, она находится вне блока `store_box`, но всё равно не является демо
        if not steam_url:
            # Ищем все ссылки, содержащие "store.steampowered.com", но не относящиеся к демо-версии
            all_steam_links = soup.find_all('a', href=lambda value: value and 'store.steampowered.com' in value)
            for link in all_steam_links:
                # Исключаем ссылки на демо (например, по наличию слова "DEMO" или иконок, таких как "fas fa-floppy-disk")
                if 'demo' not in link.text.lower() and not link.find('i', class_='fas fa-floppy-disk'):
                    steam_url = link['href']
                    break  # Прекращаем поиск, если нашли нужную ссылку
        
        return {
            'release_date': release_date,
            'steam_url': steam_url,
            'description': game_data.get('description', ''),
            'image': game_data.get('image', ''),
            'author': game_data.get('author', ''),
            'publisher': game_data.get('publisher', '')
        }
    
    def collect_game_links(year, progress_callback):
        global total_pages, processed_pages
        
        base_url = 'https://adventuregamers.com/games/adventure/all-year-desc/page{}'
        page_number = 1
        game_links = []
        
        while True:
            url = base_url.format(page_number)
            html = fetch_page(url)
            games = parse_games_from_page(html)
            
            if not games:
                break
            
            total_pages += 1
            processed_pages += 1
            progress_callback(f"Обработано страниц: {processed_pages}/{total_pages}")
            
            for game_year, game_link, game_name in games:
                if game_year < year:
                    return game_links
                game_links.append((game_year, game_link, game_name))
            
            page_number += 1
        
        return game_links
    
    def process_game(game_link, game_name, game_year):
        game_url = f"https://adventuregamers.com{game_link}"
        game_html = fetch_page(game_url)
        game_details = parse_game_details(game_html)
        return (game_url, game_name, game_year, game_details['release_date'], game_details['steam_url'], game_details['description'], game_details['image'], game_details['author'], game_details['publisher'])
    
    def collect_game_data(game_links, max_workers, progress_callback):
        global total_games, processed_games
        
        games_data = []
        total_games = len(game_links)
        
        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            futures = []
            for game_year, game_link, game_name in game_links:
                futures.append(executor.submit(process_game, game_link, game_name, game_year))
            
            for future in futures:
                games_data.append(future.result())
                processed_games += 1
                progress_callback(f"Обработано игр: {processed_games}/{total_games}")
        
        return games_data
    
    def save_to_csv(data, filename):
        with open(filename, 'w', newline='', encoding='utf-8') as csvfile:
            writer = csv.writer(csvfile, delimiter='\t')
            writer.writerow(['Ссылка на игру', 'Название игры', 'Год', 'Дата выхода', 'Ссылка на Steam', 'Описание', 'Изображение', 'Автор', 'Издатель'])
            writer.writerows(data)
    
    def start_collection(year_entry, max_workers_entry, progress_label_pages, progress_label_games):
        year = int(year_entry.get())
        max_workers = int(max_workers_entry.get())
        progress_label_pages.config(text="Начало сбора данных...")
        
        def update_progress_pages(message):
            progress_label_pages.config(text=message)
        
        def update_progress_games(message):
            progress_label_games.config(text=message)
        
        def collect_and_save():
            game_links = collect_game_links(year, update_progress_pages)
            games_data = collect_game_data(game_links, max_workers, update_progress_games)
            save_to_csv(games_data, 'games_data.csv')
            messagebox.showinfo("Сбор данных завершен", "Сбор данных успешно завершен!")
        
        data_thread = threading.Thread(target=collect_and_save)
        data_thread.start()
    
    def main():
        root = tk.Tk()
        root.title("Adventure DB Сборщик")
        root.geometry("350x350")
    
        tk.Label(root, text="Введите стартовый год:").pack(pady=5)
        year_entry = tk.Entry(root)
        year_entry.pack(pady=5)
        
        max_workers_label = tk.Label(root, text="Количество max workers:")
        max_workers_label.pack(pady=5)
        
        max_workers_entry = tk.Entry(root)
        max_workers_entry.insert(0, "4")
        max_workers_entry.pack(pady=5)
    
        max_workers_label2 = tk.Label(root, text="(увеличение количества max workers ускорит сбор,\nно сайт может заблокировать вас за большое количество\nзапросов за короткий промежуток времени, рекомендую\nдержать количество max workers на уровне 4 и ниже)", fg="red")
        max_workers_label2.pack(pady=2)
            
        
        progress_label_pages = tk.Label(root, text="")
        progress_label_pages.pack(pady=10)
        
        progress_label_games = tk.Label(root, text="")
        progress_label_games.pack(pady=10)
        
        start_button = tk.Button(root, text="Начать сбор", command=lambda: start_collection(year_entry, max_workers_entry, progress_label_pages, progress_label_games))
        start_button.pack(pady=10)
        
        root.mainloop()
    
    if __name__ == "__main__":
        main()

     

     

  7. Запустите программу с помощью
    
    python start.py
  8. В открывшемся окне:
    - Введите стартовый год (будут собраны только игры, которые идут с начала этого года и позже)
    - Max workers можно не менять, см. предупреждение.
     
  9. После этого можете нажать на кнопку “Начать сбор” и через несколько секунд начнётся сбор информации.
  10. Во время сбора вам будет виден прогресс выполнения. В конце появится табличка, что сбор завершён. После этого можно закрывать программу.
  11. Файл в виде csv таблицы хранится в папке с программой. Можете импортировать её на гугл-таблицы или в любое табличное приложение (разделитель — табуляция).

 

Изменено пользователем 0wn3df1x
  • Лайк (+1) 1
  • +1 1

Поделиться сообщением


Ссылка на сообщение

Обновление: Улучшение скрипта для сбора игр с каталога Steam #2 || Python

Теперь:

  • Скрипт собирает данные в 4 раза быстрее и объём страниц, с которых он собирает данные, уменьшился в 4 раза, что временно* позволяет избежать необходимости постоянной смены прокси в случае со сбором игр гигантского каталога вроде каталога игр с английским языком в американском регионе (тогда страниц было больше 3000, теперь чуть больше 800);  *Временно, поскольку каталог растёт и всё равно рано дойдёт до критического числа страниц, а значит в будущем, к сожалению, всё равно придётся заниматься периодическим переключением прокси.
  • Можно поставить флажок на “игры с карточками”, чтобы остались только игры, в которых есть карточки.
  • Можно сперва скачать файлы для американского региона (us), затем для русского региона (us). После чего американский файл загрузить в качестве FullBase_US, а русский в качестве FullBase_Ru. Затем нажать кнопку “Собрать базу” и получить файл FullBase_USRU.csv, где к играм из американского региона добавятся игры, которые есть только в российском регионе. Также добавится столбец с рублёвыми ценами. Если рублёвых цен нет — будет написано [NDR] (значит игра недоступна в российском регионе).

 

jeGlvAe.png



Предназначение скрипта:
Система фильтрации каталога Steam оставляет желать лучшего. Запросы к базе данных ограничены узкой подборкой:

Скрытый текст
  • Вы можете выбрать стоимость игр только до какого-то предела. Допустим, до 300 рублей, до 150 рублей и так далее. Нельзя установить точные рамки: “от 1000 до 1500”, от “1200 до 1300” и так далее.
  • В Steam есть количество обзоров и рейтинг этих обзоров, но в каталоге нет запроса, который позволил бы оставить только игры с заданных рейтингом и количеством обзоров. Эту проблему можно исправить плагином Augmented Steam, но он не может отправить запрос для получения ограниченной выборки. Вместо этого он просто скрывает игры, которые не попадают под заданные критерии. Таким образом, если вы поставите этот плагин, зададите в нём критерии и начнёте листать магазин — ваш браузер начнёт перегружаться скрытым кодом. Из-за большого количества запросов вы и вовсе можете быть заблокированы на несколько минут — сначала у вас отвалятся картинки и видео, а затем появится надпись о том, что вы не можете получить доступ к сайту.
  • Для того, чтобы увидеть обзоры игры и жанры, к которым она относится, вам нужно навести на неё курсор и дождаться, пока появится виджет со всей информацией. 

Мой скрипт позволяет задать валюту (она же регион), с которого будут собираться данные, язык, на котором доступны игры, наличие скидок, платность/бесплатность и тэги. После чего он соберёт данные обо всех вышедших играх в определённом регионе и доступных на заданном языке и сохранит их в csv файл.

Скрытый текст
  • app_id — айди игры
  • image_url — картинка игры из магазина
  • title — название игры
  • release_date — дата выхода игры в формате день.месяц.год (есть игры с забагованными датами, которые идут в формате “April 2024” и т.п., они запихиваются в конец таблицы.
  • review_percentage — рейтинг игры.
  • review_count — количество обзоров.
  • tag_ids — основные метки, присвоенные игре (они же жанры).
  • price — текущая цена.
  • orprice — оригинальная цена (пишется только в случае наличия сейчас скидки).
  • (RuPrice — цена в рублях; актуально для FullBase_USRU.csv)
Скрытый текст

9rQIeZv.png

Скрытый текст

Эту таблицу можно загрузить на Google Spreadsheets и там навешать на неё фильтров, после чего фильтровать игры с нужными жанрами, рейтингами, обзорами и ценами. Также можно использовать формулу “image” для выведения картинок. 
Можно добавить столбец, где “https://store.steampowered.com/app/” будет соединяться с идентификатором игры, образуя ссылку.

Скрытый текст

CiHGilP.png

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

Скрытый текст

Инструкция:

  1. Если у вас нет python и для вас это что-то далёкое, то вот готовая сборка программы.
    Скачиваете и запускаете, после чего можете перейти к пункту 9.
     
  2. Допустим, третий python у вас есть.
  3. В консоли перейдите в папку, где будет лежать программа.
  4. В папке создайте файл requirements.txt и напишите в нём
    
    requests
    beautifulsoup4
    aiohttp
  5. Пропишите в консоли
    
    pip install -r requirements.txt

     

  6. Создайте файл config.txt и поместите в него следующие данные:
    
    cc=us
    supportedlang=english
    filename=3010ENUS.csv
    cards=False
    specials=False
    hidef2p=False
    tags=

     

  7. Создайте файл tags.txt и поместите в него следующие данные:
    Скрытый текст
    
    
    9	Strategy
    19	Action
    21	Adventure
    84	Design & Illustration
    87	Utilities
    113	Free to Play
    122	RPG
    128	Massively Multiplayer
    492	Indie
    493	Early Access
    597	Casual
    599	Simulation
    699	Racing
    701	Sports
    784	Video Production
    809	Photo Editing
    872	Animation & Modeling
    1027	Audio Production
    1036	Education
    1038	Web Publishing
    1445	Software Training
    1616	Trains
    1621	Music
    1625	Platformer
    1628	Metroidvania
    1638	Dog
    1643	Building
    1644	Driving
    1645	Tower Defense
    1646	Hack and Slash
    1647	Western
    1649	GameMaker
    1651	Satire
    1654	Relaxing
    1659	Zombies
    1662	Survival
    1663	FPS
    1664	Puzzle
    1665	Match 3
    1666	Card Game
    1667	Horror
    1669	Moddable
    1670	4X
    1671	Superhero
    1673	Aliens
    1674	Typing
    1676	RTS
    1677	Turn-Based
    1678	War
    1680	Heist
    1681	Pirates
    1684	Fantasy
    1685	Co-op
    1687	Stealth
    1688	Ninja
    1695	Open World
    1697	Third Person
    1698	Point & Click
    1702	Crafting
    1708	Tactical
    1710	Surreal
    1714	Psychedelic
    1716	Rogue-like
    1717	Hex Grid
    1718	MOBA
    1719	Comedy
    1720	Dungeon Crawler
    1721	Psychological Horror
    1723	Action RTS
    1730	Sokoban
    1732	Voxel
    1733	Unforgiving
    1734	Fast-Paced
    1736	LEGO
    1738	Hidden Object
    1741	Turn-Based Strategy
    1742	Story Rich
    1743	Fighting
    1746	Basketball
    1751	Comic Book
    1752	Rhythm
    1753	Skateboarding
    1754	MMORPG
    1755	Space
    1756	Great Soundtrack
    1759	Perma Death
    1770	Board Game
    1773	Arcade
    1774	Shooter
    1775	PvP
    1777	Steampunk
    3796	Based On A Novel
    3798	Side Scroller
    3799	Visual Novel
    3810	Sandbox
    3813	Real Time Tactics
    3814	Third-Person Shooter
    3834	Exploration
    3835	Post-apocalyptic
    3839	First-Person
    3841	Local Co-Op
    3843	Online Co-Op
    3854	Lore-Rich
    3859	Multiplayer
    3871	2D
    3877	Precision Platformer
    3878	Competitive
    3916	Old School
    3920	Cooking
    3934	Immersive
    3942	Sci-fi
    3952	Gothic
    3955	Character Action Game
    3959	Rogue-lite
    3964	Pixel Graphics
    3968	Physics
    3978	Survival Horror
    3987	Historical
    3993	Combat
    4004	Retro
    4018	Vampire
    4026	Difficult
    4036	Parkour
    4046	Dragons
    4057	Magic
    4064	Thriller
    4085	Anime
    4094	Minimalist
    4102	Combat Racing
    4106	Action-Adventure
    4115	Cyberpunk
    4136	Funny
    4137	Transhumanism
    4145	Cinematic
    4150	World War II
    4155	Class-Based
    4158	Beat 'em up
    4161	Real-Time
    4166	Atmospheric
    4168	Military
    4172	Medieval
    4175	Realistic
    4182	Singleplayer
    4184	Chess
    4191	3D
    4195	Cartoony
    4202	Trading
    4231	Action RPG
    4234	Short
    4236	Loot
    4242	Episodic
    4252	Stylized
    4255	Shoot 'Em Up
    4291	Spaceships
    4295	Futuristic
    4305	Colorful
    4325	Turn-Based Combat
    4328	City Builder
    4342	Dark
    4345	Gore
    4364	Grand Strategy
    4376	Assassin
    4400	Abstract
    4434	JRPG
    4474	CRPG
    4486	Choose Your Own Adventure
    4508	Co-op Campaign
    4520	Farming
    4559	Quick-Time Events
    4562	Cartoon
    4598	Alternate History
    4604	Dark Fantasy
    4608	Swordplay
    4637	Top-Down Shooter
    4667	Violent
    4684	Wargame
    4695	Economy
    4700	Movie
    4711	Replay Value
    4726	Cute
    4736	2D Fighter
    4747	Character Customization
    4754	Politics
    4758	Twin Stick Shooter
    4777	Spectacle fighter
    4791	Top-Down
    4821	Mechs
    4835	6DOF
    4840	4 Player Local
    4845	Capitalism
    4853	Political
    4878	Parody
    4885	Bullet Hell
    4947	Romance
    4975	2.5D
    4994	Naval Combat
    5030	Dystopian
    5055	eSports
    5094	Narration
    5125	Procedural Generation
    5153	Kickstarter
    5154	Score Attack
    5160	Dinosaurs
    5179	Cold War
    5186	Psychological
    5228	Blood
    5230	Sequel
    5300	God Game
    5310	Games Workshop
    5348	Mod
    5350	Family Friendly
    5363	Destruction
    5372	Conspiracy
    5379	2D Platformer
    5382	World War I
    5390	Time Attack
    5395	3D Platformer
    5407	Benchmark
    5411	Beautiful
    5432	Programming
    5502	Hacking
    5537	Puzzle-Platformer
    5547	Arena Shooter
    5577	RPGMaker
    5608	Emotional
    5611	Mature
    5613	Detective
    5652	Collectathon
    5673	Modern
    5708	Remake
    5711	Team-Based
    5716	Mystery
    5727	Baseball
    5752	Robots
    5765	Gun Customization
    5794	Science
    5796	Bullet Time
    5851	Isometric
    5900	Walking Simulator
    5914	Tennis
    5923	Dark Humor
    5941	Reboot
    5981	Mining
    5984	Drama
    6041	Horses
    6052	Noir
    6129	Logic
    6214	Birds
    6276	Inventory Management
    6310	Diplomacy
    6378	Crime
    6426	Choices Matter
    6506	3D Fighter
    6621	Pinball
    6625	Time Manipulation
    6650	Nudity
    6691	1990's
    6702	Mars
    6730	PvE
    6815	Hand-drawn
    6869	Nonlinear
    6910	Naval
    6915	Martial Arts
    6948	Rome
    6971	Multiple Endings
    7038	Golf
    7107	Real-Time with Pause
    7108	Party
    7113	Crowdfunded
    7178	Party Game
    7208	Female Protagonist
    7250	Linear
    7309	Skiing
    7328	Bowling
    7332	Base Building
    7368	Local Multiplayer
    7423	Sniper
    7432	Lovecraftian
    7478	Illuminati
    7481	Controller
    7569	Grid-Based Movement
    7622	Offroad
    7702	Narrative
    7743	1980s
    7918	Dwarf
    7926	Artificial Intelligence
    7948	Soundtrack
    8013	Software
    8075	TrackIR
    8093	Minigames
    8122	Level Editor
    8253	Music-Based Procedural Generation
    8369	Investigation
    8461	Well-Written
    8666	Runner
    8945	Resource Management
    9130	Hentai
    9157	Underwater
    9204	Immersive Sim
    9271	Trading Card Game
    9541	Demons
    9551	Dating Sim
    9564	Hunting
    9592	Dynamic Narration
    9803	Snow
    9994	Experience
    10235	Life Sim
    10383	Transportation
    10397	Memes
    10437	Trivia
    10679	Time Travel
    10695	Party-Based RPG
    10808	Supernatural
    10816	Split Screen
    11014	Interactive Fiction
    11095	Boss Rush
    11104	Vehicular Combat
    11123	Mouse only
    11333	Villain Protagonist
    11634	Vikings
    12057	Tutorial
    12095	Sexual Content
    12190	Boxing
    12286	Warhammer 40K
    12472	Management
    13070	Solitaire
    13190	America
    13276	Tanks
    13382	Archery
    13577	Sailing
    13782	Experimental
    13906	Game Development
    14139	Turn-Based Tactics
    14153	Dungeons & Dragons
    14720	Nostalgia
    14906	Intentionally Awkward Controls
    15045	Flight
    15172	Conversation
    15277	Philosophical
    15339	Documentary
    15564	Fishing
    15868	Motocross
    15954	Silent Protagonist
    16094	Mythology
    16250	Gambling
    16598	Space Sim
    16689	Time Management
    17015	Werewolves
    17305	Strategy RPG
    17337	Lemmings
    17389	Tabletop
    17770	Asynchronous Multiplayer
    17894	Cats
    17927	Pool
    18594	FMV
    19568	Cycling
    19780	Submarine
    19995	Dark Comedy
    21006	Underground
    21725	Tactical RPG
    21978	VR
    22602	Agriculture
    22955	Mini Golf
    24003	Word Game
    24904	NSFW
    25085	Touch-Friendly
    26921	Political Sim
    27758	Voice Control
    28444	Snowboarding
    29363	3D Vision
    29482	Souls-like
    29855	Ambient
    30358	Nature
    30927	Fox
    31275	Text-Based
    31579	Otome
    32322	Deckbuilding
    33572	Mahjong
    35079	Job Simulator
    42089	Jump Scare
    42329	Coding
    42804	Action Roguelike
    44868	LGBTQ+
    47827	Wrestling
    49213	Rugby
    51306	Foreign
    56690	On-Rails Shooter
    61357	Electronic Music
    65443	Adult Content
    71389	Spelling
    87918	Farming Sim
    91114	Shop Keeper
    92092	Jet
    96359	Skating
    97376	Cozy
    102530	Elf
    117648	8-bit Music
    123332	Bikes
    129761	ATV
    143739	Electronic
    150626	Gaming
    158638	Cricket
    176733	Tile-Matching
    176981	Battle Royale
    180368	Faith
    189941	Instrumental Music
    198631	Mystery Dungeon
    198913	Motorbike
    220585	Colony Sim
    233824	Feature Film
    252854	BMX
    255534	Automation
    323922	Musou
    324176	Hockey
    337964	Rock Music
    348922	Steam Machine
    353880	Looter Shooter
    363767	Snooker
    379975	Clicker
    454187	Traditional Roguelike
    552282	Wholesome
    603297	Hardware
    615955	Idler
    620519	Hero Shooter
    745697	Social Deduction
    769306	Escape Room
    776177	360 Video
    791774	Card Battler
    847164	Volleyball
    856791	Asymmetric VR
    916648	Creature Collector
    922563	Roguevania
    1003823	Profile Features Limited
    1023537	Boomer Shooter
    1084988	Auto Battler
    1091588	Roguelike Deckbuilder
    1100686	Outbreak Sim
    1100687	Automobile Sim
    1100688	Medical Sim
    1100689	Open World Survival Craft
    1199779	Extraction Shooter
    1220528	Hobby Sim
    1254546	Football (Soccer)
    1254552	Football (American)

     

  8. Создайте файл start.py и поместите в него основной код скрипта
    Скрытый текст
    
    
    import tkinter as tk
    from tkinter import filedialog, messagebox
    import requests
    from bs4 import BeautifulSoup
    import csv
    import asyncio
    import aiohttp
    import re
    import os
    import threading
    from datetime import datetime
    from calendar import monthrange
    import time
    
    # Функция для загрузки данных о тегах из файла
    def load_tags():
        tags = {}
        with open('tags.txt', 'r', encoding='utf-8') as file:
            for line in file:
                tag_id, tag_text = line.strip().split('\t')
                tags[tag_id] = tag_text
        return tags
    
    # Функция для извлечения данных из элемента
    def extract_data(element, tags):
        app_id = element.get('data-ds-appid', '')
        title = element.find(class_='title').text.strip() if element.find(class_='title') else ''
        tag_ids = element.get('data-ds-tagids', '')
        tag_ids = tag_ids.strip('[]')  # Удаляем квадратные скобки из строки
        tag_ids = [tags.get(tag_id, '') for tag_id in tag_ids.split(',')]
        tooltip_html = element.find(class_='search_review_summary')['data-tooltip-html'] if element.find(class_='search_review_summary') else ''
        review_percentage = re.search(r'(\d+)%', tooltip_html).group(1) if tooltip_html else ''
        review_count = re.search(r'of the (\d{1,3}(?:,\d{3})*)', tooltip_html).group(1) if tooltip_html else ''
        review_count = review_count.replace(',', '')  # Убираем запятые из числа обзоров
        image_url = element.find('img')['src'] if element.find('img') else ''
        release_date = element.find(class_='search_released').text.strip() if element.find(class_='search_released') else ''
        release_date = parse_date(release_date)  # Преобразуем дату в единый формат
        orprice = element.find(class_='discount_original_price').text.strip() if element.find(class_='discount_original_price') else 'No price'
        price = element.find(class_='discount_final_price').text.strip() if element.find(class_='discount_final_price') else 'No price'
    
        return [app_id, image_url, title, release_date, review_percentage, review_count, tag_ids, price, orprice]
    
    # Функция для преобразования даты в единый формат
    def parse_date(date_str):
        if not date_str or date_str == 'Coming Soon':
            return date_str
        try:
            date_obj = datetime.strptime(date_str, '%b %d, %Y')
        except ValueError:
            try:
                date_obj = datetime.strptime(date_str, '%d %b, %Y')
            except ValueError:
                try:
                    date_obj = datetime.strptime(date_str, '%B %Y')
                    last_day = monthrange(date_obj.year, date_obj.month)[1]
                    date_obj = date_obj.replace(day=last_day)
                except ValueError:
                    try:
                        date_obj = datetime.strptime(date_str, '%b %Y')
                        last_day = monthrange(date_obj.year, date_obj.month)[1]
                        date_obj = date_obj.replace(day=last_day)
                    except ValueError:
                        return date_str
        return date_obj.strftime('%d.%m.%Y')
    
    # Функция для получения количества страниц
    def get_total_pages(url):
        response = requests.get(url)
        data = response.json()
        total_count = data['total_count']
        return (total_count // 100) + 1
    
    # Асинхронная функция для сбора данных
    async def fetch_data(session, url, tags, writer, progress_var, total_pages, start_time, speed_var, time_left_var):
        try:
            async with session.get(url) as response:
                response.raise_for_status()
                data = await response.json()
                results_html = data['results_html']
                soup = BeautifulSoup(results_html, 'html.parser')
                elements = soup.find_all(class_='search_result_row')
                for element in elements:
                    data = extract_data(element, tags)
                    writer.writerow(data)
                progress_var.set(progress_var.get() + 1)
                update_progress_label(progress_var, total_pages, start_time, speed_var, time_left_var)
        except (aiohttp.ClientError, asyncio.TimeoutError) as e:
            print(f"Error fetching {url}: {e}")
            await asyncio.sleep(30)
            await fetch_data(session, url, tags, writer, progress_var, total_pages, start_time, speed_var, time_left_var)
    
    # Асинхронная функция для запуска сбора данных
    async def start_collecting(cc, supportedlang, filename, progress_var, speed_var, time_left_var, options):
        tags = load_tags()
        base_url = f'https://store.steampowered.com/search/results/?query&count=100&dynamic_data=&sort_by=Released_DESC&ignore_preferences=1&category1=998&cc={cc}&supportedlang={supportedlang}&ndl=1&infinite=1'
    
        # Добавляем дополнительные параметры в URL
        if options['cards']:
            base_url += '&category2=29'
        if options['specials']:
            base_url += '&specials=1'
        if options['hidef2p']:
            base_url += '&hidef2p=1'
        if options['tags']:
            base_url += f'&tags={options["tags"]}'
    
        total_pages = get_total_pages(base_url)
        progress_var.set(0)
        temp_filename = filename + '.tmp'
        with open(temp_filename, 'w', newline='', encoding='utf-8') as csvfile:
            writer = csv.writer(csvfile, delimiter='\t')
            writer.writerow(['app_id', 'image_url', 'title', 'release_date', 'review_percentage', 'review_count', 'tag_ids', 'price', 'orprice'])
            async with aiohttp.ClientSession() as session:
                start_time = time.time()
                tasks = [fetch_data(session, base_url + f'&start={start * 100}', tags, writer, progress_var, total_pages, start_time, speed_var, time_left_var) for start in range(total_pages)]
                await asyncio.gather(*tasks)
        
        # Сортировка данных по дате
        sort_data_by_date(temp_filename, filename)
        os.remove(temp_filename)
        messagebox.showinfo("Сбор завершён", "Сбор данных завершен.")
    
    # Функция для сортировки данных по дате
    def sort_data_by_date(temp_filename, filename):
        data = []
        with open(temp_filename, 'r', encoding='utf-8') as csvfile:
            reader = csv.reader(csvfile, delimiter='\t')
            header = next(reader)
            for row in reader:
                data.append(row)
        
        # Сортировка данных по дате, помещая ошибочные даты в конец
        data.sort(key=lambda x: (is_valid_date(x[3]), datetime.strptime(x[3], '%d.%m.%Y') if is_valid_date(x[3]) else datetime.min), reverse=True)
        
        with open(filename, 'w', newline='', encoding='utf-8') as csvfile:
            writer = csv.writer(csvfile, delimiter='\t')
            writer.writerow(header)
            writer.writerows(data)
    
    # Функция для проверки, является ли дата валидной
    def is_valid_date(date_str):
        try:
            datetime.strptime(date_str, '%d.%m.%Y')
            return True
        except ValueError:
            return False
    
    # Функция для сохранения конфигурации
    def save_config(cc, supportedlang, filename, options):
        with open('config.txt', 'w', encoding='utf-8') as file:
            file.write(f'cc={cc}\nsupportedlang={supportedlang}\nfilename={filename}\n')
            file.write(f'cards={options["cards"]}\nspecials={options["specials"]}\nhidef2p={options["hidef2p"]}\ntags={options["tags"]}')
    
    # Функция для загрузки конфигурации
    def load_config():
        config = {'cc': 'us', 'supportedlang': 'russian', 'filename': 'result', 'cards': False, 'specials': False, 'hidef2p': False, 'tags': ''}
        if os.path.exists('config.txt'):
            with open('config.txt', 'r', encoding='utf-8') as file:
                for line in file:
                    key, value = line.strip().split('=')
                    if value.lower() == 'true':
                        value = True
                    elif value.lower() == 'false':
                        value = False
                    config[key] = value
        return config
    
    # Функция для запуска сбора данных в отдельном потоке
    def start_collecting_thread(cc, supportedlang, filename, progress_var, speed_var, time_left_var, options):
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        loop.run_until_complete(start_collecting(cc, supportedlang, filename, progress_var, speed_var, time_left_var, options))
        loop.close()
    
    # Функция для обновления метки прогресса
    def update_progress_label(progress_var, total_pages, start_time, speed_var, time_left_var):
        current_time = time.time()
        elapsed_time = current_time - start_time
        speed = progress_var.get() / elapsed_time if elapsed_time > 0 else 0
        speed_var.set(f"{speed:.2f} стр/сек")
        remaining_pages = total_pages - progress_var.get()
        remaining_time = remaining_pages / speed if speed > 0 else 0
        minutes, seconds = divmod(remaining_time, 60)
        time_left_var.set(f"Осталось {int(minutes)} м. {int(seconds)} сек.")
        progress_label.config(text=f"{progress_var.get()}/{total_pages}")
    
    # Функция для запуска сбора данных
    def on_start_collecting():
        cc = cc_entry.get()
        supportedlang = supportedlang_entry.get()
        filename = filename_entry.get()
        options = {
            'cards': cards_var.get(),
            'specials': specials_var.get(),
            'hidef2p': hidef2p_var.get(),
            'tags': tags_entry.get()
        }
        save_config(cc, supportedlang, filename, options)
        threading.Thread(target=start_collecting_thread, args=(cc, supportedlang, filename, progress_var, speed_var, time_left_var, options)).start()
    
    # Функция для загрузки FullBase_US
    def load_fullbase_us():
        fullbase_us_path = filedialog.askopenfilename(filetypes=[("CSV files", "*.csv")])
        if fullbase_us_path:
            fullbase_us_entry.delete(0, tk.END)
            fullbase_us_entry.insert(0, fullbase_us_path)
    
    # Функция для загрузки FullBase_RU
    def load_fullbase_ru():
        fullbase_ru_path = filedialog.askopenfilename(filetypes=[("CSV files", "*.csv")])
        if fullbase_ru_path:
            fullbase_ru_entry.delete(0, tk.END)
            fullbase_ru_entry.insert(0, fullbase_ru_path)
    
    # Функция для объединения файлов
    def combine_bases():
        fullbase_us_path = fullbase_us_entry.get()
        fullbase_ru_path = fullbase_ru_entry.get()
    
        if not fullbase_us_path or not fullbase_ru_path:
            messagebox.showwarning("Предупреждение", "Выберите оба файла перед объединением.")
            return
    
        combined_data = []
    
        # Чтение FullBase_US
        with open(fullbase_us_path, 'r', encoding='utf-8') as us_file:
            us_reader = csv.reader(us_file, delimiter='\t')
            us_header = next(us_reader)
            combined_data.extend(list(us_reader))
    
        # Чтение FullBase_RU
        with open(fullbase_ru_path, 'r', encoding='utf-8') as ru_file:
            ru_reader = csv.reader(ru_file, delimiter='\t')
            ru_header = next(ru_reader)
            ru_data = list(ru_reader)
    
        us_app_ids = {row[0] for row in combined_data}
    
        # Добавление данных из FullBase_RU, которых нет в FullBase_US
        for row in ru_data:
            if row[0] not in us_app_ids:
                combined_data.append(row)
    
        # Добавление столбца RuPrice
        ru_prices = {row[0]: row[7] for row in ru_data}
        combined_data_with_ruprice = []
        for row in combined_data:
            ru_price = ru_prices.get(row[0], "[NDR]")
            combined_data_with_ruprice.append(row + [ru_price])
    
        # Сохранение результата в новый CSV файл
        output_file = 'FullBase_USRU.csv'
        with open(output_file, 'w', newline='', encoding='utf-8') as outfile:
            writer = csv.writer(outfile, delimiter='\t')
            writer.writerow(us_header + ['RuPrice'])
            writer.writerows(combined_data_with_ruprice)
    
        messagebox.showinfo("Успех", f"Файл {output_file} успешно создан.")
    
    # Создание главного окна
    root = tk.Tk()
    root.title("Steam Data Collector")
    
    # Загрузка конфигурации
    config = load_config()
    
    # Создание элементов интерфейса
    tk.Label(root, text="Валюта").grid(row=0, column=0)
    cc_entry = tk.Entry(root)
    cc_entry.insert(0, config['cc'])
    cc_entry.grid(row=0, column=1)
    
    tk.Label(root, text="Язык").grid(row=1, column=0)
    supportedlang_entry = tk.Entry(root)
    supportedlang_entry.insert(0, config['supportedlang'])
    supportedlang_entry.grid(row=1, column=1)
    
    tk.Label(root, text="Название файла").grid(row=2, column=0)
    filename_entry = tk.Entry(root)
    filename_entry.insert(0, config['filename'])
    filename_entry.grid(row=2, column=1)
    
    # Опции
    cards_var = tk.BooleanVar(value=config['cards'])
    cards_check = tk.Checkbutton(root, text="Игры с карточками", variable=cards_var)
    cards_check.grid(row=3, column=0, columnspan=2)
    
    specials_var = tk.BooleanVar(value=config['specials'])
    specials_check = tk.Checkbutton(root, text="Скидки", variable=specials_var)
    specials_check.grid(row=4, column=0, columnspan=2)
    
    hidef2p_var = tk.BooleanVar(value=config['hidef2p'])
    hidef2p_check = tk.Checkbutton(root, text="Скрыть бесплатные", variable=hidef2p_var)
    hidef2p_check.grid(row=5, column=0, columnspan=2)
    
    tk.Label(root, text="Тэги").grid(row=6, column=0)
    tags_entry = tk.Entry(root)
    tags_entry.insert(0, config['tags'])
    tags_entry.grid(row=6, column=1)
    
    start_button = tk.Button(root, text="Начать сбор", command=on_start_collecting)
    start_button.grid(row=7, column=0, columnspan=2)
    
    progress_var = tk.IntVar()
    progress_label = tk.Label(root, text="0/0")
    progress_label.grid(row=8, column=0, columnspan=2)
    
    speed_var = tk.StringVar()
    speed_label = tk.Label(root, textvariable=speed_var)
    speed_label.grid(row=9, column=0, columnspan=2)
    
    time_left_var = tk.StringVar()
    time_left_label = tk.Label(root, textvariable=time_left_var)
    time_left_label.grid(row=10, column=0, columnspan=2)
    
    # Создание элементов интерфейса для новых функций
    tk.Label(root, text="FullBase_US").grid(row=11, column=0)
    fullbase_us_entry = tk.Entry(root)
    fullbase_us_entry.grid(row=11, column=1)
    load_us_button = tk.Button(root, text="Загрузить FullBase_US", command=load_fullbase_us)
    load_us_button.grid(row=11, column=2)
    
    tk.Label(root, text="FullBase_RU").grid(row=12, column=0)
    fullbase_ru_entry = tk.Entry(root)
    fullbase_ru_entry.grid(row=12, column=1)
    load_ru_button = tk.Button(root, text="Загрузить FullBase_RU", command=load_fullbase_ru)
    load_ru_button.grid(row=12, column=2)
    
    combine_button = tk.Button(root, text="Собрать базу", command=combine_bases)
    combine_button.grid(row=13, column=0, columnspan=3)
    
    # Запуск главного цикла
    root.mainloop()

     

     

  9. Запустите программу с помощью
    
    python start.py
  10. В открывшемся окне:
    - Измените валюту/регион на нужную вам. (us — США; ru — Россия; kz — Казахстан) 
    - Измените язык на нужный вам (english — будут показаны игры, где есть английский; russian — будут показаны игры, где есть русский)
    - Можете изменить название файла, если хотите.
    - Можете поставить флажок на “игры с карточками”, чтобы показывало только игры с карточками.
    - Можете поставить флажок на скидках, чтобы выдало только игры со скидками.
    - Можете поставить флажок на “Скрыть бесплатные”, чтобы их не искало.
    - Можете через запятую перечислить числовые значения тэгов (их можно узнать в файле tags.txt)
    jeGlvAe.png
  11. После этого можете нажать на кнопку “Начать сбор” и через несколько секунд начнётся сбор информации.
  12. Во время сбора вам будет виден прогресс выполнения, скорость загрузки страниц и оставшееся время. В конце появится табличка, что сбор завершён. После этого можно закрывать программу.
  13. Файл в виде csv таблицы хранится в папке с программой. Можете импортировать её на гугл-таблицы или в любое табличное приложение (разделитель — табуляция).
     
  14. Касательно функций ниже. Вы можете прописать в валюте us и собрать все игры с russian в файле ruus. Затем прописать в валюте ru и собрать все игры с russian в файле ruru. Затем можете загрузить ruus в качестве FullBase_US, а ruru в качестве FullBase_RU и нажать кнопку “Собрать базу”. Данные будут собраны в FullBase_USRU.csv, где к играм из американского региона добавятся игры, которые есть только в российском регионе. Также добавится столбец с рублёвыми ценами. Если рублёвых цен нет — будет написано [NDR] (значит игра недоступна в российском регионе).

(!) Желательно собирать информацию под прокси или vpn, чтобы ваш родной айпишник не заблокировало на несколько минут за частое обращение к серверу. 

(!) Если вы собираете слишком большой объём данных, к примеру, собираете все игры из американского региона на английском языке (us+english), и сбор информации начнёт переваливать за тысячу страниц, то после 1000-й страницы консоль может начать сыпать ошибками, а сам скрипт перестанет собирать информацию на 30 секунд. Через 30 секунд он возобновит сбор информации. Вы можете воспользоваться этим моментом, чтобы сменить прокси и избежать временной блокировки. Скрипт соберёт пропущенную информацию в любом случае.

(!) При сборе небольшого объёма информации, к примеру, всех игр на русском языке в любом регионе, ошибок и временных блокировок возникнуть не должно. 
^^^^ После оптимизации скрипта это также относится к американскому региону с английским, пока количество страниц в нём не перевалило за тысячу. Но этот рубеж скоро будет преодолён и тогда проблема вновь может возникнуть.

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

Также он удобен для ведения бэклога. В своей userdata вы можете раздобыть информацию об играх из своего списка желаемого и играх, которые есть на аккаунте, после чего воспользоваться vlookup, чтобы отметить их на таблице. Это позволит отфильтровать игры, которые у вас есть и которые вы уже желаете, чтобы найти что-нибудь новое и пометить. Также можно пометить игры, которые вы уже изучили, чтобы в дальнейшем их скрывать. 

  • Лайк (+1) 1

Поделиться сообщением


Ссылка на сообщение

Скрипт для улучшения поиска товаров у конкретного продавца на Plati market (он же Plati ru) || Tamper Monkey — функционал перемещён в Plati.Market; Ultimate Enhancer.

Изменено пользователем 0wn3df1x
  • Лайк (+1) 1

Поделиться сообщением


Ссылка на сообщение

Скрыватель игр Steam || Tamper Monkey — функционал перемещён в Ultimate Steam Enhancer.

Скрытый текст

 

В Steam есть функция добавления игр в игнорируемые. В данный момент она устроена так:

  • Сперва вам необходимо навести курсор на многоточие у значка игры
    7vpVvkZ.png
     
  • Затем выбрать “Скрыть”
    8kXDxKJ.png

После того, как вы нажимаете скрыть — Steam делает элемент невидимым, а также отправляет запрос на сервер, чтобы игра добавилась в список игнорируемых вами игр и скрывалась в дальнейшем.

С этим есть две проблемы:

  • Сам процесс скрытия является неудобным. Многоточие занимает всего 1/3 от высоты элемента и по нему легко промахнуться. Кроме того — отсутствует возможность скрытия нескольких игр сразу, каждый раз приходится нажимать многоточие, а затем выбирать скрытие.
  • Скрытие просто делает элементы невидимыми, но они продолжают присутствовать в коде. Сейчас в Steam больше 6000 будущих игр и если вы скроете, к примеру, 5000 из них — код этих 5000 игр всё равно будет присутствовать на странице, загружая вашу память и замедляя взаимодействие.

Мой скрипт:

  • Добавляет к каждому элементу кольца высотой в 99% элемента, в результате чего по ним практически невозможно промахнуться. 
  • Добавляет кнопку “Скрыть выбранное”, которая скрывает все помеченные кольцами игры.
  • Процедура, как и в оригинальном скрипте, отправляет запрос на сервер, чтобы игры добавлялись в список игнорируемых вами игр и скрывались в дальнейшем.
  • Но теперь, вместо простого скрытия с глаз пользователя, игнорируемые игры полностью удаляются из кода страницы, что заметно уменьшает нагрузку и ускоряет взаимодействие.

ZP1aeC0.png

 

Скрытый текст


// ==UserScript==
// @name         Скрыватель игр Steam
// @namespace    steam-game-hider
// @version      2
// @description  Добавляет флажки к играм в магазине Steam и позволяет скрывать выбранные игры по нажатию кнопки.
// @author       0wn3df1x
// @match        https://store.steampowered.com/search/*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        unsafeWindow
// ==/UserScript==

(function () {
    "use strict";

    // Добавляет флажки к каждой игре
    function addCheckboxes() {
        const gameLinks = document.querySelectorAll("a.search_result_row:not(.ds_ignored):not(.ds_excluded_by_preferences):not(.ds_wishlist):not(.ds_owned)");
        gameLinks.forEach(link => {
            if (link.querySelector(".my-checkbox")) return;
            const checkbox = document.createElement("input");
            checkbox.type = "checkbox";
            checkbox.className = "my-checkbox";
            checkbox.dataset.appid = link.dataset.dsAppid;
            link.insertBefore(checkbox, link.firstChild);

            checkbox.addEventListener("change", function () {
                link.style.background = this.checked ? "linear-gradient(to bottom, #381616, #5d1414)" : "";
            });
        });
    }

    // Скрывает выбранные игры
    function hideSelectedGames() {
        const checkboxes = document.querySelectorAll(".my-checkbox:checked");
        checkboxes.forEach(checkbox => {
            const link = document.querySelector(`a[data-ds-appid="${checkbox.dataset.appid}"]`);
            if (link) {
                link.classList.add("ds_ignored", "ds_flagged");
                checkbox.remove();
                jQuery.ajax({
                    url: "https://store.steampowered.com/recommended/ignorerecommendation/",
                    type: "POST",
                    data: {
                        sessionid: g_sessionID,
                        appid: checkbox.dataset.appid,
                        remove: 0,
                        snr: "1_account_notinterested_",
                    },
                    success: () => {
                        console.log(`Игра с appid ${checkbox.dataset.appid} добавлена в список игнорирования`);
                        GDynamicStore.InvalidateCache();
                    },
                });
            }
        });
    }

    // Удаляет все элементы с классами ds_ignored и ds_excluded_by_preferences
    function removeIgnoredGames() {
        const ignoredGames = document.querySelectorAll("a.search_result_row.ds_ignored, a.search_result_row.ds_excluded_by_preferences,a.search_result_row.ds_wishlist");
        ignoredGames.forEach(game => game.remove());
    }

    // Добавляем кнопку "Скрыть выбранное"
    const button = document.createElement("button");
    button.textContent = "Скрыть выбранное";
    button.addEventListener("click", hideSelectedGames);
    button.classList.add("my-button", "floating-button");
    document.body.appendChild(button);

    // Стилизуем кнопку и делаем её плавающей
    GM_addStyle(`
        .my-button {
            margin-right: 10px;
            padding: 10px 20px;
            border: none;
            border-radius: 50px;
            font-size: 16px;
            font-weight: 700;
            color: #fff;
            background: linear-gradient(to right, #16202D, #1B2838);
            box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.2);
            cursor: pointer;
            font-family: "Roboto", sans-serif;
        }
        .my-button:hover {
            background: linear-gradient(to right, #0072ff, #00c6ff);
            box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.3);
        }
        .floating-button {
            position: fixed;
            top: 20px;
            right: 20px;
            z-index: 1000;
        }
    `);

    // Стилизуем флажки
    GM_addStyle(`
        input[type=checkbox] {
            -webkit-appearance: none;
            -moz-appearance: none;
            appearance: none;
            border: 6px inset rgba(255, 0, 0, 0.8);
            border-radius: 50%;
            width: 42px;
            height: 42px;
            outline: none;
            transition: .15s ease-in-out;
            vertical-align: middle;
            position: absolute;
            left: 0px;
            top: 50%;
            transform: translateY(-50%);
            background-color: rgba(0, 0, 0, 0.0);
            box-shadow: inset 0 0 0 0 rgba(255, 255, 255, 0.5);
            cursor: pointer;
            z-index: 9999;
        }
        input[type=checkbox]:checked {
            background-color: rgba(0, 0, 0, 0.5);
            border-color: #b71c1c;
            box-shadow: inset 0 0 0 12px rgba(255, 0, 0, 0.5);
        }
        input[type=checkbox]:after {
            content: "";
            display: block;
            position: absolute;
            left: 50%;
            top: 50%;
            transform: translate(-50%, -50%) scale(0);
            width: 25px;
            height: 25px;
            border-radius: 50%;
            background-color: rgba(0, 0, 0, 0.9);
            opacity: 0.9;
            box-shadow: 0 0 0 0 #b71c1c;
            transition: transform .15s ease-in-out, box-shadow .15s ease-in-out;
        }
        input[type=checkbox]:checked:after {
            transform: translate(-50%, -50%) scale(1);
            box-shadow: 0 0 0 4px #b71c1c;
        }
    `);

    // Добавляем MutationObserver для обработки динамически подгружаемых элементов
    const observer = new MutationObserver(mutations => {
        mutations.forEach(mutation => {
            if (mutation.type === "childList" && mutation.addedNodes.length) {
                addCheckboxes();
                removeIgnoredGames();
            }
        });
    });

    observer.observe(document.body, { childList: true, subtree: true });

    // Инициализация флажков и удаление игр с классом ds_ignored при загрузке страницы
    addCheckboxes();
    removeIgnoredGames();
})();

 


P.S. Если вдруг вы захотите видеть скрытые игры — можно будет просто отключить скрипт.

 

 

Изменено пользователем 0wn3df1x
  • Лайк (+1) 1

Поделиться сообщением


Ссылка на сообщение

СписЖел Steam || Tamper Monkey

Обновление системы списков желаемого в Steam обеспечило головную боль для владельцев крупных списков желаемого, поскольку с новой системой резко сократилось количество игр в запросе к серверу, из-за чего выросло число самих запросов. Рост числа запросов напрягает стимовскую систему защиты от DDOS, в результате чего на пользователя вешается временная блокировка и в списке желаемого перестают показываться картинки и сами игры.

Данный скрипт используют новую систему, но при этом увеличивает количество игр в едином батче до 200, что кардинально сокращает количество запросов к серверу. С ним список желаемого на 1000 игр подразумевает всего 5 запросов, обеспечивая снятие напряжения с системы защиты. Минус — это не оригинальный список желаемого, а подобие его “зеркала” в новом окне: можно открывать игры по ссылкам, но удалять их, менять порядок и тому подобное — не получится. 

Выглядит вот так:
yTtvvc6.png

Игры упорядочены по технической дате выхода (указана в скобках) от старых к новым.
Если на игру есть скидка — блок подсвечивается жёлтым.

 

Скрытый текст

// ==UserScript==
// @name         СписЖел
// @namespace    https://store.steampowered.com/wishlist/
// @version      0.1
// @description  Смотрим списки желаемого по-другому
// @author       0wn3df1x
// @match        https://store.steampowered.com/wishlist/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const BATCH_SIZE = 200;

    // Соответствия между числовыми идентификаторами тегов и текстовыми значениями
    const tagMappings = {
        9: 'Strategy',
        19: 'Action',
        21: 'Adventure',
        84: 'Design & Illustration',
        87: 'Utilities',
        113: 'Free to Play',
        122: 'RPG',
        128: 'Massively Multiplayer',
        492: 'Indie',
        493: 'Early Access',
        597: 'Casual',
        599: 'Simulation',
        699: 'Racing',
        701: 'Sports',
        784: 'Video Production',
        809: 'Photo Editing',
        872: 'Animation & Modeling',
        1027: 'Audio Production',
        1036: 'Education',
        1038: 'Web Publishing',
        1445: 'Software Training',
        1616: 'Trains',
        1621: 'Music',
        1625: 'Platformer',
        1628: 'Metroidvania',
        1638: 'Dog',
        1643: 'Building',
        1644: 'Driving',
        1645: 'Tower Defense',
        1646: 'Hack and Slash',
        1647: 'Western',
        1649: 'GameMaker',
        1651: 'Satire',
        1654: 'Relaxing',
        1659: 'Zombies',
        1662: 'Survival',
        1663: 'FPS',
        1664: 'Puzzle',
        1665: 'Match 3',
        1666: 'Card Game',
        1667: 'Horror',
        1669: 'Moddable',
        1670: '4X',
        1671: 'Superhero',
        1673: 'Aliens',
        1674: 'Typing',
        1676: 'RTS',
        1677: 'Turn-Based',
        1678: 'War',
        1680: 'Heist',
        1681: 'Pirates',
        1684: 'Fantasy',
        1685: 'Co-op',
        1687: 'Stealth',
        1688: 'Ninja',
        1695: 'Open World',
        1697: 'Third Person',
        1698: 'Point & Click',
        1702: 'Crafting',
        1708: 'Tactical',
        1710: 'Surreal',
        1714: 'Psychedelic',
        1716: 'Rogue-like',
        1717: 'Hex Grid',
        1718: 'MOBA',
        1719: 'Comedy',
        1720: 'Dungeon Crawler',
        1721: 'Psychological Horror',
        1723: 'Action RTS',
        1730: 'Sokoban',
        1732: 'Voxel',
        1733: 'Unforgiving',
        1734: 'Fast-Paced',
        1736: 'LEGO',
        1738: 'Hidden Object',
        1741: 'Turn-Based Strategy',
        1742: 'Story Rich',
        1743: 'Fighting',
        1746: 'Basketball',
        1751: 'Comic Book',
        1752: 'Rhythm',
        1753: 'Skateboarding',
        1754: 'MMORPG',
        1755: 'Space',
        1756: 'Great Soundtrack',
        1759: 'Perma Death',
        1770: 'Board Game',
        1773: 'Arcade',
        1774: 'Shooter',
        1775: 'PvP',
        1777: 'Steampunk',
        3796: 'Based On A Novel',
        3798: 'Side Scroller',
        3799: 'Visual Novel',
        3810: 'Sandbox',
        3813: 'Real Time Tactics',
        3814: 'Third-Person Shooter',
        3834: 'Exploration',
        3835: 'Post-apocalyptic',
        3839: 'First-Person',
        3841: 'Local Co-Op',
        3843: 'Online Co-Op',
        3854: 'Lore-Rich',
        3859: 'Multiplayer',
        3871: '2D',
        3877: 'Precision Platformer',
        3878: 'Competitive',
        3916: 'Old School',
        3920: 'Cooking',
        3934: 'Immersive',
        3942: 'Sci-fi',
        3952: 'Gothic',
        3955: 'Character Action Game',
        3959: 'Rogue-lite',
        3964: 'Pixel Graphics',
        3968: 'Physics',
        3978: 'Survival Horror',
        3987: 'Historical',
        3993: 'Combat',
        4004: 'Retro',
        4018: 'Vampire',
        4026: 'Difficult',
        4036: 'Parkour',
        4046: 'Dragons',
        4057: 'Magic',
        4064: 'Thriller',
        4085: 'Anime',
        4094: 'Minimalist',
        4102: 'Combat Racing',
        4106: 'Action-Adventure',
        4115: 'Cyberpunk',
        4136: 'Funny',
        4137: 'Transhumanism',
        4145: 'Cinematic',
        4150: 'World War II',
        4155: 'Class-Based',
        4158: "Beat 'em up",
        4161: 'Real-Time',
        4166: 'Atmospheric',
        4168: 'Military',
        4172: 'Medieval',
        4175: 'Realistic',
        4182: 'Singleplayer',
        4184: 'Chess',
        4191: '3D',
        4195: 'Cartoony',
        4202: 'Trading',
        4231: 'Action RPG',
        4234: 'Short',
        4236: 'Loot',
        4242: 'Episodic',
        4252: 'Stylized',
        4255: "Shoot 'Em Up",
        4291: 'Spaceships',
        4295: 'Futuristic',
        4305: 'Colorful',
        4325: 'Turn-Based Combat',
        4328: 'City Builder',
        4342: 'Dark',
        4345: 'Gore',
        4364: 'Grand Strategy',
        4376: 'Assassin',
        4400: 'Abstract',
        4434: 'JRPG',
        4474: 'CRPG',
        4486: 'Choose Your Own Adventure',
        4508: 'Co-op Campaign',
        4520: 'Farming',
        4559: 'Quick-Time Events',
        4562: 'Cartoon',
        4598: 'Alternate History',
        4604: 'Dark Fantasy',
        4608: 'Swordplay',
        4637: 'Top-Down Shooter',
        4667: 'Violent',
        4684: 'Wargame',
        4695: 'Economy',
        4700: 'Movie',
        4711: 'Replay Value',
        4726: 'Cute',
        4736: '2D Fighter',
        4747: 'Character Customization',
        4754: 'Politics',
        4758: 'Twin Stick Shooter',
        4777: 'Spectacle fighter',
        4791: 'Top-Down',
        4821: 'Mechs',
        4835: '6DOF',
        4840: '4 Player Local',
        4845: 'Capitalism',
        4853: 'Political',
        4878: 'Parody',
        4885: 'Bullet Hell',
        4947: 'Romance',
        4975: '2.5D',
        4994: 'Naval Combat',
        5030: 'Dystopian',
        5055: 'eSports',
        5094: 'Narration',
        5125: 'Procedural Generation',
        5153: 'Kickstarter',
        5154: 'Score Attack',
        5160: 'Dinosaurs',
        5179: 'Cold War',
        5186: 'Psychological',
        5228: 'Blood',
        5230: 'Sequel',
        5300: 'God Game',
        5310: 'Games Workshop',
        5348: 'Mod',
        5350: 'Family Friendly',
        5363: 'Destruction',
        5372: 'Conspiracy',
        5379: '2D Platformer',
        5382: 'World War I',
        5390: 'Time Attack',
        5395: '3D Platformer',
        5407: 'Benchmark',
        5411: 'Beautiful',
        5432: 'Programming',
        5502: 'Hacking',
        5537: 'Puzzle-Platformer',
        5547: 'Arena Shooter',
        5577: 'RPGMaker',
        5608: 'Emotional',
        5611: 'Mature',
        5613: 'Detective',
        5652: 'Collectathon',
        5673: 'Modern',
        5708: 'Remake',
        5711: 'Team-Based',
        5716: 'Mystery',
        5727: 'Baseball',
        5752: 'Robots',
        5765: 'Gun Customization',
        5794: 'Science',
        5796: 'Bullet Time',
        5851: 'Isometric',
        5900: 'Walking Simulator',
        5914: 'Tennis',
        5923: 'Dark Humor',
        5941: 'Reboot',
        5981: 'Mining',
        5984: 'Drama',
        6041: 'Horses',
        6052: 'Noir',
        6129: 'Logic',
        6214: 'Birds',
        6276: 'Inventory Management',
        6310: 'Diplomacy',
        6378: 'Crime',
        6426: 'Choices Matter',
        6506: '3D Fighter',
        6621: 'Pinball',
        6625: 'Time Manipulation',
        6650: 'Nudity',
        6691: "1990's",
        6702: 'Mars',
        6730: 'PvE',
        6815: 'Hand-drawn',
        6869: 'Nonlinear',
        6910: 'Naval',
        6915: 'Martial Arts',
        6948: 'Rome',
        6971: 'Multiple Endings',
        7038: 'Golf',
        7107: 'Real-Time with Pause',
        7108: 'Party',
        7113: 'Crowdfunded',
        7178: 'Party Game',
        7208: 'Female Protagonist',
        7250: 'Linear',
        7309: 'Skiing',
        7328: 'Bowling',
        7332: 'Base Building',
        7368: 'Local Multiplayer',
        7423: 'Sniper',
        7432: 'Lovecraftian',
        7478: 'Illuminati',
        7481: 'Controller',
        7569: 'Grid-Based Movement',
        7622: 'Offroad',
        7702: 'Narrative',
        7743: '1980s',
        7918: 'Dwarf',
        7926: 'Artificial Intelligence',
        7948: 'Soundtrack',
        8013: 'Software',
        8075: 'TrackIR',
        8093: 'Minigames',
        8122: 'Level Editor',
        8253: 'Music-Based Procedural Generation',
        8369: 'Investigation',
        8461: 'Well-Written',
        8666: 'Runner',
        8945: 'Resource Management',
        9130: 'Hentai',
        9157: 'Underwater',
        9204: 'Immersive Sim',
        9271: 'Trading Card Game',
        9541: 'Demons',
        9551: 'Dating Sim',
        9564: 'Hunting',
        9592: 'Dynamic Narration',
        9803: 'Snow',
        9994: 'Experience',
        10235: 'Life Sim',
        10383: 'Transportation',
        10397: 'Memes',
        10437: 'Trivia',
        10679: 'Time Travel',
        10695: 'Party-Based RPG',
        10808: 'Supernatural',
        10816: 'Split Screen',
        11014: 'Interactive Fiction',
        11095: 'Boss Rush',
        11104: 'Vehicular Combat',
        11123: 'Mouse only',
        11333: 'Villain Protagonist',
        11634: 'Vikings',
        12057: 'Tutorial',
        12095: 'Sexual Content',
        12190: 'Boxing',
        12286: 'Warhammer 40K',
        12472: 'Management',
        13070: 'Solitaire',
        13190: 'America',
        13276: 'Tanks',
        13382: 'Archery',
        13577: 'Sailing',
        13782: 'Experimental',
        13906: 'Game Development',
        14139: 'Turn-Based Tactics',
        14153: 'Dungeons & Dragons',
        14720: 'Nostalgia',
        14906: 'Intentionally Awkward Controls',
        15045: 'Flight',
        15172: 'Conversation',
        15277: 'Philosophical',
        15339: 'Documentary',
        15564: 'Fishing',
        15868: 'Motocross',
        15954: 'Silent Protagonist',
        16094: 'Mythology',
        16250: 'Gambling',
        16598: 'Space Sim',
        16689: 'Time Management',
        17015: 'Werewolves',
        17305: 'Strategy RPG',
        17337: 'Lemmings',
        17389: 'Tabletop',
        17770: 'Asynchronous Multiplayer',
        17894: 'Cats',
        17927: 'Pool',
        18594: 'FMV',
        19568: 'Cycling',
        19780: 'Submarine',
        19995: 'Dark Comedy',
        21006: 'Underground',
        21725: 'Tactical RPG',
        21978: 'VR',
        22602: 'Agriculture',
        22955: 'Mini Golf',
        24003: 'Word Game',
        24904: 'NSFW',
        25085: 'Touch-Friendly',
        26921: 'Political Sim',
        27758: 'Voice Control',
        28444: 'Snowboarding',
        29363: '3D Vision',
        29482: 'Souls-like',
        29855: 'Ambient',
        30358: 'Nature',
        30927: 'Fox',
        31275: 'Text-Based',
        31579: 'Otome',
        32322: 'Deckbuilding',
        33572: 'Mahjong',
        35079: 'Job Simulator',
        42089: 'Jump Scare',
        42329: 'Coding',
        42804: 'Action Roguelike',
        44868: 'LGBTQ+',
        47827: 'Wrestling',
        49213: 'Rugby',
        51306: 'Foreign',
        56690: 'On-Rails Shooter',
        61357: 'Electronic Music',
        65443: 'Adult Content',
        71389: 'Spelling',
        87918: 'Farming Sim',
        91114: 'Shop Keeper',
        92092: 'Jet',
        96359: 'Skating',
        97376: 'Cozy',
        102530: 'Elf',
        117648: '8-bit Music',
        123332: 'Bikes',
        129761: 'ATV',
        143739: 'Electronic',
        150626: 'Gaming',
        158638: 'Cricket',
        176733: 'Tile-Matching',
        176981: 'Battle Royale',
        180368: 'Faith',
        189941: 'Instrumental Music',
        198631: 'Mystery Dungeon',
        198913: 'Motorbike',
        220585: 'Colony Sim',
        233824: 'Feature Film',
        252854: 'BMX',
        255534: 'Automation',
        323922: 'Musou',
        324176: 'Hockey',
        337964: 'Rock Music',
        348922: 'Steam Machine',
        353880: 'Looter Shooter',
        363767: 'Snooker',
        379975: 'Clicker',
        454187: 'Traditional Roguelike',
        552282: 'Wholesome',
        603297: 'Hardware',
        615955: 'Idler',
        620519: 'Hero Shooter',
        745697: 'Social Deduction',
        769306: 'Escape Room',
        776177: '360 Video',
        791774: 'Card Battler',
        847164: 'Volleyball',
        856791: 'Asymmetric VR',
        916648: 'Creature Collector',
        922563: 'Roguevania',
        1003823: 'Profile Features Limited',
        1023537: 'Boomer Shooter',
        1084988: 'Auto Battler',
        1091588: 'Roguelike Deckbuilder',
        1100686: 'Outbreak Sim',
        1100687: 'Automobile Sim',
        1100688: 'Medical Sim',
        1100689: 'Open World Survival Craft',
        1199779: 'Extraction Shooter',
        1220528: 'Hobby Sim',
        1254546: 'Football (Soccer)',
        1254552: 'Football (American)',
        // Добавьте остальные соответствия здесь
    };

    // Добавляем кнопку на страницу
    setTimeout(() => {
        const button = document.createElement('button');
        button.innerText = 'СписЖел';
        button.style.marginLeft = '10px';
        button.style.backgroundColor = '#66c0f4';
        button.style.color = '#fff';
        button.style.border = 'none';
        button.style.padding = '10px 20px';
        button.style.borderRadius = '4px';
        button.style.cursor = 'pointer';
        button.style.transition = 'background-color 0.3s ease, transform 0.3s ease';
        button.onclick = openModal;
        document.querySelector('h2').appendChild(button);
    }, 1000);

    function openModal() {
        const modalOverlay = document.createElement('div');
        modalOverlay.className = 'FullModalOverlay';
        modalOverlay.style.display = 'block';
        modalOverlay.style.position = 'fixed';
        modalOverlay.style.top = '0';
        modalOverlay.style.left = '0';
        modalOverlay.style.width = '100%';
        modalOverlay.style.height = '100%';
        modalOverlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
        modalOverlay.style.zIndex = '1000';

        const modalContent = document.createElement('div');
        modalContent.className = 'ModalOverlayContent';
        modalContent.style.position = 'fixed';
        modalContent.style.top = '50%';
        modalContent.style.left = '50%';
        modalContent.style.transform = 'translate(-50%, -50%)';
        modalContent.style.backgroundColor = '#1b2838';
        modalContent.style.padding = '20px';
        modalContent.style.borderRadius = '8px';
        modalContent.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.2)';
        modalContent.style.zIndex = '1001';

        const closeButton = document.createElement('button');
        closeButton.innerText = '×';
        closeButton.style.position = 'absolute';
        closeButton.style.top = '10px';
        closeButton.style.right = '10px';
        closeButton.style.backgroundColor = 'transparent';
        closeButton.style.border = 'none';
        closeButton.style.color = '#c7d5e0';
        closeButton.style.fontSize = '20px';
        closeButton.style.cursor = 'pointer';
        closeButton.onclick = () => modalOverlay.remove();

        const input = document.createElement('input');
        input.type = 'text';
        input.placeholder = 'Enter Steam ID';
        input.style.marginBottom = '10px';
        input.style.width = '100%';
        input.style.padding = '10px';
        input.style.borderRadius = '4px';
        input.style.border = '1px solid #0e141b';
        input.style.backgroundColor = '#16202d';
        input.style.color = '#c7d5e0';

        const fetchButton = document.createElement('button');
        fetchButton.innerText = 'Получить СписЖел';
        fetchButton.style.padding = '10px';
        fetchButton.style.borderRadius = '4px';
        fetchButton.style.border = 'none';
        fetchButton.style.backgroundColor = '#66c0f4';
        fetchButton.style.color = '#fff';
        fetchButton.style.cursor = 'pointer';
        fetchButton.onclick = () => {
            fetchWishlist(input.value).then(() => fetchGameDetails());
            modalOverlay.remove();
        };

        modalContent.appendChild(closeButton);
        modalContent.appendChild(input);
        modalContent.appendChild(fetchButton);
        modalOverlay.appendChild(modalContent);
        document.body.appendChild(modalOverlay);
    }

    async function fetchWishlist(steamId) {
        if (!steamId) {
            alert('Please enter a Steam ID');
            return;
        }

        // Удаляем старый wshlstinf0
        localStorage.removeItem('wshlstinf0');

        const wishlistUrl = `https://api.steampowered.com/IWishlistService/GetWishlist/v1/?steamid=${steamId}`;
        const response = await fetch(wishlistUrl);
        const data = await response.json();

        if (data.response && data.response.items) {
            const wishlistItems = data.response.items;
            const wishlistData = wishlistItems.map(item => ({
                appid: item.appid,
                date_added: item.date_added
            }));
            localStorage.setItem('wshlstinf0', JSON.stringify(wishlistData));
            alert('Данные списка желаемого сохранены в local storage');
        } else {
            alert('А где эт самое?');
        }
    }

    async function fetchGameDetails() {
        const wishlistData = JSON.parse(localStorage.getItem('wshlstinf0'));
        if (!wishlistData) {
            alert('Чот в local storage нет ничего');
            return;
        }

        const appIds = wishlistData.map(item => item.appid);
        const batches = [];
        for (let i = 0; i < appIds.length; i += BATCH_SIZE) {
            batches.push(appIds.slice(i, i + BATCH_SIZE));
        }

        const results = [];
        for (const batch of batches) {
            const batchResults = await fetchGameDetailsBatch(batch);
            results.push(...batchResults);
        }

        openResultsInNewWindow(results, wishlistData);

        // Удаляем wshlstinf0 после использования
        localStorage.removeItem('wshlstinf0');
    }

    async function fetchGameDetailsBatch(appIds) {
        const gameDetailsUrl = `https://api.steampowered.com/IStoreBrowseService/GetItems/v1?input_json=${encodeURIComponent(JSON.stringify({
            ids: appIds.map(appid => ({ appid })),
            context: {
                language: 'english',
                country_code: 'RU', //Смена валюты. Можно заменить на US или KZ
                steam_realm: 1
            },
            data_request: {
                include_release: true,
                include_tag_count: true
            }
        }))}`;

        const response = await fetch(gameDetailsUrl);
        const data = await response.json();

        if (data.response && data.response.store_items) {
            return data.response.store_items.map(item => {
                const release = item.release || {};
                const releaseDate = release.steam_release_date || release.custom_release_date_message || 'N/A';
                const bestPurchaseOption = item.best_purchase_option || {};
                return {
                    appid: item.appid,
                    name: item.name || 'N/A',
                    tagids: item.tagids ? item.tagids.join(',') : 'N/A',
                    releaseDate: releaseDate,
                    comingSoonDisplay: release.coming_soon_display || 'date_full',
                    finalPrice: bestPurchaseOption.formatted_final_price || 'N/A',
                    originalPrice: bestPurchaseOption.formatted_original_price || 'N/A',
                    discountPercentage: bestPurchaseOption.discount_pct || 'N/A'
                };
            });
        } else {
            return [];
        }
    }

    function formatDate(timestamp) {
        const date = new Date(timestamp * 1000);
        const day = String(date.getDate()).padStart(2, '0');
        const month = String(date.getMonth() + 1).padStart(2, '0');
        const year = date.getFullYear();
        return `${day}.${month}.${year}`;
    }

    function formatDateTime(timestamp) {
        const date = new Date(timestamp * 1000);
        const day = String(date.getDate()).padStart(2, '0');
        const month = String(date.getMonth() + 1).padStart(2, '0');
        const year = date.getFullYear();
        const hours = String(date.getHours()).padStart(2, '0');
        const minutes = String(date.getMinutes()).padStart(2, '0');
        const seconds = String(date.getSeconds()).padStart(2, '0');
        return `${day}.${month}.${year} ${hours}:${minutes}:${seconds}`;
    }

    function formatDisplayedDate(releaseDate, comingSoonDisplay) {
        if (isNaN(releaseDate)) return releaseDate;

        const date = new Date(releaseDate * 1000);
        const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
        const year = date.getFullYear();
        const month = monthNames[date.getMonth()];
        const quarter = Math.floor(date.getMonth() / 3) + 1;

        switch (comingSoonDisplay) {
            case 'date_month':
                return `${month} ${year}`;
            case 'date_quarter':
                return `Q${quarter} ${year}`;
            case 'date_year':
                return `${year}`;
            default:
                return formatDate(releaseDate);
        }
    }

    function formatTechReleaseDate(releaseDate) {
        if (releaseDate === 'Coming soon' || releaseDate === 'To be announced') {
            return '31.12.2123';
        }
        if (releaseDate === 'N/A') {
            return '31.12.2999';
        }
        return isNaN(releaseDate) ? releaseDate : formatDate(releaseDate);
    }

    function replaceTagIdsWithNames(tagids) {
        if (!tagids) return 'N/A';
        const tags = tagids.split(',').map(tagId => tagMappings[tagId] || tagId);
        return tags.join(', ');
    }

    function openResultsInNewWindow(results, wishlistData) {
        const newWindow = window.open('', '_blank');
        newWindow.document.write(`
            <html>
            <head>
                <title>СписЖел</title>
                <style>
                    body {
                        font-family: Arial, sans-serif;
                        background-color: #1b2838;
                        color: #acb2b8;
                        margin: 0;
                        padding: 20px;
                    }
                    .container {
                        display: flex;
                        flex-wrap: wrap;
                        gap: 20px;
                    }
                    .game-card {
                        background-color: #16202d;
                        border: 1px solid #0e141b;
                        border-radius: 8px;
                        padding: 15px;
                        width: 300px;
                        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
                        display: flex;
                        flex-direction: column;
                        justify-content: space-between;
                        transition: transform 0.3s ease, box-shadow 0.3s ease;
                    }
                    .game-card:hover {
                        transform: translateY(-5px);
                        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
                    }
                    .game-card img {
                        width: 100%;
                        height: auto;
                        border-radius: 8px;
                        transition: transform 0.3s ease;
                    }
                    .game-card:hover img {
                        transform: scale(1.05);
                    }
                    .game-card h2 {
                        margin: 10px 0;
                        font-size: 18px;
                        color: #c7d5e0;
                        transition: color 0.3s ease;
                    }
                    .game-card h2:hover {
                        color: #66c0f4;
                    }
                    .game-card .tags {
                        display: flex;
                        flex-wrap: wrap;
                        gap: 5px;
                        margin-bottom: 10px;
                    }
                    .game-card .tags span {
                        background-color: #0e141b;
                        padding: 3px 8px;
                        border-radius: 4px;
                        font-size: 12px;
                        color: #acb2b8;
                        transition: background-color 0.3s ease, color 0.3s ease;
                    }
                    .game-card .tags span:hover {
                        background-color: #66c0f4;
                        color: #fff;
                    }
                    .game-card .price-info {
                        display: flex;
                        justify-content: space-between;
                        align-items: center;
                        margin-top: 10px;
                    }
                    .game-card .price-info .original-price {
                        text-decoration: line-through;
                        font-size: 14px;
                        color: #888;
                        transition: color 0.3s ease;
                    }
                    .game-card .price-info .original-price:hover {
                        color: #ff4500;
                    }
                    .game-card .price-info .discount-percentage {
                        background-color: #4c6b22;
                        color: #fff;
                        padding: 3px 8px;
                        border-radius: 4px;
                        font-size: 16px;
                        transition: background-color 0.3s ease;
                    }
                    .game-card .price-info .discount-percentage:hover {
                        background-color: #66c0f4;
                    }
                    .game-card .date-added {
                        text-align: right;
                        font-size: 12px;
                        color: #888;
                        margin-top: 10px;
                        transition: color 0.3s ease;
                    }
                    .game-card .date-added:hover {
                        color: #66c0f4;
                    }
                    .game-card.discounted {
                        border: 2px solid #ffd700; /* Золотистый цвет */
                        position: relative;
                    }
                    .game-card.discounted::before {
                        content: '';
                        position: absolute;
                        top: -2px;
                        left: -2px;
                        right: -2px;
                        bottom: -2px;
                        border: 2px solid #ffd700; /* Золотистый цвет */
                        border-radius: 8px;
                        animation: pulse 2s infinite;
                    }
                    @keyframes pulse {
                        0% {
                            box-shadow: 0 0 0 0 rgba(255, 215, 0, 0.7); /* Золотистый цвет */
                        }
                        70% {
                            box-shadow: 0 0 0 10px rgba(255, 215, 0, 0); /* Золотистый цвет */
                        }
                        100% {
                            box-shadow: 0 0 0 0 rgba(255, 215, 0, 0); /* Золотистый цвет */
                        }
                    }
                    /* Стили для кнопки */
                    button {
                        background-color: #66c0f4;
                        color: #fff;
                        border: none;
                        padding: 10px 20px;
                        border-radius: 4px;
                        cursor: pointer;
                        transition: background-color 0.3s ease;
                    }
                    button:hover {
                        background-color: #4b8ac9;
                    }
                    /* Стили для модального окна */
                    .FullModalOverlay {
                        display: none;
                        position: fixed;
                        top: 0;
                        left: 0;
                        width: 100%;
                        height: 100%;
                        background-color: rgba(0, 0, 0, 0.5);
                        z-index: 1000;
                    }
                    .ModalOverlayContent {
                        position: fixed;
                        top: 50%;
                        left: 50%;
                        transform: translate(-50%, -50%);
                        background-color: #1b2838;
                        padding: 20px;
                        border-radius: 8px;
                        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
                        z-index: 1001;
                    }
                    .ModalOverlayContent input {
                        width: 100%;
                        padding: 10px;
                        margin-bottom: 10px;
                        border-radius: 4px;
                        border: 1px solid #0e141b;
                        background-color: #16202d;
                        color: #c7d5e0;
                    }
                    .ModalOverlayContent button {
                        margin-right: 10px;
                    }
                    .ModalOverlayContent button:last-child {
                        margin-right: 0;
                    }
                    .ModalOverlayContent button.close {
                        position: absolute;
                        top: 10px;
                        right: 10px;
                        background-color: transparent;
                        border: none;
                        color: #c7d5e0;
                        font-size: 20px;
                        cursor: pointer;
                    }
                </style>
            </head>
            <body>
                <h1 style="color: #c7d5e0;">СписЖел</h1>
                <div class="container">
        `);

        // Сортировка результатов по технической дате
        results.sort((a, b) => {
            const dateA = new Date(formatTechReleaseDate(a.releaseDate).split('.').reverse().join('-'));
            const dateB = new Date(formatTechReleaseDate(b.releaseDate).split('.').reverse().join('-'));
            return dateA - dateB;
        });

        results.forEach(result => {
            const dateAdded = wishlistData.find(item => item.appid === result.appid).date_added;
            const formattedDateAdded = formatDateTime(dateAdded);
            const formattedTechReleaseDate = formatTechReleaseDate(result.releaseDate);
            const formattedDisplayedReleaseDate = formatDisplayedDate(result.releaseDate, result.comingSoonDisplay);
            const imageUrl = `https://shared.fastly.steamstatic.com/store_item_assets/steam/apps/${result.appid}/header_292x136.jpg`;
            const gameUrl = `https://store.steampowered.com/app/${result.appid}`;
            const tagNames = replaceTagIdsWithNames(result.tagids);

            const cardClass = result.discountPercentage !== 'N/A' ? 'game-card discounted' : 'game-card';

            newWindow.document.write(`
                <div class="${cardClass}">
                    <a href="${gameUrl}" target="_blank">
                        <img src="${imageUrl}" alt="Game Image">
                        <h2>${result.name}</h2>
                    </a>
                    <div class="tags">
                        ${tagNames.split(', ').map(tag => `<span>${tag}</span>`).join('')}
                    </div>
                    <div>
                        <strong>Release Date:</strong> ${formattedDisplayedReleaseDate} (${formattedTechReleaseDate})
                    </div>
                    <div class="price-info">
                        <div>
                            ${result.originalPrice !== 'N/A' ? `<span class="original-price">${result.originalPrice}</span>` : ''}
                            <span>${result.finalPrice}</span>
                        </div>
                        ${result.discountPercentage !== 'N/A' ? `<span class="discount-percentage">${result.discountPercentage}%</span>` : ''}
                    </div>
                    <div class="date-added">
                        Added: ${formattedDateAdded}
                    </div>
                </div>
            `);
        });

        newWindow.document.write(`
                </div>
            </body>
            </html>
        `);
        newWindow.document.close();
    }
})();

 

Скрытый текст
  1. Сперва вам придётся узнать ваш или чужой Steam ID, выглядит он вот так:
    https://steamcommunity.com/profiles/76561198015968954
    Нас интересуют только цифры: 76561198015968954
  2. В списке желаемого появится кнопка СписЖел: 
    GUn6a89.png
  3. При её нажатии появится модальное окно:
    HQmQDxp.png
    Вбиваем цифры и жмём “Получить СписЖел”.
     
  4. Жмём ОК
    r1P47TK.png
     
  5. Готово:
    JyfME5L.png

P.S. С крупными списками желаемого ожидание может занять какое-то время. 700 игр грузятся секунд за 7.  Тут важно понимать, что получается огромный объём данных.


 

Изменено пользователем 0wn3df1x
  • Лайк (+1) 1

Поделиться сообщением


Ссылка на сообщение
1 час назад, 0wn3df1x сказал:

Steam Reviews Extender || Tamper Monkey

Система обзоров в Steam проверена временем, но бывают ситуации, в которых она создаёт искажённое представление о продукте.

К примеру, иногда подрядчик, осуществляющий локализацию на китайский язык, делает свою работу недобросовестно. И в этом случае игроки из Китая закономерно оставляют отрицательные обзоры. Китайская популяция огромна и настолько же огромная китайская аудитория в Steam. Отрицательные обзоры от настолько огромной аудитории могут уронить рейтинг любого проекта. Как результат — игра хорошая, а обзоры к ней смешанные. И многие потребители, видя смешанные обзоры, просто закрывают страницу. Обратная ситуация связана с играми, ориентированными на китайский рынок. Вы заходите на их страницу, видите огромное количество положительных обзоров, но без изучения этих обзоров вы не сможете понять, что большинство из них оставили игроки из Китая и игра интересна преимущественно китайским пользователям.

Ещё одна проблема — во избежание злоупотреблений со стороны издателей и разработчиков, система обзоров не учитывает обзоры активировавших игру ключом. Но ведь иногда активировать игру ключом дешевле. Плюс, учитывая ситуацию последних лет, ключи остаются одним из редких вариантов активации определённых игр в России.

Третья проблема — отсутствие статистики по обзорам на языке, который использует потребитель, хотя они важны.
К примеру:

  • Иногда те же отечественные игры непонятны покупателям вне стран СНГ, либо в этих игра плохая локализация на какой-то из европейских языков. И в таком случае иностранцы оставляют отрицательные обзоры, в то время как русскоязычные в восторге от игры.
  • Это же касается и жанровых предпочтений. Ряд игр нравится русскоязычным больше, чем игрокам из Азии, Европы или США. 
  • Либо наоборот — игра имеет хорошие обзоры, но в ней просто отвратительная русская локализация. Тем не менее, количество русских обзоров не настолько велико, чтобы повлиять на рейтинг игры. И без изучения обзоров вы не сразу поймёте, что с игрой что-то не так.

Мой скрипт призван решить все перечисленные проблемы.
Поэтому он добавляет три новых вида обзоров в описание игр:

  • Тотальные обзоры — Все обзоры с учётом активаций ключами.
  • Безкитайские обзоры — Все обзоры с вычетом китайских.
  • Русские обзоры — Только русские обзоры.

spkDPBA.png

Если какие-то из обзоров вам не нужны, то вы можете заменить True на False в 17-19 строках кода.


    // Настройки для включения/отключения обзоров
    const settings = {
        showTotalReviews: true, // Показывать тотальные обзоры
        showNonChineseReviews: true, // Показывать безкитайские обзоры
        showRussianReviews: true // Показывать русские обзоры
    };

 

  Код скрипта (Показать содержимое)

 

Это имба, страшно удобно.

  • Лайк (+1) 1

Поделиться сообщением


Ссылка на сообщение

Stelicas || Electron

Stelicas (Steam Library Categories Scraper) - инструмент для извлечения категорий вашей библиотеки Steam, получения подробной информации об играх (включая теги, даты выпуска, отзывы и многое другое) и экспорта данных в структурированный CSV-формат для удобной организации и анализа.

Репозиторий на GitHub

yt1P1R0.png

Введение
Раньше категории библиотеки Steam можно было легко извлечь из файла sharedconfig.vdf. Однако с последними обновлениями клиента Steam этот файл устарел и больше не отражает текущее состояние вашей библиотеки. Самая актуальная информация теперь хранится в базе данных leveldb, которая является частью локального хранилища клиента Steam. Stelicas разработан для доступа к этой базе данных, извлечения ваших категорий библиотеки и обогащения данных подробной информацией об играх из Steam API.

Скрытый текст

Особенности

  • Извлечение категорий библиотеки Steam: Извлекайте ваши пользовательские категории библиотеки Steam напрямую из локальной базы данных.
  • Получение подробной информации об играх: Получайте подробную информацию об играх, включая:
    • Название игры
    • Тип игры
    • Теги
    • Дата выпуска
    • Оценки и количество отзывов
    • Издатели, разработчики и франшизы
    • И многое другое
  • Экспорт в CSV: Сохраняйте извлеченные данные в структурированном CSV-формате для удобного анализа и организации.
  • Удобный интерфейс: Простой и интуитивно понятный интерфейс, который проведет вас через процесс.
Скрытый текст

Как это работает
Stelicas работает, получая доступ к базе данных leveldb, расположенной в локальном хранилище клиента Steam. Он читает записи базы данных, чтобы извлечь ваши пользовательские категории библиотеки и связанные с ними идентификаторы игр. Затем он использует Steam API для получения подробной информации о каждой игре. Наконец, он компилирует все данные в CSV-файл для удобного доступа и анализа.

Во время процесса сбора данных клиент Steam должен быть закрыт. Stelicas включает встроенную проверку, чтобы убедиться, что Steam не запущен, прежде чем продолжить. Если Steam обнаружен как запущенный, инструмент откажется собирать данные, чтобы предотвратить конфликты.

Скрытый текст

Требования
Перед использованием Stelicas убедитесь, что на вашей системе установлено следующее:

  • Node.js (v16 или выше)
  • npm (обычно поставляется с Node.js)
  • Клиент Steam (установлен и выполнен вход)
Скрытый текст

Установка

В репозитории есть релиз. С ним можно сразу переходить к использованию.

Без него:
1. Клонируйте репозиторий:   


git clone https://github.com/0wn3dg0d/stelicas.git
		   cd stelicas

2. Установите зависимости:   


npm install

3. Запустите приложение:   


npm start
Скрытый текст

Использование

1. Запустите Stelicas:
   После запуска Stelicas.exe или выполнения npm start откроется окно приложения Stelicas.

2. Введите ваш Account ID (Steam 3 ID):
   - Ваш Steam3 ID - это уникальный идентификатор вашего аккаунта Steam. Обычно он имеет формат `U:1:XXXXXXX` (используйте часть `XXXXXXX`).
   - Этот ID соответствует имени папки в вашем каталоге Steam `userdata`, который обычно находится по адресу:     


{Steam folder}\userdata

Каждая папка в этом каталоге представляет собой другой аккаунт Steam, и имя папки - это часть `XXXXXXX` вашего Steam3 ID.
   - Если вы не знаете свой Steam3 ID, Stelicas может автоматически обнаружить его из вашей установки Steam. Нажмите на любой из обнаруженных ID, чтобы заполнить поле.

0WOrDBj.png

3. Выберите язык, валюту страны и поддерживаемый язык:
   - Язык: Выберите язык, на котором вы хотите получить информацию об играх (например, описания, названия). Если игра имеет перевод для выбранного языка, данные будут отображаться на этом языке.
   - Валюта страны: Выберите валюту страны. Рекомендуется оставить это как US Dollar, так как игры в других валютах могут быть недоступны в определенных регионах, что может привести к отсутствию данных.
   - Поддерживаемый язык: Этот параметр указывает, поддерживает ли игра выбранный язык. Результат отображается в формате {true;false;true} и т.д., где:

  • Первое значение указывает, есть ли у игры перевод интерфейса.
  • Второе значение указывает, есть ли у игры полный аудиоперевод.
  • Третье значение указывает, есть ли у игры перевод субтитров.
  • Если результат `{TRUE}`, это означает, что игра полностью поддерживает выбранный язык (все значения `true`).
  • Если результат `{FALSE}`, это означает, что игра не поддерживает выбранный язык вообще (все значения `false`).

JgPebA6.png

4. Запустите процесс:
   - Нажмите кнопку Start, чтобы начать извлечение категорий вашей библиотеки и получение информации об играх.
   - Прогресс-бар покажет статус процесса.

5. Просмотрите выходные данные:
   - После завершения процесса данные будут сохранены в папке output.

Скрытый текст

Выходные данные
Выходные данные состоят из двух CSV-файлов:

1. `id_categories.csv`: Этот файл содержит сопоставление идентификаторов игр с их связанными категориями. Он полезен, если вам нужна только информация о категориях без дополнительных данных об играх.

   Пример id_categories.csv:
Kbl8ZXF.png

2. `final_data.csv`: Этот файл содержит полный набор данных, включая подробную информацию об играх, такую как название, теги, дата выпуска, отзывы и многое другое. Ниже приведено подробное описание столбцов в этом файле.

Столбцы в final_data.csv

  • game_id: Уникальный идентификатор Steam игры.
  • name: Название игры.
  • categories: Пользовательские категории, которые вы назначили игре в вашей библиотеке Steam.
  • type: Тип игры (например, игра, DLC и т.д.).
  • tags: Теги, связанные с игрой.
  • release_date: Дата выпуска игры.
  • review_percentage: Процент положительных отзывов.
  • review_count: Общее количество отзывов.
  • is_free: Указывает, является ли игра бесплатной.
  • is_early_access: Указывает, находится ли игра в раннем доступе.
  • publishers: Издатели игры.
  • developers: Разработчики игры.
  • franchises: Франшизы, связанные с игрой.
  • short_description: Краткое описание игры.
  • supported_language: Указывает, поддерживает ли игра выбранный вами язык.
  • Steam-Link: Ссылка на страницу игры в Steam Community (не в магазине Steam). Это сделано намеренно, так как некоторые игры могут быть удалены из магазина Steam, но их страницы всё ещё доступны через Community. Использование ссылки на Community гарантирует, что вы сможете перейти на страницу любой игры, даже если она больше не доступна в магазине.
  • Pic: Ссылка на изображение заголовка игры.

Пример выходных данных:

14Ms9Je.png

Скрытый текст

FAQ

  • Почему Stelicas требует, чтобы клиент Steam был закрыт?

Клиент Steam блокирует базу данных `leveldb`, пока он запущен. Чтобы получить доступ к базе данных и извлечь ваши категории библиотеки, клиент Steam должен быть закрыт. Stelicas включает встроенную проверку, чтобы убедиться, что Steam не запущен, прежде чем продолжить.

  • Будет ли Stelicas работать, если на компьютере несколько аккаунтов Steam?

В настоящее время этот сценарий не тестировался. Теоретически, Stelicas должен работать с несколькими аккаунтами, так как он полагается на Steam3 ID для идентификации правильных пользовательских данных. Однако, поскольку это не проверено на практике, я не могу гарантировать его функциональность в таких случаях. Если у вас несколько аккаунтов, я рекомендую протестировать и предоставить отзыв.

  • Почему игры без категорий не включены в выходные данные?

Stelicas разработан для извлечения и организации игр на основе категорий, которые вы назначили в вашей библиотеке Steam. Если игра не назначена ни к одной категории, она не появится в выходных данных. Это связано с тем, что инструмент сосредоточен на категоризации и анализе игр, которые были явно организованы пользователем. Если вы хотите включить игры без категорий, вы можете вручную назначить их категории в вашей библиотеке Steam и перезапустить инструмент.

  • Почему изменения в категориях игр не сразу появляются в выходных данных?

База данных Steam `leveldb`, которую использует Stelicas для извлечения информации о категориях, может не сразу отражать изменения, внесенные в ваши категории библиотеки. Это может происходить по нескольким причинам:

  • Кэширование базы данных: Steam может кэшировать данные категорий для оптимизации производительности, что означает, что изменения могут быть записаны в базу данных не сразу.
  • Задержка синхронизации: Steam может задерживать синхронизацию изменений с локальной базой данных, особенно если клиент находится под высокой нагрузкой или если есть задержки, связанные с сетью.
  • Исторические данные: База данных `leveldb` может сохранять исторические или избыточные данные в течение определенного периода, даже после удаления или изменения категорий.

Хотя такое поведение может быть разочаровывающим, важно отметить, что база данных leveldb по-прежнему является самым актуальным и надежным источником информации о категориях по сравнению с устаревшим файлом sharedconfig.vdf. Если изменения не появляются сразу, попробуйте перезапустить Steam или подождать несколько минут перед повторным запуском Stelicas.

Если проблема сохраняется, это может быть связано с тем, как Steam внутренне управляет своей базой данных, и мало что можно сделать со стороны инструмента.

 

Изменено пользователем 0wn3df1x
  • Лайк (+1) 1

Поделиться сообщением


Ссылка на сообщение

Обновление: Steam Reviews Extender & Ru Indicator  || Tamper Monkey

Суть обновления:

  • Добавлены индикаторы наличия русского языка.
    Сейчас, чтобы узнать о наличии текстового перевода или озвучки приходится листать страницу игры вниз. Если вы открываете много страниц — этот скроллинг начинает надоедать. Поэтому я решил добавить индикаторы на видное место. Если в игре есть какой-то вид перевода, то индикатор горит ярко. Если нет, то индикатор тёмный. Всё интуитивно понятно. А если нет — при наведении курсора на любой индикатор всплывет подсказка. Порядок:
    • Интерфейс
    • Озвучка
    • Субтитры
       
  • Добавлена кнопка “Загрузить” доп. обзоры.
    В прошлой версии скрипта была проблема: когда вы заходили на страницу любой игры, скрипт отправлял по 3 запроса на получение дополнительных обзоров независимо от того, нужны они вам или нет. Теперь скрипт показывает обзоры только тогда, когда вы этого хотите — достаточно нажать кнопку “загрузить” и он выведет все три дополнительные категории.
     

dgrLRAW.png
 

Скрытый текст

Описание:
Система обзоров в Steam проверена временем, но бывают ситуации, в которых она создаёт искажённое представление о продукте.

К примеру, иногда подрядчик, осуществляющий локализацию на китайский язык, делает свою работу недобросовестно. И в этом случае игроки из Китая закономерно оставляют отрицательные обзоры. Китайская популяция огромна и настолько же огромная китайская аудитория в Steam. Отрицательные обзоры от настолько огромной аудитории могут уронить рейтинг любого проекта. Как результат — игра хорошая, а обзоры к ней смешанные. И многие потребители, видя смешанные обзоры, просто закрывают страницу. Обратная ситуация связана с играми, ориентированными на китайский рынок. Вы заходите на их страницу, видите огромное количество положительных обзоров, но без изучения этих обзоров вы не сможете понять, что большинство из них оставили игроки из Китая и игра интересна преимущественно китайским пользователям.

Ещё одна проблема — во избежание злоупотреблений со стороны издателей и разработчиков, система обзоров не учитывает обзоры активировавших игру ключом. Но ведь иногда активировать игру ключом дешевле. Плюс, учитывая ситуацию последних лет, ключи остаются одним из редких вариантов активации определённых игр в России.

Третья проблема — отсутствие статистики по обзорам на языке, который использует потребитель, хотя они важны.
К примеру:

  • Иногда те же отечественные игры непонятны покупателям вне стран СНГ, либо в этих игра плохая локализация на какой-то из европейских языков. И в таком случае иностранцы оставляют отрицательные обзоры, в то время как русскоязычные в восторге от игры.
  • Это же касается и жанровых предпочтений. Ряд игр нравится русскоязычным больше, чем игрокам из Азии, Европы или США. 
  • Либо наоборот — игра имеет хорошие обзоры, но в ней просто отвратительная русская локализация. Тем не менее, количество русских обзоров не настолько велико, чтобы повлиять на рейтинг игры. И без изучения обзоров вы не сразу поймёте, что с игрой что-то не так.

Мой скрипт призван решить все перечисленные проблемы.
Поэтому он добавляет три новых вида обзоров в описание игр:

  • Тотальные обзоры — Все обзоры с учётом активаций ключами.
  • Безкитайские обзоры — Все обзоры с вычетом китайских.
  • Русские обзоры — Только русские обзоры.
    • При щелчке по русским обзорам выводится модальное окно с 20-ю самыми полезными обзорами.
      Внутри модального окна есть кнопка для показа 20-ти самых актуальных обзоров.

spkDPBA.png

Если какие-то из обзоров вам не нужны, то вы можете заменить True на False в 17-19 строках кода.


    // Настройки для включения/отключения обзоров
    const settings = {
        showTotalReviews: true, // Показывать тотальные обзоры
        showNonChineseReviews: true, // Показывать безкитайские обзоры
        showRussianReviews: true // Показывать русские обзоры
    };


Сейчас, при щелчке по русским обзорам, вы получаете данные о самых полезных обзорах за всё время.
Поскольку в функции: 


    // Функция для отправки запроса и получения HTML-кода обзоров
    function fetchRussianReviewsHTML(appid, filter, callback) {
        let url = `https://store.steampowered.com/appreviews/${appid}?language=russian&purchase_type=all&filter=${filter}&day_range=9223372036854775807`;
        GM_xmlhttpRequest({
            method: "GET",
            url: url,
            onload: function(response) {
                let data = JSON.parse(response.responseText);
                callback(data.html); // Извлекаем HTML-код из ответа
            }
        });
    }

day_range выставлен на:


&day_range=9223372036854775807


9223372036854775807 — это максимально возможное значение для 64-битного целого числа со знаком (signed 64-bit integer), которое часто используется в программировании как своеобразный маркер "бесконечности".  В данном случае это значение позволяет получить данные, которые не ограничиваются ни днями, ни месяцами, а охватывают всю доступную историю.

Если вы хотите получить самые полезные обзоры за год, просто измените его на 365.


&day_range=365

Или любое другое нужное вам число.

----
Обновление от 24.12.2024:

Суть обновления: Теперь при щелчке по статистике русских обзоров открывается модальное окно с 20-ю самыми полезными обзорами на русском. Внутри модального окна есть кнопка “показать актуальные”, чтобы показать 20 последних обзоров.

Скрытый текст

bYDOPYY.gif


Обновление от 31.01.2025:

Суть обновления:

  • Добавлены индикаторы наличия русского языка.
    Сейчас, чтобы узнать о наличии текстового перевода или озвучки приходится листать страницу игры вниз. Если вы открываете много страниц — этот скроллинг начинает надоедать. Поэтому я решил добавить индикаторы на видное место.

    Если в игре есть какой-то вид перевода, то индикатор горит ярко. Если нет, то индикатор тёмный.
    Всё интуитивно понятно. А если нет — при наведении курсора на любой индикатор всплывет подсказка. Порядок такой:
    • Интерфейс
    • Озвучка
    • Субтитры
       
  • Добавлена кнопка “Загрузить” дополнительные обзоры.
    В прошлой версии скрипта была проблема: когда вы заходили на страницу любой игры, скрипт отправлял по 3 запроса на получение дополнительных обзоров независимо от того, нужны они вам или нет. Теперь скрипт показывает обзоры только тогда, когда вы этого хотите — достаточно нажать кнопку “загрузить” и он выведет все три дополнительные категории.
     

dgrLRAW.png

Скрытый текст

// ==UserScript==
// @name         Steam Reviews Extender & Ru Indicator
// @namespace    https://store.steampowered.com/
// @version      3.0
// @description  Расширяет обзоры и добавляет значки о наличии русского языка на странице игры в Steam.
// @author       0wn3df1x
// @match        https://store.steampowered.com/app/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// ==/UserScript==

(function () {
    'use strict';

    // ==== Часть 1: Индикатор русского языка ====

    function createFruitIndicator(apple, hasSupport, orange) {
        const banana = document.createElement('div');
        banana.style.position = 'relative';
        banana.style.cursor = 'pointer';

        const grape = document.createElement('div');
        grape.style.width = '60px';
        grape.style.height = '60px';
        grape.style.borderRadius = '4px';
        grape.style.display = 'flex';
        grape.style.alignItems = 'center';
        grape.style.justifyContent = 'center';
        grape.style.background = hasSupport ? 'rgba(66, 135, 245, 0.2)' : 'rgba(0, 0, 0, 0.1)';
        grape.style.border = `1px solid ${hasSupport ? '#2A5891' : '#3c3c3c'}`;
        grape.style.opacity = '0.95';
        grape.style.transition = 'transform 0.3s ease, box-shadow 0.3s ease';
        grape.style.overflow = 'hidden';
        grape.style.position = 'relative';
        grape.style.transform = 'translateZ(0)';

        const kiwi = document.createElement('div');
        kiwi.innerHTML = apple;
        kiwi.style.width = '30px';
        kiwi.style.height = '30px';
        kiwi.style.display = 'block';
        kiwi.style.margin = '0 auto';
        kiwi.style.transition = 'fill 0.3s ease';

        grape.appendChild(kiwi);

        const svgElement = kiwi.querySelector('svg');

        function setColor(hasSupport) {
            const borderColor = hasSupport ? '#2A5891' : '#3c3c3c';
            const svgFill = hasSupport ? '#FFFFFF' : '#0E1C25';

            grape.style.border = `1px solid ${borderColor}`;
            svgElement.style.fill = svgFill;
        }

        setColor(hasSupport);

        const pineapple = document.createElement('div');
        const hasLabel = hasSupport ? orange : getGenitiveCase(orange);
        pineapple.textContent = hasSupport ? `Есть ${orange}` : `Нет ${hasLabel}`;
        pineapple.style.position = 'absolute';
        pineapple.style.top = '50%';
        pineapple.style.left = '100%';
        pineapple.style.transform = 'translateY(-50%) translateX(10px)';
        pineapple.style.background = 'rgba(0, 0, 0, 0.8)';
        pineapple.style.color = '#fff';
        pineapple.style.padding = '8px 12px';
        pineapple.style.borderRadius = '8px';
        pineapple.style.fontSize = '14px';
        pineapple.style.whiteSpace = 'nowrap';
        pineapple.style.opacity = '0';
        pineapple.style.transition = 'opacity 0.3s ease';
        pineapple.style.zIndex = '10000';
        pineapple.style.pointerEvents = 'none';
        banana.appendChild(pineapple);

        banana.addEventListener('mouseenter', () => {
            grape.style.transform = 'scale(1.1) translateZ(0)';
            pineapple.style.opacity = '1';
        });

        banana.addEventListener('mouseleave', () => {
            grape.style.transform = 'scale(1) translateZ(0)';
            pineapple.style.opacity = '0';
        });

        banana.appendChild(grape);
        return banana;
    }

    function getGenitiveCase(orange) {
        switch (orange) {
            case 'интерфейс': return 'интерфейса';
            case 'озвучка': return 'озвучки';
            case 'субтитры': return 'субтитров';
            default: return orange;
        }
    }

    function checkRussianSupport() {
        const mango = document.querySelector('#languageTable table.game_language_options');
        if (!mango) return { interface: false, voice: false, subtitles: false };

        const strawberry = mango.querySelectorAll('tr');
        for (let blueberry of strawberry) {
            const watermelon = blueberry.querySelector('td.ellipsis');
            if (watermelon && /русский|Russian/i.test(watermelon.textContent.trim())) {
                const cherry = blueberry.querySelector('td.checkcol:nth-child(2) span');
                const raspberry = blueberry.querySelector('td.checkcol:nth-child(3) span');
                const blackberry = blueberry.querySelector('td.checkcol:nth-child(4) span');

                return {
                    interface: cherry !== null,
                    voice: raspberry !== null,
                    subtitles: blackberry !== null
                };
            }
        }
        return { interface: false, voice: false, subtitles: false };
    }

    function addRussianIndicators() {
        const russianSupport = checkRussianSupport();
        if (!russianSupport) return;

        let lemon = document.querySelector('#gameHeaderImageCtn');
        if (!lemon) return;

        const lime = document.createElement('div');
        lime.style.position = 'absolute';
        lime.style.top = '-10px';
        lime.style.left = 'calc(100% + 10px)';
        lime.style.display = 'flex';
        lime.style.flexDirection = 'column';
        lime.style.gap = '15px';
        lime.style.alignItems = 'flex-start';
        lime.style.zIndex = '1002';
        lime.style.marginTop = '10px';

        const peach = createFruitIndicator(`<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12,0C5.38,0,0,5.38,0,12s5.38,12,12,12s12-5.38,12-12S18.62,0,12,0z M12,22C6.49,22,2,17.51,2,12S6.49,2,12,2	s10,4.49,10,10S17.51,22,12,22z M10.5,10h3v8h-3V10z M10.5,5h3v3h-3V5z" /></svg>`, russianSupport.interface, 'интерфейс');
        const plum = createFruitIndicator(`<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M15,21v-2c3.86,0,7-3.14,7-7s-3.14-7-7-7V3c4.96,0,9,4.04,9,9S19.96,21,15,21z M15,17v-2c1.65,0,3-1.35,3-3s-1.35-3-3-3V7 c2.76,0,5,2.24,5,5S17.76,17,15,17z M1,12v4h5l6,5V3L6,8H1V12" /></svg>`, russianSupport.voice, 'озвучка');
        const apricot = createFruitIndicator(`<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g><path d="M11,24l-4.4-5H0V0h23v19h-7.6L11,24z M2,17h5.4l3.6,4l3.6-4H21V2H2V17z" /></g><g><rect x="5" y="8" width="3" height="3" /></g><g><rect x="10" y="8" width="3" height="3" /></g><g><rect x="15" y="8" width="3" height="3" /></g></svg>`, russianSupport.subtitles, 'субтитры');

        lime.appendChild(peach);
        lime.appendChild(plum);
        lime.appendChild(apricot);

        lemon.style.position = 'relative';
        lemon.appendChild(lime);

        const appName = document.querySelector('#appHubAppName.apphub_AppName');
        if (appName) {
            appName.style.maxWidth = '530px';
            appName.style.overflow = 'hidden';
            appName.style.textOverflow = 'ellipsis';
            appName.style.whiteSpace = 'nowrap';
            appName.title = appName.textContent;
        }
    }

    // ==== Часть 2: Расширение обзоров ====

    const settings = {
        showTotalReviews: true,
        showNonChineseReviews: true,
        showRussianReviews: true
    };

    function fetchReviews(appid, language, callback) {
        let url = `https://store.steampowered.com/appreviews/${appid}?json=1&language=${language}&purchase_type=all`;
        GM_xmlhttpRequest({
            method: "GET",
            url: url,
            onload: function (response) {
                let data = JSON.parse(response.responseText);
                callback(data);
            }
        });
    }

    function fetchRussianReviewsHTML(appid, filter, callback) {
        let url = `https://store.steampowered.com/appreviews/${appid}?language=russian&purchase_type=all&filter=${filter}&day_range=365`;
        GM_xmlhttpRequest({
            method: "GET",
            url: url,
            onload: function (response) {
                let data = JSON.parse(response.responseText);
                callback(data.html);
            }
        });
    }

    function addStyles() {
        GM_addStyle(`
            .additional-reviews {
                margin-top: 10px;
            }
            .additional-reviews .user_reviews_summary_row {
                display: flex;
                line-height: 16px;
                cursor: pointer;
                margin-bottom: 5px;
            }
            .additional-reviews .subtitle {
                flex: 1;
                color: #556772;
                font-size: 12px;
            }
            .additional-reviews .summary {
                flex: 3;
                color: #c6d4df;
                font-size: 12px;
                overflow: hidden;
                white-space: nowrap;
                text-overflow: ellipsis;
            }
            .additional-reviews .game_review_summary {
                font-weight: normal;
            }
            .additional-reviews .positive {
                color: #66c0f4;
            }
            .additional-reviews .mixed {
                color: #B9A074;
            }
            .additional-reviews .negative {
                color: #a34c25;
            }
            .additional-reviews .no_reviews {
                color: #929396;
            }
            .additional-reviews .responsive_hidden {
                color: #556772;
                margin-left: 5px;
            }
            .modal {
                display: none;
                position: fixed;
                z-index: 1000;
                left: 0;
                top: 0;
                width: 100%;
                height: 100%;
                overflow: auto;
                background-color: rgba(0,0,0,0.8);
            }
            .modal-content {
                background-color: #1b2838;
                margin: 10% auto;
                padding: 20px;
                border: 1px solid #888;
                width: 80%;
                max-width: 800px;
                color: #c6d4df;
                position: relative;
                max-height: 80vh;
                overflow-y: auto;
            }
            .close {
                color: #aaa;
                position: sticky;
                top: 0;
                float: right;
                font-size: 28px;
                font-weight: bold;
                cursor: pointer;
                background: rgba(0,0,0,0.8);
                padding: 5px 10px;
                border-radius: 5px;
                transition: color 0.2s ease, background 0.2s ease, transform 0.2s ease;
                box-shadow: 0 2px 4px rgba(0,0,0,0.2);
            }
            .close:hover {
                color: #fff;
                background: #e64a4a;
                transform: scale(1.1);
            }
            .close:active {
                background: #c43c3c;
                transform: scale(0.95);
            }
            .refresh-button {
                position: left;
                top: 10px;
                right: 50px;
                background: #66c0f4;
                color: #1b2838;
                padding: 10px 20px;
                border: none;
                cursor: pointer;
                z-index: 1001;
                border-radius: 2px;
                transition: background 0.2s ease, color 0.2s ease;
            }
            .refresh-button:hover {
                background: #45b0e6;
                color: #fff;
            }
            .refresh-button:active {
                background: #329cd4;
                transform: translateY(1px);
            }
        `);
    }

    function formatNumber(number) {
        return number.toLocaleString('en-US');
    }

    function getReviewClass(percent, totalReviews) {
        if (totalReviews === 0) return 'no_reviews';
        if (percent >= 70) return 'positive';
        if (percent >= 40) return 'mixed';
        if (percent >= 1) return 'negative';
        return 'negative';
    }

    function addLoadButton() {
        let reviewsContainer = document.querySelector('.user_reviews');
        if (reviewsContainer) {
            let additionalReviews = document.createElement('div');
            additionalReviews.className = 'additional-reviews';

            additionalReviews.innerHTML = `
                <div class="user_reviews_summary_row" id="load-reviews-button">
                    <div class="subtitle column all">Доп. обзоры:</div>
                    <div class="summary column">
                        <span class="game_review_summary no_reviews">Загрузить</span>
                    </div>
                </div>
            `;

            reviewsContainer.appendChild(additionalReviews);

            document.getElementById('load-reviews-button').addEventListener('click', function () {
                loadAdditionalReviews();
            });
        }
    }

    function loadAdditionalReviews() {
        let appid = window.location.pathname.match(/\/app\/(\d+)/)[1];
        let languages = [];
        let data = {};

        if (settings.showTotalReviews || settings.showNonChineseReviews) {
            languages.push('all');
        }
        if (settings.showNonChineseReviews) {
            languages.push('schinese');
        }
        if (settings.showRussianReviews) {
            languages.push('russian');
        }

        languages.forEach(language => {
            fetchReviews(appid, language, (response) => {
                data[language] = response;
                if (Object.keys(data).length === languages.length) {
                    displayAdditionalReviews(data['all'], data['schinese'], data['russian']);
                }
            });
        });
    }

    function displayAdditionalReviews(allData, schineseData, russianData) {
        let allReviews = allData ? allData.query_summary : null;
        let schineseReviews = schineseData ? schineseData.query_summary : null;
        let russianReviews = russianData ? russianData.query_summary : null;

        let additionalReviews = document.querySelector('.additional-reviews');
        if (additionalReviews) {
            additionalReviews.innerHTML = '';

            if (settings.showTotalReviews && allReviews) {
                let allPercent = allReviews.total_reviews > 0 ? Math.round((allReviews.total_positive / allReviews.total_reviews) * 100) : 0;
                let allClass = getReviewClass(allPercent, allReviews.total_reviews);
                additionalReviews.innerHTML += `
                    <div class="user_reviews_summary_row">
                        <div class="subtitle column all">Тотальные:</div>
                        <div class="summary column">
                            <span class="game_review_summary ${allClass}">${allPercent}% из ${formatNumber(allReviews.total_reviews)} положительные</span>
                        </div>
                    </div>
                `;
            }

            if (settings.showNonChineseReviews && allReviews && schineseReviews) {
                let schintotalrev = allReviews.total_reviews - schineseReviews.total_reviews;
                let schintotapos = allReviews.total_positive - schineseReviews.total_positive;
                let schinpercent = schintotalrev > 0 ? Math.round((schintotapos / schintotalrev) * 100) : 0;
                let schinClass = getReviewClass(schinpercent, schintotalrev);
                additionalReviews.innerHTML += `
                    <div class="user_reviews_summary_row">
                        <div class="subtitle column all">Безкитайские:</div>
                        <div class="summary column">
                            <span class="game_review_summary ${schinClass}">${schinpercent}% из ${formatNumber(schintotalrev)} положительные</span>
                        </div>
                    </div>
                `;
            }

            if (settings.showRussianReviews && russianReviews) {
                let rustotalrev = russianReviews.total_reviews;
                let ruspositive = russianReviews.total_positive;
                let ruspercent = rustotalrev > 0 ? Math.round((ruspositive / rustotalrev) * 100) : 0;
                let rusClass = getReviewClass(ruspercent, rustotalrev);
                additionalReviews.innerHTML += `
                    <div class="user_reviews_summary_row" id="russian-reviews-row">
                        <div class="subtitle column all">Русские:</div>
                        <div class="summary column">
                            <span class="game_review_summary ${rusClass}">${ruspercent}% из ${formatNumber(rustotalrev)} положительные</span>
                        </div>
                    </div>
                `;

                document.getElementById('russian-reviews-row').addEventListener('click', function () {
                    openModal();
                });
            }
        }
    }

    function openModal() {
        let modal = document.createElement('div');
        modal.className = 'modal';
        modal.innerHTML = `
            <div class="modal-content">
                <span class="close">&times;</span>
                <button class="refresh-button" id="refresh-reviews">Загрузить актуальные</button>
                <div id="reviews-container"></div>
            </div>
        `;
        document.body.appendChild(modal);

        modal.querySelector('.close').addEventListener('click', function () {
            modal.style.display = 'none';
        });

        modal.querySelector('#refresh-reviews').addEventListener('click', function () {
            refreshReviews(modal);
        });

        modal.style.display = 'block';

        loadReviews(modal, 'all');
    }

    function refreshReviews(modal) {
        modal.querySelector('#reviews-container').innerHTML = '';
        loadReviews(modal, 'recent');
    }

    function loadReviews(modal, filter) {
        fetchRussianReviewsHTML(window.location.pathname.match(/\/app\/(\d+)/)[1], filter, function (html) {
            modal.querySelector('#reviews-container').innerHTML = html;
            modal.querySelector('#LoadMoreReviewsall')?.remove();
            modal.querySelector('#LoadMoreReviewsrecent')?.remove();
        });
    }

    // ==== Основная функция ====

    function main() {
        addStyles();
        addRussianIndicators();
        addLoadButton();
    }

    main();
})();

 

 

Изменено пользователем 0wn3df1x
  • Лайк (+1) 1

Поделиться сообщением


Ссылка на сообщение

Steam Store - Информатор || Tamper Monkey — функционал перемещён в Ultimate Steam Enhancer.

Скрытый текст

 

Steam Store - Информатор  || Tamper Monkey

Описание:

Во время поиска по каталогу Steam количество выдаваемой информации ограничено. Изначально вы видите изображение, название, дату выхода, индикатор качества обзоров и цену. 

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

Мой скрипт добавляет ещё одну подсказку слева, в которой отображает:

  • Издателей
  • Разработчиков
  • Франшизу (серию игр)
  • Фильтрованные отзывы (просеянные алгоритмом Steam, без учёта аномальных).
  • Информацию о том, находится ли игра в раннем доступе
  • Информацию о переводе игры на русский язык (указывает, есть ли перевод интерфейса, озвучки и субтитров).
  • [Информацию об английском языке, в версии для переводчиков, под вторым спойлером]
  • Короткое описание игры.

krLCGPz.jpeg

 

Скрытый текст

1. Инициализация:

  • Скрипт запускается с задержкой в 2 секунды (WAIT_TIME = 2000), чтобы страница успела загрузиться.
  • После задержки начинается сбор appId всех видимых игр на странице.

2. Сбор данных:

  • Скрипт ищет все элементы с классом a.search_result_row[data-ds-appid], которые представляют игры в каталоге Steam.
  • Для каждого найденного элемента извлекается appId и добавляется в коллекцию collectedAppIds, если он ещё не был добавлен.

3. Запрос данных:

  • Если найдены новые appId, скрипт отправляет запрос к API Steam (IStoreBrowseService/GetItems/v1) для получения дополнительной информации о играх.

4. Обработка данных:

  • Полученные данные обрабатываются и сохраняются в атрибуте data-game-info соответствующего элемента игры.

5. Отображение подсказки:

  • При наведении на элемент игры запускается таймер hoverTimer, который через 300 мс отображает кастомную подсказку слева от элемента.
  • Подсказка содержит всю собранную информацию о игре, включая издателей, разработчиков, франшизу, отзывы, ранний доступ, поддержку русского языка и краткое описание.

6. Динамическая подгрузка:

  • Скрипт использует MutationObserver для отслеживания изменений в DOM (например, при прокрутке страницы и подгрузке новых игр).
  • При обнаружении новых элементов игры, скрипт запускает процесс сбора appId из них для запроса данных.
Скрытый текст



// ==UserScript==
// @name         Steam Store - Информатор
// @namespace    https://store.steampowered.com/
// @version      0.5
// @description  Выводит дополнительную информацию об играх в поиске по каталогу
// @author       You
// @match        https://store.steampowered.com/search/*
// @grant        GM_xmlhttpRequest
// ==/UserScript==

(function() {
    'use strict';

    const API_URL = "https://api.steampowered.com/IStoreBrowseService/GetItems/v1";
    const WAIT_TIME = 2000; // 2 seconds
    const VISIBLE_ELEMENTS_SELECTOR = "a.search_result_row[data-ds-appid]";
    const HOVER_ELEMENT_SELECTOR = "a.search_result_row";

    let collectedAppIds = new Set();
    let tooltip = null;
    let hoverTimer = null;
    let hideTimer = null;

    function fetchGameData(appIds) {
        const inputJson = {
            ids: Array.from(appIds).map(appid => ({ appid })),
            context: { language: "russian", country_code: "US", steam_realm: 1 },
            data_request: {
                include_assets: true,
                include_release: true,
                include_platforms: true,
                include_all_purchase_options: true,
                include_screenshots: true,
                include_trailers: true,
                include_ratings: true,
                include_tag_count: true,
                include_reviews: true,
                include_basic_info: true,
                include_supported_languages: true,
                include_full_description: true,
                include_included_items: true,
                included_item_data_request: {
                    include_assets: true,
                    include_release: true,
                    include_platforms: true,
                    include_all_purchase_options: true,
                    include_screenshots: true,
                    include_trailers: true,
                    include_ratings: true,
                    include_tag_count: true,
                    include_reviews: true,
                    include_basic_info: true,
                    include_supported_languages: true,
                    include_full_description: true,
                    include_included_items: true,
                    include_assets_without_overrides: true,
                    apply_user_filters: false,
                    include_links: true
                },
                include_assets_without_overrides: true,
                apply_user_filters: false,
                include_links: true
            }
        };

        GM_xmlhttpRequest({
            method: "GET",
            url: `${API_URL}?input_json=${encodeURIComponent(JSON.stringify(inputJson))}`,
            onload: function(response) {
                const data = JSON.parse(response.responseText);
                processGameData(data);
            }
        });
    }

    function processGameData(data) {
        const items = data.response.store_items;
        items.forEach(item => {
            const appId = item.id;
            const gameElement = document.querySelector(`a.search_result_row[data-ds-appid="${appId}"]`);
            if (gameElement) {
                const gameData = {
                    is_early_access: item.is_early_access,
                    review_count: item.reviews?.summary_filtered?.review_count,
                    percent_positive: item.reviews?.summary_filtered?.percent_positive,
                    short_description: item.basic_info?.short_description,
                    publishers: item.basic_info?.publishers?.map(p => p.name).join(", "),
                    developers: item.basic_info?.developers?.map(d => d.name).join(", "),
                    franchises: item.basic_info?.franchises?.map(f => f.name).join(", "),
                    language_support: item.supported_languages?.find(lang => lang.elanguage === 8)
                };

                gameElement.dataset.gameInfo = JSON.stringify(gameData);
            }
        });
    }

    function collectAndFetchAppIds() {
        const visibleElements = document.querySelectorAll(VISIBLE_ELEMENTS_SELECTOR);
        const newAppIds = new Set();

        visibleElements.forEach(element => {
            const appId = element.dataset.dsAppid;
            if (!collectedAppIds.has(appId)) {
                newAppIds.add(parseInt(appId, 10));
                collectedAppIds.add(appId);
            }
        });

        if (newAppIds.size > 0) {
            fetchGameData(newAppIds);
        }
    }

    function handleHover(event) {
        const gameElement = event.target.closest(HOVER_ELEMENT_SELECTOR);

        if (gameElement && gameElement.dataset.gameInfo) {
            clearTimeout(hoverTimer);
            clearTimeout(hideTimer);

            hoverTimer = setTimeout(() => {
                const gameData = JSON.parse(gameElement.dataset.gameInfo);
                displayGameInfo(gameElement, gameData);
            }, 300);
        } else {
            clearTimeout(hoverTimer);
            clearTimeout(hideTimer);
            if (tooltip) {
                tooltip.style.opacity = 0;
                setTimeout(() => {
                    tooltip.style.display = 'none';
                }, 300);
            }
        }
    }

    function displayGameInfo(element, data) {
        if (!tooltip) {
            tooltip = document.createElement('div');
            tooltip.className = 'custom-tooltip';
            tooltip.innerHTML = '<div class="tooltip-arrow"></div><div class="tooltip-content"></div>';
            document.body.appendChild(tooltip);
        }

        const tooltipContent = tooltip.querySelector('.tooltip-content');

        let languageSupportText = "Отсутствует";
        if (data.language_support) {
            languageSupportText = "";
            if (data.language_support.supported) languageSupportText += "<br>Интерфейс: ✔ ";
            if (data.language_support.full_audio) languageSupportText += "<br>Озвучка: ✔ ";
            if (data.language_support.subtitles) languageSupportText += "<br>Субтитры: ✔";
            if (languageSupportText === "") languageSupportText = "Отсутствует";
        }

        tooltipContent.innerHTML = `
            <div style="margin-bottom: 0px;"><strong>Издатели:</strong> ${data.publishers || "Нет данных"}</div>
            <div style="margin-bottom: 0px;"><strong>Разработчики:</strong> ${data.developers || "Нет данных"}</div>
            <div style="margin-bottom: 10px;"><strong>Серия игр:</strong> ${data.franchises || "Нет данных"}</div>
            <div style="margin-bottom: 10px;"><strong>Отзывы:</strong> ${data.review_count || "0"} (${data.percent_positive || "0"}% положительных)</div>
            <div style="margin-bottom: 10px;"><strong>Ранний доступ:</strong> ${data.is_early_access ? "Да" : "Нет"}</div>
            <div style="margin-bottom: 10px;"><strong>Русский язык:</strong> ${languageSupportText}</div>
            <div style="margin-bottom: 10px;"><strong>Описание:</strong> ${data.short_description || "Нет данных"}</div>
        `;

        tooltip.style.display = 'block';

        const rect = element.getBoundingClientRect();
        const tooltipRect = tooltip.getBoundingClientRect();
        tooltip.style.left = `${rect.left + window.scrollX - tooltipRect.width - 4}px`;
        tooltip.style.top = `${rect.top + window.scrollY - 20}px`;

        tooltip.style.opacity = 0;
        tooltip.style.display = 'block';
        setTimeout(() => {
            tooltip.style.opacity = 1;
        }, 10);

        element.addEventListener('mouseleave', () => {
            clearTimeout(hideTimer);
            hideTimer = setTimeout(() => {
                tooltip.style.opacity = 0;
                setTimeout(() => {
                    tooltip.style.display = 'none';
                }, 300);
            }, 200);
        }, { once: true });

        element.addEventListener('mouseover', () => {
            clearTimeout(hideTimer);
        });
    }

    function observeNewElements() {
        const observer = new MutationObserver((mutations) => {
            mutations.forEach(mutation => {
                if (mutation.type === 'childList') {
                    collectAndFetchAppIds();
                }
            });
        });

        observer.observe(document.body, { childList: true, subtree: true });
    }

    function initialize() {
        setTimeout(() => {
            collectAndFetchAppIds();
            observeNewElements();
            document.addEventListener('mouseover', handleHover);
        }, WAIT_TIME);
    }

    initialize();

    const style = document.createElement('style');
    style.innerHTML = `
        .custom-tooltip {
            position: absolute;
            background: linear-gradient(to bottom, #e3eaef, #c7d5e0);
            color: #30455a;
            padding: 12px;
            border-radius: 0px;
            box-shadow: 0 0 12px #000;
            font-size: 12px;
            max-width: 300px;
            display: none;
            z-index: 1000;
            opacity: 0;
            transition: opacity 0.4s ease-in-out;
        }
        .tooltip-arrow {
            position: absolute;
            right: -9px;
            top: 32px;
            width: 0;
            height: 0;
            border-top: 10px solid transparent;
            border-bottom: 10px solid transparent;
            border-left: 10px solid #E1E8ED;
        }
    `;
    document.head.appendChild(style);
})();

 


 

Изменено пользователем 0wn3df1x
  • Лайк (+1) 1

Поделиться сообщением


Ссылка на сообщение

@0wn3df1x Блин, сделай уже один большой скрипт чтоб я как царь его поставил.

Мелкие тоже поставил, все страшно полезные. Удивительно, что большие аддоны типа Augmented, SIH и прочие всем этим пренебрегают.

  • Лайк (+1) 1
  • Хаха (+1) 1

Поделиться сообщением


Ссылка на сообщение

Создайте аккаунт или войдите в него для комментирования

Вы должны быть пользователем, чтобы оставить комментарий

Создать аккаунт

Зарегистрируйтесь для получения аккаунта. Это просто!

Зарегистрировать аккаунт

Войти

Уже зарегистрированы? Войдите здесь.

Войти сейчас



Zone of Games © 2003–2025 | Реклама на сайте.

×