FRPG Mailbox Condenser

Condense duplicate items in FRPG mailbox log

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         FRPG Mailbox Condenser
// @namespace    http://tampermonkey.net/
// @version      v1.0
// @author       CoreDialer
// @match        https://farmrpg.com/index.php
// @icon         https://www.google.com/s2/favicons?sz=64&domain=farmrpg.com
// @grant        none
// @description  Condense duplicate items in FRPG mailbox log
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // State tracking
    let isCondensed = false;
    let originalItems = [];

    // Check if we're on the mastery page by monitoring URL changes and DOM
    function checkForMailboxPage() {
        // Check if URL contains mastery.php or if mastery elements are present
        const isMasteryPage = window.location.href.includes('mailboxlog.php') ||
                             document.querySelector('.page[data-name="mailboxlog"]') !== null;

        // Only show button on mastery page
        if (isMasteryPage) {
            if (!document.getElementById('condense-mailbox-btn')) {
                addCondenseButton();
            }
        } else {
            // Remove button if not on mastery page and reset state
            const existingButton = document.getElementById('condense-mailbox-btn');
            if (existingButton) {
                existingButton.remove();
            }
            // Reset state when leaving page
            isCondensed = false;
            originalItems = [];
        }
    }

    function addCondenseButton() {
        const mailboxContainer = document.querySelector('.list-block-search');

        if (!mailboxContainer || document.getElementById('condense-mailbox-btn')) {
            return;
        }

        const button = document.createElement('button');
        button.id = 'condense-mailbox-btn';
        updateButtonText(button);
        button.style.cssText = `
            background: #2196F3;
            color: white;
            border: none;
            padding: 8px 16px;
            margin: 10px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.2);
        `;

        button.addEventListener('click', toggleMailboxCondensation);
        button.addEventListener('mouseover', () => {
            button.style.background = '#1976D2';
        });
        button.addEventListener('mouseout', () => {
            button.style.background = '#2196F3';
        });

        mailboxContainer.insertBefore(button, mailboxContainer.firstChild);
    }

    function updateButtonText(button) {
        button.textContent = isCondensed ? 'Expand Mailbox' : 'Condense Mailbox';
    }

    function toggleMailboxCondensation() {
        const button = document.getElementById('condense-mailbox-btn');

        if (isCondensed) {
            uncondenseMailbox();
        } else {
            condenseMailbox();
        }

        updateButtonText(button);
    }

    function condenseMailbox() {
        const mailboxList = document.querySelector('.list-block-search > ul');
        if (!mailboxList) return;

        const items = Array.from(mailboxList.querySelectorAll('li'));
        if (items.length === 0) return;

        // Store original items for uncondensing
        originalItems = items.map(item => item.cloneNode(true));

        let condensedGroups = [];
        let currentGroup = [];

        for (let i = 0; i < items.length; i++) {
            const item = items[i];
            const itemData = extractItemData(item);

            if (!itemData) {
                // If we can't parse this item, finalize current group and start fresh
                if (currentGroup.length > 0) {
                    condensedGroups.push([...currentGroup]);
                    currentGroup = [];
                }
                condensedGroups.push([item]);
                continue;
            }

            // Check if this item can be grouped with the current group
            if (currentGroup.length === 0) {
                // Start a new group
                currentGroup = [{ element: item, data: itemData }];
            } else {
                const lastItemData = currentGroup[currentGroup.length - 1].data;

                if (canGroup(lastItemData, itemData)) {
                    // Add to current group
                    currentGroup.push({ element: item, data: itemData });
                } else {
                    // Finalize current group and start new one
                    condensedGroups.push([...currentGroup]);
                    currentGroup = [{ element: item, data: itemData }];
                }
            }
        }

        // Don't forget the last group
        if (currentGroup.length > 0) {
            condensedGroups.push(currentGroup);
        }

        // Now process the groups and condense where applicable
        processGroups(condensedGroups, mailboxList);
        isCondensed = true;
    }

    function uncondenseMailbox() {
        const mailboxList = document.querySelector('.list-block-search > ul');
        if (!mailboxList || originalItems.length === 0) return;

        // Clear the mailbox
        mailboxList.innerHTML = '';

        // Restore original items
        originalItems.forEach(item => {
            mailboxList.appendChild(item.cloneNode(true));
        });

        isCondensed = false;
    }

    function extractItemData(item) {
        try {
            const titleElement = item.querySelector('.item-title');
            const afterElement = item.querySelector('.item-after');

            if (!titleElement || !afterElement) return null;

            const titleText = titleElement.textContent.trim();
            const afterText = afterElement.textContent.trim();

            // Extract sender and recipient from title (format: "Sender to Recipient")
            const titleMatch = titleText.match(/^(.+?)\s+to\s+(.+?)(?:\s|$)/);
            if (!titleMatch) return null;

            const sender = titleMatch[1].trim();
            const recipient = titleMatch[2].trim();

            // Extract item info from after element
            const itemMatch = afterText.match(/(.+?)\s+x(\d+)$/);
            if (!itemMatch) return null;

            const itemName = itemMatch[1].trim();
            const quantity = parseInt(itemMatch[2]);

            // Extract timestamp
            const timeMatch = titleText.match(/(\w{3}\s+\d+,\s+\d{2}:\d{2}:\d{2}\s+\w{2})/);
            const timestamp = timeMatch ? timeMatch[1] : '';

            return {
                sender,
                recipient,
                itemName,
                quantity,
                timestamp
            };
        } catch (e) {
            console.error('Error extracting item data:', e);
            return null;
        }
    }

    function canGroup(item1, item2) {
        return item1.sender === item2.sender &&
               item1.recipient === item2.recipient &&
               item1.itemName === item2.itemName;
    }

    function processGroups(groups, mailboxList) {
        // Clear the mailbox
        mailboxList.innerHTML = '';

        groups.forEach(group => {
            if (group.length === 1) {
                // Single item, just add it back
                const item = group[0].element || group[0];
                mailboxList.appendChild(item);
            } else if (group.length > 1 && group[0].data) {
                // Multiple items that can be condensed
                const condensedItem = createCondensedItem(group);
                mailboxList.appendChild(condensedItem);
            } else {
                // Fallback: add all items individually
                group.forEach(item => {
                    const element = item.element || item;
                    mailboxList.appendChild(element);
                });
            }
        });
    }

    function createCondensedItem(group) {
        const firstItem = group[0];
        const totalQuantity = group.reduce((sum, item) => sum + item.data.quantity, 0);

        // Clone the first item as template
        const condensedLi = firstItem.element.cloneNode(true);

        // Add highlight styling for condensed items
        condensedLi.style.cssText = `
            background: linear-gradient(135deg, rgba(33, 150, 243, 0.08) 0%, rgba(33, 150, 243, 0.04) 100%);
            border-left: 3px solid rgba(33, 150, 243, 0.3);
            transition: all 0.2s ease;
        `;

        // Enhanced hover effect
        condensedLi.addEventListener('mouseenter', () => {
            condensedLi.style.background = 'linear-gradient(135deg, rgba(33, 150, 243, 0.12) 0%, rgba(33, 150, 243, 0.06) 100%)';
            condensedLi.style.borderLeftColor = 'rgba(33, 150, 243, 0.5)';
        });

        condensedLi.addEventListener('mouseleave', () => {
            condensedLi.style.background = 'linear-gradient(135deg, rgba(33, 150, 243, 0.08) 0%, rgba(33, 150, 243, 0.04) 100%)';
            condensedLi.style.borderLeftColor = 'rgba(33, 150, 243, 0.3)';
        });

        // Update the quantity in the condensed item
        const afterElement = condensedLi.querySelector('.item-after');
        if (afterElement) {
            const afterText = afterElement.innerHTML;
            const updatedAfterText = afterText.replace(/x\d+/, `x${totalQuantity}`);
            afterElement.innerHTML = updatedAfterText;
        }

        // Add a visual indicator that this is condensed
        const titleElement = condensedLi.querySelector('.item-title');
        if (titleElement && group.length > 1) {
            const span = titleElement.querySelector('span');
            if (span) {
                span.innerHTML += ` <span style="color: #2196F3; font-size: 10px; font-weight: bold;">(${group.length} messages)</span>`;
            }
        }

        // Add hover tooltip showing breakdown
        condensedLi.title = `Condensed from ${group.length} messages:\n` +
                           group.map(item => `${item.data.timestamp}: ${item.data.itemName} x${item.data.quantity}`).join('\n');

        return condensedLi;
    }
    // Run initial check
    checkForMailboxPage();

    // Set up observers to detect URL changes and DOM changes
    // For SPA (Single Page Application) navigation
    let lastUrl = location.href;
    new MutationObserver(() => {
        const url = location.href;
        if (url !== lastUrl) {
            lastUrl = url;
            // Reset state when URL changes
            isCondensed = false;
            originalItems = [];
            checkForMailboxPage();
        }
    }).observe(document, {subtree: true, childList: true});

    // Check periodically as fallback
    setInterval(checkForMailboxPage, 2000);
})();