FRPG Mailbox Condenser

Condense duplicate items in FRPG mailbox log

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 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);
})();