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 3
// @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';
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: 95%;
}
.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;
}
.wishlist-grid {
display: grid;
gap: 5px;
grid-template-columns: repeat(auto-fit, minmax(180px, 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 img {
width: 100%;
height: 256px;
}
.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;
justify-content: center;
margin-bottom: 20px;
gap: 10px;
}
.sort-options select,
.sort-options button {
background: #2A2A2A;
color: #E0E0E0;
border: 1px solid #444;
border-radius: 8px;
padding: 10px 15px;
font-size: 14px;
cursor: pointer;
transition: background 0.3s, box-shadow 0.3s;
}
.sort-options select:hover,
.sort-options button: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 }) => {
const priceData = specificPrices?.purchaseable?.[0];
return priceData && priceData.listPrice < priceData.msrp;
}).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 = `
<img src="${images?.poster?.url || 'https://via.placeholder.com/200x300'}" alt="${title}">
<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 = `
<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);
});
};
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);
})();