您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Condense duplicate items in FRPG mailbox log
// ==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); })();