NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name PSPrices Subscriptions Filter - Toggle Visibility of Unique, Rare and Common Deals
// @namespace https://psprices.com
// @version 0.74
// @description Toggle showing games during their Lowest or New Lowest deals, and filter Mass-release publisher games
// @author nascent
// @match https://psprices.com/my/subscriptions/*
// @match https://psprices.com/region-*/collection/*
// @match https://psprices.com/region-*/genre/*
// @match https://psprices.com/region-*/index
// @match https://psprices.com/my/wishlist/*
// @exclude *q=*
// @updateURL https://openuserjs.org/meta/nascent/PSPrices_Subscriptions_Filter_-_Toggle_Visibility_of_Unique,_Rare_and_Common_Deals.meta.js
// @downloadURL https://openuserjs.org/install/nascent/PSPrices_Subscriptions_Filter_-_Toggle_Visibility_of_Unique,_Rare_and_Common_Deals.user.js
// @license GPL-3.0-or-later
// @icon https://www.google.com/s2/favicons?domain=psprices.com
// @grant GM.setValue
// @grant GM.getValue
// ==/UserScript==
(function () {
'use strict';
let hideLowest = false;
let hideOtherDeals = false;
let hideMassRelease = false;
const MASS_RELEASE_SELECTOR = 'button[title="Mass-release publisher"]';
const LOWEST_BADGE_SELECTOR = '[data-test-id="lowest-flag"]';
const NEW_LOWEST_BADGE_SELECTOR = '[data-test-id="lowest-ever-flag"]';
const HIDE_LOWEST_CLASS = 'psprices-hidden-lowest';
const HIDE_MODERATE_CLASS = 'psprices-hidden-moderate';
const HIDE_MASS_RELEASE_CLASS = 'psprices-hidden-mass-release';
const style = document.createElement('style');
style.innerHTML = `
.${HIDE_LOWEST_CLASS},
.${HIDE_MODERATE_CLASS},
.${HIDE_MASS_RELEASE_CLASS} {
display: none !important;
}
.psprices-filter-btn {
transition: all 0.2s ease;
white-space: nowrap;
}
`;
document.head.appendChild(style);
function hasLowestBadge(div) {
return div.querySelector(LOWEST_BADGE_SELECTOR) !== null;
}
function hasNewLowestBadge(div) {
return div.querySelector(NEW_LOWEST_BADGE_SELECTOR) !== null;
}
function isSpecialDeal(div) {
return hasLowestBadge(div) || hasNewLowestBadge(div);
}
// Find the game card container
function getGameCard(element) {
let card = element.closest('div.game-fragment');
if (!card) {
card = element.closest('div[class*="col-span-"]');
}
return card;
}
// Climb up from the game-fragment to the outermost element that
// exclusively wraps this card (e.g. the <div class=""> grid cell on
// the index page). Hiding that element removes the blank gap.
function getHideTarget(card) {
let target = card;
while (
target.parentElement &&
target.parentElement !== document.body &&
target.parentElement.tagName === 'DIV' &&
target.parentElement.childElementCount === 1
) {
target = target.parentElement;
}
return target;
}
function setHidden(card, className, shouldHide) {
const target = getHideTarget(card);
target.classList.toggle(className, shouldHide);
}
function updateLowestVisibility() {
document.querySelectorAll('div.game-fragment').forEach(div => {
setHidden(div, HIDE_LOWEST_CLASS, hideLowest && hasLowestBadge(div));
});
}
function updateOtherDealsVisibility() {
document.querySelectorAll('div.game-fragment').forEach(div => {
setHidden(div, HIDE_MODERATE_CLASS, hideOtherDeals && !isSpecialDeal(div));
});
}
function updateMassReleaseVisibility() {
document.querySelectorAll('div.game-fragment').forEach(div => {
const isMassRelease = div.querySelector(MASS_RELEASE_SELECTOR) !== null;
setHidden(div, HIDE_MASS_RELEASE_CLASS, hideMassRelease && isMassRelease);
});
}
function updateButtonState(button, isActive) {
button.style.opacity = isActive ? '0.7' : '1';
button.style.filter = isActive ? 'grayscale(50%)' : 'none';
}
function createExpandButton(shortText, fullText, bgColor, top) {
const button = document.createElement('button');
button.innerHTML = shortText;
button.className = 'psprices-filter-btn';
button.style.position = 'fixed';
button.style.top = top;
button.style.right = '10px';
button.style.zIndex = '10000';
button.style.padding = '10px 15px';
button.style.backgroundColor = bgColor;
button.style.color = 'white';
button.style.border = 'none';
button.style.borderRadius = '5px';
button.style.cursor = 'pointer';
button.style.boxShadow = '0 2px 8px rgba(0,0,0,0.2)';
button.addEventListener('mouseenter', () => {
button.innerHTML = fullText;
});
button.addEventListener('mouseleave', () => {
button.innerHTML = shortText;
});
return button;
}
const lowestButton = createExpandButton('Lowest', 'Toggle Best Price Deals', '#4CAF50', '120px');
const moderateButton = createExpandButton('Moderate', 'Toggle Moderate Deals', '#f44336', '170px');
const massReleaseButton = createExpandButton('Mass Release', 'Toggle Mass-release Publisher', '#FF9800', '220px');
document.body.appendChild(lowestButton);
document.body.appendChild(moderateButton);
document.body.appendChild(massReleaseButton);
lowestButton.addEventListener('click', async () => {
hideLowest = !hideLowest;
await GM.setValue('hideLowest', hideLowest);
updateButtonState(lowestButton, hideLowest);
updateLowestVisibility();
});
moderateButton.addEventListener('click', async () => {
hideOtherDeals = !hideOtherDeals;
await GM.setValue('hideOtherDeals', hideOtherDeals);
updateButtonState(moderateButton, hideOtherDeals);
updateOtherDealsVisibility();
});
massReleaseButton.addEventListener('click', async () => {
hideMassRelease = !hideMassRelease;
await GM.setValue('hideMassRelease', hideMassRelease);
updateButtonState(massReleaseButton, hideMassRelease);
updateMassReleaseVisibility();
});
async function initialize() {
hideLowest = await GM.getValue('hideLowest', false);
hideOtherDeals = await GM.getValue('hideOtherDeals', false);
hideMassRelease = await GM.getValue('hideMassRelease', false);
updateButtonState(lowestButton, hideLowest);
updateButtonState(moderateButton, hideOtherDeals);
updateButtonState(massReleaseButton, hideMassRelease);
updateLowestVisibility();
updateOtherDealsVisibility();
updateMassReleaseVisibility();
}
initialize();
let timeout;
const observer = new MutationObserver((mutations) => {
const hasAddedNodes = mutations.some(m => m.addedNodes.length > 0);
if (hasAddedNodes) {
clearTimeout(timeout);
timeout = setTimeout(() => {
updateLowestVisibility();
updateOtherDealsVisibility();
updateMassReleaseVisibility();
}, 100);
}
});
observer.observe(document.body, { childList: true, subtree: true });
})();