Bazaars in Item Market 2.0, powered by TornPal & IronNerd

Displays bazaar listings with sorting controls via TornPal & IronNerd

目前為 2025-02-24 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Bazaars in Item Market 2.0, powered by TornPal & IronNerd
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Displays bazaar listings with sorting controls via TornPal & IronNerd
// @match        https://www.torn.com/page.php?sid=ItemMarket*
// @grant        GM_xmlhttpRequest
// @connect      tornpal.com
// @connect      www.ironnerd.me
// @run-at       document-end

// ==/UserScript==

(function() {
    'use strict';

    dailyCleanup();
    let allDollarToggles = [];
    const CACHE_DURATION_MS = 60000;
    let currentSortKey = "price", currentSortOrder = "asc";
    let showDollarItems = localStorage.getItem("showDollarItems") === "true";

    function setStyles(el, styles) {
        Object.assign(el.style, styles);
    }

    function isDarkMode() {
        return document.body.classList.contains('dark-mode') ||
               document.body.getAttribute('data-dark-mode') === 'true';
    }

    function getButtonStyle() {
        return {
            padding: '2px 4px',
            border: isDarkMode() ? '1px solid #444' : '1px solid #ccc',
            borderRadius: '2px',
            backgroundColor: isDarkMode() ? '#1a1a1a' : '#fff',
            color: isDarkMode() ? '#fff' : '#000',
            cursor: 'pointer'
        };
    }

    function fetchJSON(url, callback) {
        GM_xmlhttpRequest({
            method: 'GET',
            url: url,
            onload: function(response) {
                try { callback(JSON.parse(response.responseText)); }
                catch(e) { callback(null); }
            },
            onerror: function() { callback(null); }
        });
    }

    const style = document.createElement("style");
    style.textContent = `
    @keyframes popAndFlash {
      0% { transform: scale(1); background-color: rgba(0, 255, 0, 0.6); }
      50% { transform: scale(1.05); }
      100% { transform: scale(1); background-color: inherit; }
    }
    .pop-flash { animation: popAndFlash 0.8s ease-in-out forwards; }
    .green-outline { border: 3px solid green !important; }
    `;
    document.head.appendChild(style);

    function getCache(itemId) {
        try {
            const key = "tornBazaarCache_" + itemId;
            const cached = localStorage.getItem(key);
            if (cached) {
                const payload = JSON.parse(cached);
                if (Date.now() - payload.timestamp < CACHE_DURATION_MS) return payload.data;
            }
        } catch(e) {}
        return null;
    }
    function setCache(itemId, data) {
        try {
            localStorage.setItem("tornBazaarCache_" + itemId, JSON.stringify({ timestamp: Date.now(), data }));
        } catch(e) {}
    }
    function getRelativeTime(ts) {
        const diffSec = Math.floor((Date.now() - ts * 1000) / 1000);
        if (diffSec < 60) return diffSec + 's ago';
        if (diffSec < 3600) return Math.floor(diffSec/60) + 'm ago';
        if (diffSec < 86400) return Math.floor(diffSec/3600) + 'h ago';
        return Math.floor(diffSec/86400) + 'd ago';
    }

    function openModal(url) {
        const originalOverflow = document.body.style.overflow;
        document.body.style.overflow = 'hidden';

        const modalOverlay = document.createElement('div');
        modalOverlay.id = 'bazaar-modal-overlay';
        setStyles(modalOverlay, {
            position: 'fixed', top: '0', left: '0', width: '100%', height: '100%',
            backgroundColor: 'rgba(0, 0, 0, 0.7)', display: 'flex', justifyContent: 'center',
            alignItems: 'center', zIndex: '10000', overflow: 'visible'
        });

        const modalContainer = document.createElement('div');
        modalContainer.id = 'bazaar-modal-container';
        setStyles(modalContainer, {
            backgroundColor: '#fff', border: '8px solid #000',
            boxShadow: '0 0 10px rgba(0,0,0,0.5)', borderRadius: '8px',
            position: 'relative', resize: 'both'
        });

        const savedSize = localStorage.getItem('bazaarModalSize');
        if (savedSize) {
            try {
                const { width, height } = JSON.parse(savedSize);
                modalContainer.style.width = (width < 200 ? '80%' : width + 'px');
                modalContainer.style.height = (height < 200 ? '80%' : height + 'px');
            } catch(e) {
                modalContainer.style.width = modalContainer.style.height = '80%';
            }
        } else {
            modalContainer.style.width = modalContainer.style.height = '80%';
        }

        const closeButton = document.createElement('button');
        closeButton.textContent = '×';
        setStyles(closeButton, Object.assign({}, getButtonStyle(), {
            position: 'absolute', top: '-20px', right: '-20px',
            width: '40px', height: '40px', backgroundColor: '#ff0000',
            color: '#fff', border: 'none', borderRadius: '50%',
            fontSize: '24px', boxShadow: '0 0 5px rgba(0,0,0,0.5)'
        }));
        closeButton.addEventListener('click', () => {
            modalOverlay.remove();
            document.body.style.overflow = originalOverflow;
        });

        modalOverlay.addEventListener('click', e => {
            if (e.target === modalOverlay) {
                modalOverlay.remove();
                document.body.style.overflow = originalOverflow;
            }
        });

        const iframe = document.createElement('iframe');
        setStyles(iframe, { width: '100%', height: '100%', border: 'none' });
        iframe.src = url;

        modalContainer.append(closeButton, iframe);
        modalOverlay.appendChild(modalContainer);
        document.body.appendChild(modalOverlay);

        if (window.ResizeObserver) {
            const observer = new ResizeObserver(entries => {
                for (let entry of entries) {
                    const { width, height } = entry.contentRect;
                    localStorage.setItem('bazaarModalSize', JSON.stringify({
                        width: Math.round(width),
                        height: Math.round(height)
                    }));
                }
            });
            observer.observe(modalContainer);
        }
    }

    function createInfoContainer(itemName, itemId) {
        const dark = isDarkMode();
        const container = document.createElement('div');
        container.id = 'item-info-container';
        container.setAttribute('data-itemid', itemId);
        setStyles(container, {
            backgroundColor: dark ? '#2f2f2f' : '#f9f9f9',
            color: dark ? '#ccc' : '#000',
            fontSize: '13px',
            border: dark ? '1px solid #444' : '1px solid #ccc',
            borderRadius: '4px',
            margin: '5px 0',
            padding: '10px',
            display: 'flex',
            flexDirection: 'column',
            gap: '8px'
        });

        const header = document.createElement('div');
        header.className = 'info-header';
        setStyles(header, { fontSize: '16px', fontWeight: 'bold', color: dark ? '#fff' : '#000' });
        header.textContent = `Item: ${itemName} (ID: ${itemId})`;
        container.appendChild(header);

        const sortControls = document.createElement('div');
        sortControls.className = 'sort-controls';
        setStyles(sortControls, {
            display: 'flex',
            alignItems: 'center',
            gap: '5px',
            fontSize: '12px',
            padding: '5px',
            backgroundColor: dark ? '#333' : '#eee',
            borderRadius: '4px'
        });
        const sortLabel = document.createElement('span');
        sortLabel.textContent = "Sort by:";
        sortControls.appendChild(sortLabel);

        const sortSelect = document.createElement('select');
        setStyles(sortSelect, {
            padding: '2px',
            border: dark ? '1px solid #444' : '1px solid #ccc',
            borderRadius: '2px',
            backgroundColor: dark ? '#1a1a1a' : '#fff',
            color: dark ? '#fff' : '#000'
        });
        [{ value: "price", text: "Price" },
         { value: "quantity", text: "Quantity" },
         { value: "updated", text: "Last Updated" }]
            .forEach(opt => {
                const option = document.createElement('option');
                option.value = opt.value;
                option.textContent = opt.text;
                sortSelect.appendChild(option);
            });
        sortSelect.value = currentSortKey;
        sortControls.appendChild(sortSelect);

        const orderToggle = document.createElement('button');
        setStyles(orderToggle, getButtonStyle());
        orderToggle.textContent = currentSortOrder === "asc" ? "Asc" : "Desc";
        sortControls.appendChild(orderToggle);

        const dollarToggle = document.createElement('button');
        setStyles(dollarToggle, getButtonStyle());
        // Use the persisted state for button text
        dollarToggle.textContent = showDollarItems ? "Showing $1 Items" : "Hiding $1 Items";
        sortControls.appendChild(dollarToggle);
        allDollarToggles.push(dollarToggle);
        dollarToggle.addEventListener('click', () => {
            showDollarItems = !showDollarItems;
            // Persist the updated state in localStorage
            localStorage.setItem("showDollarItems", showDollarItems.toString());
            allDollarToggles.forEach(btn => {
                btn.textContent = showDollarItems ? "Showing $1 Items" : "Hiding $1 Items";
            });
            if (container.filteredListings) renderCards(container, container.filteredListings);
        });

        container.appendChild(sortControls);

        const scrollWrapper = document.createElement('div');
        setStyles(scrollWrapper, {
            overflowX: 'auto',
            overflowY: 'hidden',
            height: '120px',
            whiteSpace: 'nowrap',
            paddingBottom: '3px'
        });
        const cardContainer = document.createElement('div');
        cardContainer.className = 'card-container';
        setStyles(cardContainer, { display: 'flex', flexWrap: 'nowrap', gap: '10px' });
        scrollWrapper.appendChild(cardContainer);
        container.appendChild(scrollWrapper);

        const poweredBy = document.createElement('div');
        setStyles(poweredBy, { fontSize: '10px', textAlign: 'right', marginTop: '6px' });
        poweredBy.innerHTML = `
          <span style="color:${dark ? '#666' : '#999'};">Powered by </span>
          <a href="https://tornpal.com/" target="_blank" style="color:${dark ? '#aaa' : '#555'}; text-decoration:underline;">TornPal</a>
          <span style="color:${dark ? '#666' : '#999'};"> &amp; </span>
          <a href="https://ironnerd.me/" target="_blank" style="color:${dark ? '#aaa' : '#555'}; text-decoration:underline;">IronNerd</a>
        `;
        container.appendChild(poweredBy);

        sortSelect.addEventListener('change', () => {
            currentSortKey = sortSelect.value;
            if (container.filteredListings) renderCards(container, container.filteredListings);
        });
        orderToggle.addEventListener('click', () => {
            currentSortOrder = currentSortOrder === "asc" ? "desc" : "asc";
            orderToggle.textContent = currentSortOrder === "asc" ? "Asc" : "Desc";
            if (container.filteredListings) renderCards(container, container.filteredListings);
        });

        return container;
    }

    function renderCards(infoContainer, listings) {
        const filtered = showDollarItems ? listings : listings.filter(l => l.price !== 1);
        const sorted = filtered.slice().sort((a, b) => {
            let diff = (currentSortKey === "price") ? a.price - b.price :
                       (currentSortKey === "quantity") ? a.quantity - b.quantity :
                       a.updated - b.updated;
            return currentSortOrder === "asc" ? diff : -diff;
        });
        const cardContainer = infoContainer.querySelector('.card-container');
        cardContainer.innerHTML = '';
        sorted.forEach(listing => cardContainer.appendChild(createListingCard(listing)));
    }

    function createListingCard(listing) {
        const dark = isDarkMode();
        const card = document.createElement('div');
        card.className = 'listing-card';
        setStyles(card, {
            position: 'relative',
            minWidth: '160px',
            maxWidth: '240px',
            display: 'inline-block',
            verticalAlign: 'top',
            backgroundColor: dark ? '#1a1a1a' : '#fff',
            color: dark ? '#fff' : '#000',
            border: dark ? '1px solid #444' : '1px solid #ccc',
            borderRadius: '4px',
            padding: '8px',
            fontSize: 'clamp(12px, 1vw, 16px)',
            boxSizing: 'border-box',
            overflow: 'hidden'
        });

        const linkContainer = document.createElement('div');
        setStyles(linkContainer, { display: 'flex', alignItems: 'center', gap: '5px', marginBottom: '6px', flexWrap: 'wrap' });
        const visitedKey = `visited_${listing.item_id}_${listing.player_id}`;
        let visitedData = null;
        try { visitedData = JSON.parse(localStorage.getItem(visitedKey)); } catch(e){}
        let linkColor = (visitedData && visitedData.lastClickedUpdated >= listing.updated) ? 'purple' : '#00aaff';

        const playerLink = document.createElement('a');
        playerLink.href = `https://www.torn.com/bazaar.php?userId=${listing.player_id}&itemId=${listing.item_id}&highlight=1#/`;
        playerLink.textContent = `Player: ${listing.player_id}`;
        setStyles(playerLink, { fontWeight: 'bold', color: linkColor, textDecoration: 'underline' });
        playerLink.addEventListener('click', () => {
            localStorage.setItem(visitedKey, JSON.stringify({ lastClickedUpdated: listing.updated }));
            playerLink.style.color = 'purple';
        });
        linkContainer.appendChild(playerLink);

        const iconSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
        iconSvg.setAttribute("viewBox", "0 0 512 512");
        iconSvg.setAttribute("width", "16");
        iconSvg.setAttribute("height", "16");
        iconSvg.style.cursor = "pointer";
        iconSvg.style.color = dark ? '#ffa500' : '#cc6600';
        iconSvg.title = "Open in modal";
        iconSvg.innerHTML = `
            <path fill="currentColor" d="M432 64L208 64c-8.8 0-16 7.2-16 16l0 16-64 0 0-16c0-44.2 35.8-80 80-80L432 0c44.2 0 80 35.8 80 80l0 224
                c0 44.2-35.8 80-80 80l-16 0 0-64 16 0c8.8 0 16-7.2 16-16l0-224c0-8.8-7.2-16-16-16zM0 192c0-35.3 28.7-64 64-64l256 0c35.3 0
                64 28.7 64 64l0 256c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 192zm64 32c0 17.7 14.3 32 32 32l192 0c17.7 0 32-14.3
                32-32s-14.3-32-32-32L96 192c-17.7 0-32 14.3-32 32z"/>
        `;
        iconSvg.addEventListener('click', e => {
            e.preventDefault();
            localStorage.setItem(visitedKey, JSON.stringify({ lastClickedUpdated: listing.updated }));
            playerLink.style.color = 'purple';
            openModal(`https://www.torn.com/bazaar.php?userId=${listing.player_id}&itemId=${listing.item_id}&highlight=1#/`);
        });
        linkContainer.appendChild(iconSvg);
        card.appendChild(linkContainer);

        const details = document.createElement('div');
        details.innerHTML = `<div><strong>Price:</strong> $${listing.price.toLocaleString()}</div>
                             <div><strong>Qty:</strong> ${listing.quantity}</div>`;
        details.style.marginBottom = '6px';
        card.appendChild(details);

        const footnote = document.createElement('div');
        setStyles(footnote, { fontSize: '11px', color: dark ? '#aaa' : '#555', textAlign: 'right' });
        footnote.textContent = `Updated: ${getRelativeTime(listing.updated)}`;
        card.appendChild(footnote);

        const sourceInfo = document.createElement('div');
        setStyles(sourceInfo, { fontSize: '10px', color: dark ? '#aaa' : '#555', textAlign: 'right' });
        let sourceDisplay = listing.source === "ironnerd" ? "IronNerd" : (listing.source === "bazaar" ? "TornPal" : listing.source);
        sourceInfo.textContent = "Source: " + sourceDisplay;
        card.appendChild(sourceInfo);

        return card;
    }

    function updateInfoContainer(wrapper, itemId, itemName) {
        let infoContainer = document.querySelector(`#item-info-container[data-itemid="${itemId}"]`);
        if (!infoContainer) {
            infoContainer = createInfoContainer(itemName, itemId);
            wrapper.insertBefore(infoContainer, wrapper.firstChild);
        } else {
            const header = infoContainer.querySelector('.info-header');
            if (header) header.textContent = `Item: ${itemName} (ID: ${itemId})`;
            const cardContainer = infoContainer.querySelector('.card-container');
            if (cardContainer) cardContainer.innerHTML = '';
        }
        const cachedData = getCache(itemId);
        if (cachedData) {
            infoContainer.filteredListings = cachedData.listings;
            renderCards(infoContainer, cachedData.listings);
            return;
        }
        let listings = [], responsesReceived = 0;
        function processResponse(newListings) {
            newListings.forEach(newItem => {
                let normalized = newItem.user_id !== undefined ? {
                    item_id: newItem.item_id,
                    player_id: newItem.user_id,
                    quantity: newItem.quantity,
                    price: newItem.price,
                    updated: newItem.last_updated,
                    source: "ironnerd"
                } : newItem;
                let duplicate = listings.find(item =>
                    item.player_id === normalized.player_id &&
                    item.price === normalized.price &&
                    item.quantity === normalized.quantity
                );
                if (duplicate) {
                    if (duplicate.source !== normalized.source)
                        duplicate.source = "TornPal & IronNerd";
                } else { listings.push(normalized); }
            });
            responsesReceived++;
            if (responsesReceived === 2) {
                setCache(itemId, { listings });
                infoContainer.filteredListings = listings;
                renderCards(infoContainer, listings);
            }
        }
        fetchJSON(`https://tornpal.com/api/v1/markets/clist/${itemId}`, data => {
            processResponse(data && data.listings && Array.isArray(data.listings) ? data.listings.filter(l => l.source === "bazaar") : []);
        });
        fetchJSON(`https://www.ironnerd.me/get_bazaar_items/${itemId}`, data => {
            processResponse(data && data.bazaar_items && Array.isArray(data.bazaar_items) ? data.bazaar_items : []);
        });
    }

    function processSellerWrapper(wrapper) {
        if (!wrapper || wrapper.id === 'item-info-container') return;
        const itemTile = wrapper.previousElementSibling;
        if (!itemTile) return;
        const nameEl = itemTile.querySelector('.name___ukdHN');
        const btn = itemTile.querySelector('button[aria-controls^="wai-itemInfo-"]');
        if (nameEl && btn) {
            const itemName = nameEl.textContent.trim();
            const idParts = btn.getAttribute('aria-controls').split('-');
            const itemId = idParts[idParts.length - 1];
            updateInfoContainer(wrapper, itemId, itemName);
        }
    }

    function processMobileSellerList() {
        if (window.innerWidth >= 784) return;
        const sellerList = document.querySelector('ul.sellerList___e4C9_');
        if (!sellerList) {
            const existing = document.querySelector('#item-info-container');
            if (existing) existing.remove();
            return;
        }
        const headerEl = document.querySelector('.itemsHeader___ZTO9r .title___ruNCT');
        const itemName = headerEl ? headerEl.textContent.trim() : "Unknown";
        const btn = document.querySelector('.itemsHeader___ZTO9r button[aria-controls^="wai-itemInfo-"]');
        let itemId = "unknown";
        if (btn) {
            const parts = btn.getAttribute('aria-controls').split('-');
            itemId = parts.length > 2 ? parts[parts.length - 2] : parts[parts.length - 1];
        }
        if (document.querySelector(`#item-info-container[data-itemid="${itemId}"]`)) return;
        const infoContainer = createInfoContainer(itemName, itemId);
        sellerList.parentNode.insertBefore(infoContainer, sellerList);
        updateInfoContainer(infoContainer, itemId, itemName);
    }

    function processAllSellerWrappers(root = document.body) {
        if (window.innerWidth < 784) return;
        root.querySelectorAll('[class*="sellerListWrapper"]').forEach(wrapper => processSellerWrapper(wrapper));
    }

    processAllSellerWrappers();
    processMobileSellerList();

    const observer = new MutationObserver(mutations => {
        mutations.forEach(mutation => {
            mutation.addedNodes.forEach(node => {
                if (node.nodeType === Node.ELEMENT_NODE) {
                    if (window.innerWidth < 784 && node.matches('ul.sellerList___e4C9_')) {
                        processMobileSellerList();
                    } else {
                        if (node.matches('[class*="sellerListWrapper"]')) processSellerWrapper(node);
                        processAllSellerWrappers(node);
                    }
                }
            });
            mutation.removedNodes.forEach(node => {
                if (node.nodeType === Node.ELEMENT_NODE &&
                    node.matches('ul.sellerList___e4C9_') &&
                    window.innerWidth < 784) {
                    const container = document.querySelector('#item-info-container');
                    if (container) container.remove();
                }
            });
        });
    });
    observer.observe(document.body, { childList: true, subtree: true });

    if (window.location.href.includes("bazaar.php")) {
        function scrollToTargetItem() {
            const params = new URLSearchParams(window.location.search);
            const targetItemId = params.get("itemId"), highlightParam = params.get("highlight");
            if (!targetItemId || highlightParam !== "1") return;

            function removeHighlightParam() {
                params.delete("highlight");
                history.replaceState({}, "", window.location.pathname + "?" + params.toString() + window.location.hash);
            }
            function showToast(message) {
                const toast = document.createElement('div');
                toast.textContent = message;
                setStyles(toast, {
                    position: 'fixed',
                    bottom: '20px',
                    left: '50%',
                    transform: 'translateX(-50%)',
                    backgroundColor: 'rgba(0,0,0,0.7)',
                    color: 'white',
                    padding: '10px 20px',
                    borderRadius: '5px',
                    zIndex: '100000',
                    fontSize: '14px'
                });
                document.body.appendChild(toast);
                setTimeout(() => { toast.remove(); }, 3000);
            }
            function findItemCard() {
                const img = document.querySelector(`img[src*="/images/items/${targetItemId}/"]`);
                return img ? img.closest('.item___GYCYJ') : null;
            }
            const scrollInterval = setInterval(() => {
                const card = findItemCard();
                if (card) {
                    clearInterval(scrollInterval);
                    removeHighlightParam();
                    card.classList.add("green-outline", "pop-flash");
                    card.scrollIntoView({ behavior: "smooth", block: "center" });
                    setTimeout(() => { card.classList.remove("pop-flash"); }, 800);
                } else {
                    if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
                        showToast("Item not found on this page.");
                        removeHighlightParam();
                        clearInterval(scrollInterval);
                    } else {
                        window.scrollBy({ top: 300, behavior: 'auto' });
                    }
                }
            }, 50);
        }
        function waitForItems() {
            const container = document.querySelector('.ReactVirtualized__Grid__innerScrollContainer');
            if (container && container.childElementCount > 0) scrollToTargetItem();
            else setTimeout(waitForItems, 500);
        }
        waitForItems();
    }

    function dailyCleanup() {
        const lastCleanup = localStorage.getItem("lastDailyCleanup"),
              oneDay = 24 * 60 * 60 * 1000,
              now = Date.now();
        if (!lastCleanup || (now - parseInt(lastCleanup, 10)) > oneDay) {
            const sevenDays = 7 * 24 * 60 * 60 * 1000;
            for (let i = 0; i < localStorage.length; i++) {
                const key = localStorage.key(i);
                if (key && (key.startsWith("visited_") || key.startsWith("tornBazaarCache_"))) {
                    try {
                        const val = JSON.parse(localStorage.getItem(key));
                        let ts = null;
                        if (key.startsWith("visited_") && val && val.lastClickedUpdated) {
                            ts = val.lastClickedTime || (localStorage.removeItem(key), null);
                        } else if (key.startsWith("tornBazaarCache_") && val && val.timestamp) {
                            ts = val.timestamp;
                        }
                        if (ts !== null && (now - ts) > sevenDays) localStorage.removeItem(key);
                    } catch(e) { localStorage.removeItem(key); }
                }
            }
            localStorage.setItem("lastDailyCleanup", now.toString());
        }
    }
})();