Greasy Fork is available in English.
Display sale items with sorting options (discount, price, rating) and toggle sort order
当前为
// ==UserScript==
// @name Xbox Wishlist Sale Items Only
// @namespace http://tampermonkey.net/
// @version 5
// @description Display sale items with sorting options (discount, price, rating) and toggle sort order
// @match https://www.xbox.com/*/wishlist
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
let currentSortKey = 'discountPercentage';
let sortOrder = 'desc';
let minRating = 3;
let maxPrice = Infinity;
let minDiscount = 5;
const getWishlistData = () => {
try {
const script = [...document.scripts].find((s) => s.innerText.includes('window.__PRELOADED_STATE__'));
const dataMatch = script?.innerText.match(/window\.__PRELOADED_STATE__\s*=\s*(\{.*\})\s*;/);
return dataMatch ? Object.values(JSON.parse(dataMatch[1]).core2?.products?.productSummaries || {}) : [];
} catch {
console.error('Error retrieving wishlist data');
return [];
}
};
const injectStyles = () => {
const css = `
body {
background-color: #121212;
color: #E0E0E0;
font-family: 'Roboto', sans-serif;
}
.wishlist-container {
margin: 20px auto;
padding: 20px;
background: #1E1E1E;
border: 2px solid #333;
border-radius: 12px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.6);
max-width: 1200px;
}
.wishlist-title {
text-align: center;
font-size: 24px;
font-weight: bold;
margin-bottom: 20px;
color: #E0E0E0;
text-transform: uppercase;
border-bottom: 2px solid #333;
padding-bottom: 10px;
}
.input-container {
position: relative;
display: inline-block;
}
.input-container input {
background: #2A2A2A;
color: #E0E0E0;
border: 1px solid #444;
border-radius: 8px;
padding: 5px;
font-size: 12px;
outline: none;
transition: border 0.3s, box-shadow 0.3s;
width: 225px;
}
.input-container label {
position: absolute;
left: 10px;
top: 15px;
font-size: 12px;
color: #888;
transition: all 0.3s ease;
pointer-events: none;
}
.input-container input:focus + label,
.input-container input:not(:placeholder-shown) + label {
top: 5px;
font-size: 12px;
color: #4CAF50;
}
.input-container input:focus {
border-color: #4CAF50;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
}
.wishlist-grid {
display: grid;
gap: 15px;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
padding: 0;
}
.custom-card {
position: relative;
background: #2A2A2A;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.5);
border: 1px solid transparent;
transition: transform 0.3s, box-shadow 0.3s;
}
.custom-card .image-wrapper {
position: relative;
width: 100%;
padding-top: 150%; /* Maintains 720x1080 aspect ratio (height is 1.5 times the width) */
overflow: hidden;
background: #1E1E1E; /* Placeholder background in case image fails to load */
}
.custom-card img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover; /* Ensures the image scales proportionally */
}
.custom-card:hover {
border: 1px solid white !important;
}
.info {
position: absolute;
bottom: 0;
left: 0;
background: rgba(0, 0, 0, 0.8);
width: 100%;
display: flex;
justify-content: space-between;
padding: 8px 12px;
box-sizing: border-box;
font-size: 14px;
color: #E0E0E0;
}
.price {
color: #4CAF50;
font-weight: bold;
}
.original-price {
color: #B0BEC5;
text-decoration: line-through;
font-size: 12px;
}
.discount {
color: #FF7043;
font-weight: bold;
}
.rating {
color: #FFD700;
font-weight: bold;
}
.sort-options {
display: flex;
flex-wrap: wrap;
justify-content: center;
margin-bottom: 20px;
gap: 15px;
}
.sort-options select,
.sort-options button,
.sort-options input {
background: #2A2A2A;
color: #E0E0E0;
border: 1px solid #444;
border-radius: 8px;
padding: 12px 15px; /* Increase padding */
font-size: 16px; /* Increase font size */
cursor: pointer;
transition: background 0.3s, box-shadow 0.3s;
min-width: 170px; /* Ensure inputs are wider */
}
.sort-options input {
width: 170px; /* Consistent width for inputs */
box-sizing: border-box; /* Prevent overflow */
}
.sort-options select {
width: 190px; /* Ensure dropdown is wider */
}
.sort-options button {
min-width: 190px; /* Increase button width */
font-weight: bold;
text-align: center;
}
.sort-options select:hover,
.sort-options button:hover,
.sort-options input:hover {
background: #333;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
}
`;
const style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
};
const displayWishlist = (items) => {
const grid = document.querySelector('.wishlist-grid');
grid.innerHTML = '';
items
.filter(({ specificPrices, averageRating }) => {
const priceData = specificPrices?.purchaseable?.[0];
return (
priceData &&
priceData.listPrice <= maxPrice &&
priceData.discountPercentage >= minDiscount &&
averageRating >= minRating
);
})
.sort((a, b) => {
const getValue = (item, key) => {
const priceData = item.specificPrices?.purchaseable?.[0] || {};
if (key === 'discountPercentage') return priceData.discountPercentage || 0;
if (key === 'listPrice') return priceData.listPrice || 0;
return item.averageRating || 0;
};
return sortOrder === 'asc'
? getValue(a, currentSortKey) - getValue(b, currentSortKey)
: getValue(b, currentSortKey) - getValue(a, currentSortKey);
})
.forEach(({ title = 'Unknown', images, specificPrices, averageRating, productId }) => {
const priceData = specificPrices?.purchaseable?.[0] || {};
const skuId = priceData.skuId || null;
const detailUrl = `https://www.xbox.com/en-US/games/store/${title.replace(/\s/g, '-').toLowerCase()}/${productId}/${skuId}`;
const card = document.createElement('div');
card.className = 'custom-card';
card.style.cursor = 'pointer';
card.onclick = () => window.open(detailUrl, '_blank');
card.innerHTML = `
<div class="image-wrapper">
<img src="${images?.poster?.url || 'https://via.placeholder.com/720x1080'}" alt="${title}">
</div>
<div class="info">
<div>
<div class="price">$${priceData.listPrice?.toFixed(2) || 'N/A'}
<span class="original-price">${priceData.msrp ? `$${priceData.msrp.toFixed(2)}` : ''}</span>
</div>
<div class="discount">${priceData.discountPercentage ? `${priceData.discountPercentage.toFixed(0)}% OFF` : ''}</div>
</div>
<div class="rating">★ ${averageRating?.toFixed(1) || 'N/A'}</div>
</div>
`;
grid.appendChild(card);
});
};
const addSortOptions = (wishlistData) => {
const container = document.querySelector('.wishlist-container');
const sortDiv = document.createElement('div');
sortDiv.className = 'sort-options';
sortDiv.innerHTML = `
<div class="input-container">
<input id="min-rating" type="number" min="0" max="5" step="0.1" placeholder=" " />
<label for="min-rating">Min Rating</label>
</div>
<div class="input-container">
<input id="max-price" type="number" min="0" placeholder=" " />
<label for="max-price">Max Price</label>
</div>
<div class="input-container">
<input id="min-discount" type="number" min="0" max="100" placeholder=" " />
<label for="min-discount">Min Discount %</label>
</div>
<select id="sort-select">
<option value="discountPercentage">Sort by: Discount</option>
<option value="listPrice">Sort by: Price</option>
<option value="averageRating">Sort by: Rating</option>
</select>
<button id="toggle-order">Sort: Descending</button>
`;
container.insertBefore(sortDiv, container.querySelector('.wishlist-grid'));
document.getElementById('sort-select').addEventListener('change', (e) => {
currentSortKey = e.target.value;
displayWishlist(wishlistData);
});
document.getElementById('toggle-order').addEventListener('click', () => {
sortOrder = sortOrder === 'asc' ? 'desc' : 'asc';
document.getElementById('toggle-order').textContent = `Sort: ${sortOrder === 'asc' ? 'Ascending' : 'Descending'}`;
displayWishlist(wishlistData);
});
document.getElementById('min-rating').addEventListener('input', (e) => {
minRating = parseFloat(e.target.value) || 0;
displayWishlist(wishlistData);
});
document.getElementById('max-price').addEventListener('input', (e) => {
maxPrice = parseFloat(e.target.value) || Infinity;
displayWishlist(wishlistData);
});
document.getElementById('min-discount').addEventListener('input', (e) => {
minDiscount = parseFloat(e.target.value) || 0;
displayWishlist(wishlistData);
});
document.getElementById('min-rating').value = minRating; // Set default min rating
document.getElementById('min-discount').value = minDiscount; // Set default min discount
// Trigger input events to apply default values on load
document.getElementById('min-rating').dispatchEvent(new Event('input'));
document.getElementById('min-discount').dispatchEvent(new Event('input'));
};
const init = () => {
injectStyles();
const container = document.querySelector('.WishlistPage-module__wishListForm___p6wOx');
if (!container) return console.error('Wishlist container not found');
container.classList.add('wishlist-container');
container.innerHTML = `
<div class="wishlist-title">My Wishlist</div>
<div class="wishlist-grid"></div>
`;
const wishlistData = getWishlistData();
addSortOptions(wishlistData);
displayWishlist(wishlistData);
};
window.addEventListener('load', init);
})();