// 馃憢 Hola, usa 馃悞Tampermonkey 馃憞 // https://www.tampermonkey.net/ // ==UserScript== // @name Xbox Store Price & Deals Filter // @name:es Filtros de Precio y Ofertas para Xbox Store // @name:it Filtri Prezzi e Offerte per Xbox Store // @name:fr Filtres de Prix et Offres pour Xbox Store // @name:de Preis- und Angebotsfilter f眉r Xbox Store // @namespace https://jlcareglio.github.io/ // @version 0.8.0 // @description Add price range filters and deal filters to Xbox store. Filter by Game Pass discounts, specific discount percentages, and price ranges. // @description:es Agrega filtros de rango de precios y ofertas a la tienda Xbox. Filtra por descuentos de Game Pass, porcentajes espec铆ficos de descuento y rangos de precios. // @description:it Aggiunge filtri per fascia di prezzo e offerte allo store Xbox. Filtra per sconti Game Pass, percentuali di sconto specifiche e fasce di prezzo. // @description:fr Ajoute des filtres de gamme de prix et d'offres au magasin Xbox. Filtre par r茅ductions Game Pass, pourcentages de r茅duction sp茅cifiques et gammes de prix. // @description:de F眉gt Preisbereich- und Angebotsfilter zum Xbox-Store hinzu. Filtert nach Game Pass-Rabatten, spezifischen Rabattprozenten und Preisbereichen. // @icon https://www.google.com/s2/favicons?sz=64&domain=xbox.com // @grant none // @author Jes煤s Lautaro Careglio Albornoz // @source https://gist.githubusercontent.com/JLCareglio/9cbddea558658f695983a64b9cece6a6/raw/ // @match https://www.xbox.com/*/games/all-games* // @match https://www.xbox.com/*/games/browse* // @supportURL https://gist.githubusercontent.com/JLCareglio/9cbddea558658f695983a64b9cece6a6/ // @downloadURL none // ==/UserScript== (async () => { // Funci贸n para esperar a que un elemento exista function waitForElement(selector) { return new Promise((resolve) => { if (document.querySelector(selector)) { return resolve(document.querySelector(selector)); } const observer = new MutationObserver(() => { if (document.querySelector(selector)) { observer.disconnect(); resolve(document.querySelector(selector)); } }); observer.observe(document.documentElement, { childList: true, subtree: true, }); }); } /* Significado de las distintas clases que pueden estar en alg煤n lugar dentro de una ProductCard de un juego: - "Price-module__afterPriceTextContainer___r7fdq": se puede comprar a un precio reducido si se tiene una suscripci贸n activa de gamePass - "ProductCard-module__discountTag___OjGFy" indica que tiene un descuento y el porcentaje esta en su innerText - "ProductCard-module__price___cs1xr" su innerText contiene el precio final en formato local, por ejemplo, puede tener "ARS$ 19.999,20" por lo que se tiene que convertir al flotante 19999.20 pero si no contiene ning煤n numero, por ejemplo "Gratis+" o "Free" es porque el producto es gratis y la ausencia de esta clase indica que el producto NO se puede comprar o adquirir */ const GAME_PASS_DISCOUNT_CLASS = "Price-module__afterPriceTextContainer___r7fdq"; const DISCOUNT_TAG_CLASS = "ProductCard-module__discountTag___OjGFy"; const FINAL_PRICE_CLASS = "ProductCard-module__price___cs1xr"; const FILTER_LIST_CLASS = "SortAndFilters-module__filterList___T81LH"; const BUTTON_APPLY_FILTERS_CLASS = "ApplyFiltersButton-module__applyButton___faTvE"; const BUTTON_SHOW_FILTERS_CLASS = "SortAndFilters-module__button___OeFeU"; const LOAD_MORE_ROW_CLASS = "BrowsePage-module__loadMoreRow___sx0qx"; // Esperar a que exista el elemento filterList const filterList = await waitForElement(`.${FILTER_LIST_CLASS}`); // El resto del c贸digo contin煤a igual... if (filterList) { // Modificar la funci贸n createFilter para aceptar inputs adicionales function createFilter(id, title, options, additionalContent = "") { const li = document.createElement("li"); li.className = "SortAndFilters-module__li___aV+Oo"; const svgExpanded = ` `; const svgCollapsed = ` `; li.innerHTML = `
`; return li; } function addFilterHandlers(filterElement, filterFn) { const button = filterElement.querySelector( ".SelectionDropdown-module__titleContainer___YyoD0" ); const optionsDiv = filterElement.querySelector("div[style]"); const options = filterElement.querySelectorAll( ".Selections-module__selectionContainer___m2xzM" ); button.addEventListener("click", () => { const isExpanded = button.getAttribute("aria-expanded") === "true"; button.setAttribute("aria-expanded", !isExpanded); optionsDiv.classList.toggle("hidden"); }); options.forEach((option) => { const input = option.querySelector("input"); if (!input) return; // Funci贸n para manejar el cambio de estado const toggleFilter = () => { const isSelected = option.getAttribute("aria-selected") === "true"; const newState = !isSelected; if (input.type === "radio") { // Para radio buttons, desmarcar todos los otros del mismo grupo options.forEach((opt) => { const otherInput = opt.querySelector('input[type="radio"]'); if (otherInput && otherInput.name === input.name) { opt.setAttribute("aria-selected", "false"); opt.setAttribute("aria-checked", "false"); otherInput.checked = false; } }); } option.setAttribute("aria-selected", newState); option.setAttribute("aria-checked", newState); input.checked = newState; // Aplicar filtro filterFn(); saveFilterState(); }; // Manejar click en el bot贸n completo option.addEventListener("click", (e) => { if (e.target !== input) { e.preventDefault(); // Prevenir comportamiento por defecto toggleFilter(); } }); // Manejar cambios en el input input.addEventListener("change", () => { if (input.type === "radio") { options.forEach((opt) => { const otherInput = opt.querySelector('input[type="radio"]'); if (otherInput && otherInput.name === input.name) { opt.setAttribute("aria-selected", otherInput.checked); opt.setAttribute("aria-checked", otherInput.checked); } }); } else { option.setAttribute("aria-selected", input.checked); option.setAttribute("aria-checked", input.checked); } // Aplicar filtro filterFn(); saveFilterState(); }); }); } function applyAllFilters() { const productCards = document.querySelectorAll( ".ProductCard-module__cardWrapper___6Ls86" ); const selectedDiscountFilter = document.querySelector('input[name="discountGroup"]:checked')?.value || "none"; const onlyGamePass = document.querySelector("#gamePassOnly_checkbox")?.checked || false; // Obtener el valor personalizado de descuento const customDiscountPercent = parseInt( document.querySelector("#customDiscountPercent")?.value || "0" ); productCards.forEach((card) => { // Obtener estados de los filtros const minPrice = parseFloat(document.querySelector("#priceMin").value) || 0; const maxPrice = parseFloat(document.querySelector("#priceMax").value) || Infinity; const showFree = document.querySelector("#free_checkbox").checked; const showPaid = document.querySelector("#paid_checkbox").checked; const showUnpurchasable = document.querySelector( "#unpurchasable_checkbox" ).checked; // Verificar precio const priceElement = card.querySelector(`.${FINAL_PRICE_CLASS}`); let shouldShow = true; if (!priceElement) { shouldShow = showUnpurchasable; } else { const priceText = priceElement.innerText; const hasNumbers = /\d/.test(priceText); const isFree = !hasNumbers; if (isFree) { shouldShow = showFree; } else { shouldShow = showPaid; if (shouldShow) { const price = parseFloat( priceText.replace(/[^0-9,]/g, "").replace(",", ".") ); shouldShow = price >= minPrice && (maxPrice === 0 || price <= maxPrice); } } } // Verificar descuentos if (shouldShow) { const discountTag = card.querySelector(`.${DISCOUNT_TAG_CLASS}`); const hasGamePassDiscount = card.querySelector( `.${GAME_PASS_DISCOUNT_CLASS}` ); const discountPercentage = discountTag ? parseInt(discountTag.innerText.replace(/[^0-9]/g, "")) : 0; if (onlyGamePass && selectedDiscountFilter !== "none") { // Primero verificamos si tiene descuento de Game Pass shouldShow = hasGamePassDiscount ? true : false; // Si tiene Game Pass, ahora verificamos el porcentaje de descuento if (shouldShow) { switch (selectedDiscountFilter) { case "anyDiscount": shouldShow = discountTag !== null; break; case "discount50plus": shouldShow = discountPercentage >= 50; break; case "discount75plus": shouldShow = discountPercentage >= 75; break; case "discountCustom": shouldShow = discountPercentage >= customDiscountPercent; break; } } } else if (onlyGamePass) { shouldShow = hasGamePassDiscount ? true : false; } else if (selectedDiscountFilter !== "none") { switch (selectedDiscountFilter) { case "anyDiscount": shouldShow = discountTag !== null; break; case "discount50plus": shouldShow = discountPercentage >= 50; break; case "discount75plus": shouldShow = discountPercentage >= 75; break; case "discountCustom": shouldShow = discountPercentage >= customDiscountPercent; break; } } } card.parentElement.style.display = shouldShow ? "" : "none"; }); } // Crear y a帽adir los filtros // Filtro de Precio con inputs personalizados const priceRangeInputs = `
M谩s de
Menos de
`; const priceFilter = createFilter( "PriceRange", "Precio", [ { id: "free", label: "Mostrar Gratis", defaultSelected: true }, { id: "paid", label: "Mostrar de Pago", defaultSelected: true }, { id: "unpurchasable", label: "Mostrar Incomprable", defaultSelected: true, }, ], priceRangeInputs ); // Modificar la creaci贸n del filtro de ofertas const offersFilter = createFilter("Offers", "Oferta", [ { id: "anyDiscount", label: "Con descuento", type: "radio", group: "discountGroup", }, { id: "discount50plus", label: "50% o m谩s", type: "radio", group: "discountGroup", }, { id: "discount75plus", label: "75% o m谩s", type: "radio", group: "discountGroup", }, { id: "discountCustom", label: `% o m谩s`, type: "radio", group: "discountGroup", }, { id: "gamePassOnly", label: "Solo con Game Pass", type: "checkbox" }, ]); filterList.appendChild(priceFilter); filterList.appendChild(offersFilter); // Agregar handlers para los filtros const priceFilterFn = () => { applyAllFilters(); return true; // Siempre retorna true ya que la l贸gica est谩 en applyAllFilters }; const offersFilterFn = () => { applyAllFilters(); return true; // Siempre retorna true ya que la l贸gica est谩 en applyAllFilters }; addFilterHandlers(priceFilter, priceFilterFn); addFilterHandlers(offersFilter, offersFilterFn); // Agregar listeners para los inputs de precio const priceInputs = document.querySelectorAll("#priceMin, #priceMax"); priceInputs.forEach((input) => { input.addEventListener("change", applyAllFilters); }); // Agregar listener para el input de porcentaje personalizado document .querySelector("#customDiscountPercent") ?.addEventListener("change", (e) => { const radio = document.querySelector("#discountCustom_radio"); if (radio) { radio.checked = true; radio.dispatchEvent(new Event("change")); } }); } // Funciones para manejar persistencia let currentFilterState = { priceMin: "", priceMax: "", free: true, paid: true, unpurchasable: true, discountGroup: "none", customDiscountPercent: "", gamePassOnly: false, }; function saveFilterState() { currentFilterState = { priceMin: document.querySelector("#priceMin")?.value || "", priceMax: document.querySelector("#priceMax")?.value || "", free: document.querySelector("#free_checkbox")?.checked || false, paid: document.querySelector("#paid_checkbox")?.checked || false, unpurchasable: document.querySelector("#unpurchasable_checkbox")?.checked || false, discountGroup: document.querySelector('input[name="discountGroup"]:checked')?.value || "none", customDiscountPercent: document.querySelector("#customDiscountPercent")?.value || "", gamePassOnly: document.querySelector("#gamePassOnly_checkbox")?.checked || false, }; } function loadFilterState() { if (!currentFilterState) return; if (document.querySelector("#priceMin")) document.querySelector("#priceMin").value = currentFilterState.priceMin; if (document.querySelector("#priceMax")) document.querySelector("#priceMax").value = currentFilterState.priceMax; if (document.querySelector("#free_checkbox")) document.querySelector("#free_checkbox").checked = currentFilterState.free; if (document.querySelector("#paid_checkbox")) document.querySelector("#paid_checkbox").checked = currentFilterState.paid; if (document.querySelector("#unpurchasable_checkbox")) document.querySelector("#unpurchasable_checkbox").checked = currentFilterState.unpurchasable; if ( currentFilterState.discountGroup !== "none" && document.querySelector(`#${currentFilterState.discountGroup}_radio`) ) { document.querySelector( `#${currentFilterState.discountGroup}_radio` ).checked = true; } if (document.querySelector("#customDiscountPercent")) document.querySelector("#customDiscountPercent").value = currentFilterState.customDiscountPercent; if (document.querySelector("#gamePassOnly_checkbox")) document.querySelector("#gamePassOnly_checkbox").checked = currentFilterState.gamePassOnly; } // Observer para mantener los filtros en m贸vil function setupFilterListObserver() { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === "childList") { const filterList = document.querySelector(`.${FILTER_LIST_CLASS}`); if (filterList && !filterList.hasAttribute("data-initialized")) { initializeFilters(filterList); filterList.setAttribute("data-initialized", "true"); } } }); }); observer.observe(document.body, { childList: true, subtree: true, }); } function initializeFilters(filterList) { // Definir las funciones de filtro antes de usarlas const priceFilterFn = () => { applyAllFilters(); saveFilterState(); return true; }; const offersFilterFn = () => { applyAllFilters(); saveFilterState(); return true; }; // Crear los filtros const priceFilter = createFilter( "PriceRange", "Precio", [ { id: "free", label: "Mostrar Gratis", defaultSelected: currentFilterState.free, }, { id: "paid", label: "Mostrar de Pago", defaultSelected: currentFilterState.paid, }, { id: "unpurchasable", label: "Mostrar Incomprable", defaultSelected: currentFilterState.unpurchasable, }, ], `
M谩s de
Menos de
` ); const offersFilter = createFilter("Offers", "Oferta", [ { id: "anyDiscount", label: "Con descuento", type: "radio", group: "discountGroup", }, { id: "discount50plus", label: "50% o m谩s", type: "radio", group: "discountGroup", }, { id: "discount75plus", label: "75% o m谩s", type: "radio", group: "discountGroup", }, { id: "discountCustom", label: `% o m谩s`, type: "radio", group: "discountGroup", }, { id: "gamePassOnly", label: "Solo con Game Pass", type: "checkbox" }, ]); filterList.appendChild(priceFilter); filterList.appendChild(offersFilter); addFilterHandlers(priceFilter, priceFilterFn); addFilterHandlers(offersFilter, offersFilterFn); // Agregar listeners para los inputs const priceInputs = document.querySelectorAll("#priceMin, #priceMax"); priceInputs.forEach((input) => { input.addEventListener("change", () => { applyAllFilters(); saveFilterState(); }); }); document .querySelector("#customDiscountPercent") ?.addEventListener("change", (e) => { const radio = document.querySelector("#discountCustom_radio"); if (radio) { radio.checked = true; radio.dispatchEvent(new Event("change")); saveFilterState(); } }); // Cargar estado guardado loadFilterState(); applyAllFilters(); } // Agregar listener para el bot贸n de aplicar filtros function setupApplyButton() { const applyButton = document.querySelector( `.${BUTTON_APPLY_FILTERS_CLASS}` ); if (applyButton) { applyButton.addEventListener("click", () => { applyAllFilters(); saveFilterState(); }); } } // Inicializaci贸n if (document.querySelector(`.${FILTER_LIST_CLASS}`)) { initializeFilters(document.querySelector(`.${FILTER_LIST_CLASS}`)); } setupFilterListObserver(); setupApplyButton(); })();