Greasy Fork

Greasy Fork is available in English.

Xbox Wishlist Sale Items Only

Display sale items with sorting options (discount, price, rating) and toggle sort order

当前为 2025-04-16 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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);
})();