Greasy Fork

Greasy Fork is available in English.

Aliexpress Coins history Sum Up

Adds a count to "Store daily check-in" entries per day, displays coin sums per day (+<store sum> store | +<total sum> total) next to the date, and adjusts the history panel width for better readability.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Aliexpress Coins history Sum Up
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Adds a count to "Store daily check-in" entries per day, displays coin sums per day (+<store sum> store | +<total sum> total) next to the date, and adjusts the history panel width for better readability.
// @author       syncNtune
// @match        https://www.aliexpress.com/p/coin-pc-index/mycoin.html
// @icon         https://www.google.com/s2/favicons?sz=64&domain=aliexpress.com
// @license      MIT // Added license
// @grant        none
// ==/UserScript==


(function() {
    'use strict';

    console.log('Script started: ALI NEW 3 Counting Store daily check-in & Sums');

    // --- Configuration and Variables ---
    let processTimeout = null; // Variable to hold the timeout ID for delayed processing
    const storeCheckinTitleMarker = 'Store daily check-in';
    const dailySummaryClass = 'daily-summary-text-userscript'; // Unique class for our sum span

    // CSS Selectors for elements we interact with
    const historyContainerSelector = '.coin-history-content-data-list-content';
    const dateDivSelector = '.data-history-item-date';
    const contentItemSelector = '.data-history-item-content';
    const titleSpanSelector = '.data-history-item-content-title';
    const numSpanSelector = '.data-history-item-content-num';
    const mainHistoryDivSelector = '.coin-history'; // Selector for the div to change width

    // Desired width for the main history panel
    const targetWidth = '350px'; // Adjust this value if needed


    // --- Function to modify the main history div width ---
    function setHistoryDivWidth() {
        const historyDiv = document.querySelector(mainHistoryDivSelector);
        if (historyDiv) {
            console.log(`ACTION: Found "${mainHistoryDivSelector}" div. Setting width to ${targetWidth}.`);
            historyDiv.style.width = targetWidth;
             // Use setProperty with !important if needed to override AliExpress's CSS
             // historyDiv.style.setProperty('width', targetWidth, 'important');
        } else {
            console.log(`INFO: "${mainHistoryDivSelector}" div not found yet for width adjustment.`);
        }
    }


    // --- Function to process the check-in items and calculate sums ---
    // This function iterates through date groups, collects items per day,
    // calculates sums, numbers 'Store daily check-in' items, and adds sum text to the date header.
    // It's designed to be safe to call multiple times (idempotent for already processed items/sums).
    function processCheckInItems() {
        console.log('--- Processing history items started ---');
        const container = document.querySelector(historyContainerSelector);

        if (!container) {
            console.log(`INFO: History container "${historyContainerSelector}" not found.`);
            return false; // Indicate container wasn't found
        }

        const dateDivs = container.querySelectorAll(dateDivSelector);
        if (dateDivs.length === 0) {
            console.log(`INFO: No date entries "${dateDivSelector}" found within the container.`);
             return true; // Indicate processing finished (even if nothing found)
        }

        console.log(`INFO: Found ${dateDivs.length} date entries. Starting group processing...`);

        let totalCheckinsNumberedInRun = 0; // Track items numbered *in this specific call*

        // Iterate through each date div to group items by day
        // Iterating forwards to easily collect subsequent items for the day.
        const dateDivsArray = Array.from(dateDivs);
        for (let i = 0; i < dateDivsArray.length; i++) {
            const dateDiv = dateDivsArray[i];
            const dateText = dateDiv.textContent.trim();

            // --- Cleanup previous sum span on date header ---
            // Find and remove our previously added sum span if it exists
            const existingSummarySpan = dateDiv.querySelector('.' + dailySummaryClass);
            if (existingSummarySpan) {
                 // console.log(`DEBUG: Removing existing sum span from date "${dateText}".`); // Too verbose
                 existingSummarySpan.remove();
            }

            // console.log(`DEBUG: Processing date group for: ${dateText}`); // Too verbose

            let dailyStoreCheckinSum = 0;
            let dailyTotalSum = 0;
            let dailyCheckinCounter = 1; // Counter specific to check-ins for *this* day
            const itemsForThisDate = [];

            // Collect all content items that belong to this date (until the next date div or end)
            let currentElement = dateDiv.nextElementSibling;
            while (currentElement && !currentElement.classList.contains(dateDivSelector.substring(1))) { // Check class without the '.'
                if (currentElement.classList.contains(contentItemSelector.substring(1))) { // Check class without the '.'
                     itemsForThisDate.push(currentElement); // Add to array
                }
                currentElement = currentElement.nextElementSibling;
            }
             // console.log(`DEBUG: Finished collecting items for date "${dateText}". Found ${itemsForThisDate.length} items.`); // Too verbose

            // --- Process collected items for sums and numbering ---
            // Iterate through the collected items to calculate sums and identify/number check-ins
            itemsForThisDate.forEach(itemElement => {
                const titleDiv = itemElement.querySelector(titleSpanSelector);
                const numSpan = itemElement.querySelector(numSpanSelector);

                if (titleDiv && numSpan) {
                    let currentTitle = titleDiv.textContent.trim();
                    const coinText = numSpan.textContent.trim();

                    // Robustly parse the coin amount (handles + or - signs and potential non-numeric chars)
                    const coins = parseInt(coinText.replace(/[^0-9+-]/g, ''), 10); // Base 10

                    if (!isNaN(coins)) {
                        dailyTotalSum += coins; // Sum total coins for the day

                        // Check if it's a "Store daily check-in" item
                        if (currentTitle.includes(storeCheckinTitleMarker)) {
                            dailyStoreCheckinSum += coins; // Sum store check-in coins

                            // --- Handle Numbering ---
                            // Find the exact position of the marker phrase
                            const markerIndex = currentTitle.indexOf(storeCheckinTitleMarker);

                            if (markerIndex !== -1) {
                                // Check the text *immediately after* the marker for our #N format
                                const textAfterMarker = currentTitle.substring(markerIndex + storeCheckinTitleMarker.length).trim();

                                // If it doesn't already start with '#' followed by digits, number it
                                if (!textAfterMarker.match(/^#\d+/)) {
                                    console.log(`ACTION: Numbering "${currentTitle}" for ${dateText}. Assigning count #${dailyCheckinCounter}`);
                                    // Construct the new title by inserting ' #N' after the marker phrase
                                    const newTitle = currentTitle.substring(0, markerIndex + storeCheckinTitleMarker.length)
                                                     + ' #' + dailyCheckinCounter
                                                     + currentTitle.substring(markerIndex + storeCheckinTitleMarker.length); // Add back any text that was after the marker

                                    titleDiv.textContent = newTitle;
                                    totalCheckinsNumberedInRun++; // Increment total count across all days in this run
                                    // console.log(`ACTION: Title changed to "${titleDiv.textContent}".`); // Too verbose
                                } else {
                                     // Item is already numbered from a previous run, just log it (optional)
                                     // console.log(`DEBUG: "${storeCheckinTitleMarker}" item "${currentTitle}" is already numbered. Skipping numbering action for this item.`); // Too verbose
                                }
                            } else {
                                // This case is unexpected if includes() was true, but good for debugging potential issues
                                console.warn(`WARNING: Found item including "${storeCheckinTitleMarker}" but couldn't find marker index? Title: "${currentTitle}"`);
                            }

                            // Increment the daily check-in counter regardless of whether we modified the text
                            // This keeps the sequence correct for all check-in items on this day.
                            dailyCheckinCounter++;
                        }
                    } else {
                         console.log(`INFO: Could not parse coins from "${coinText}" for item "${currentTitle}". Skipping coin sum for this item.`);
                    }
                } else {
                     console.log('INFO: Content item found, but title or num span missing. Skipping processing for this item.');
                }
            }); // End itemsForThisDate loop

            // --- Add sums to the date header ---
            // Create the summary text format: +<store sum> store | +<total sum> total
            const dateSummaryText = ` +${dailyStoreCheckinSum} store | +${dailyTotalSum} total`;

            // Append the summary text inside a new span element to the date div
            // We already removed any existing span at the start of the date loop.
             console.log(`ACTION: Adding summary "${dateSummaryText}" to date "${dateText}".`);
             const summarySpan = document.createElement('span');
             summarySpan.classList.add(dailySummaryClass); // Add our unique class
             summarySpan.style.marginLeft = '10px'; // Add some space
             summarySpan.style.fontWeight = 'normal'; // Reduce emphasis
             summarySpan.style.fontSize = '0.9em'; // Make it slightly smaller
             summarySpan.style.color = '#555'; // Slightly muted color (optional)
             summarySpan.textContent = dateSummaryText;
             dateDiv.appendChild(summarySpan); // Append the new span element


        } // End dateDivs loop

        console.log(`--- Processing finished. Check-ins numbered/updated in this run: ${totalCheckinsNumberedInRun}. ---`);
        return true; // Indicate processing completed
    }

    // --- Function to set up the MutationObserver ---
    // Observes the history container for additions of new history items (like on scroll).
    function setupObserver(targetNode) {
         console.log('INFO: Setting up MutationObserver on the container...');
        const observer = new MutationObserver((mutations) => {
            let needsProcessing = false;
            for (const mutation of mutations) {
                // Check if nodes were added directly to the container
                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                   // Check if any of the added nodes are likely history items (date or content)
                   for (const node of mutation.addedNodes) {
                        // node.nodeType === Node.ELEMENT_NODE ensures it's an element
                        // Check for the class names to ensure it's relevant content
                        if (node.nodeType === Node.ELEMENT_NODE &&
                           (node.classList.contains(dateDivSelector.substring(1)) ||
                            node.classList.contains(contentItemSelector.substring(1))))
                        {
                            console.log('INFO: MutationObserver detected addition of a history item. Scheduling reprocessing.');
                            needsProcessing = true;
                            break; // Found a relevant addition, no need to check other added nodes
                        }
                   }
                   if(needsProcessing) break; // Found a relevant mutation, stop checking this record
                }
            }
            if (needsProcessing) {
                console.log('INFO: Reprocessing scheduled by MutationObserver...');
                // Clear previous timeout if it exists to avoid multiple rapid calls
                if (processTimeout) {
                    clearTimeout(processTimeout);
                }
                // Set a new timeout to process after a short delay
                // This gives the browser/page scripts a moment to finish rendering
                processTimeout = setTimeout(() => {
                    console.log('INFO: Running reprocessing after delay...');
                    processCheckInItems();
                }, 300); // Delay in milliseconds (adjust if needed)
            }
        });

        // Start observing the container for direct child additions
        observer.observe(targetNode, { childList: true });
        console.log('INFO: Observer started on container.');
    }


    // --- Initial Load Handling ---
    // Uses requestAnimationFrame to wait until the browser is ready to paint,
    // which often means the basic DOM structure is in place.
    function waitForContainerAndProcess() {
        const container = document.querySelector(historyContainerSelector);
        if (container) {
            console.log('INFO: History container found. Processing initial content...');
            // Use a small timeout after finding the container to let its children fully render
            setTimeout(() => {
                 processCheckInItems();
                 // Set up observer for future dynamic content loading (like scrolling)
                 setupObserver(container);
            }, 100); // Small delay
        } else {
            console.log('INFO: History container not found yet. Requesting next frame...');
            // If container not found, try again on the next animation frame
            requestAnimationFrame(waitForContainerAndProcess);
        }
    }

    // --- Main Execution Flow ---

    // 1. Adjust the width of the history panel early.
    // Using requestAnimationFrame ensures the basic layout div exists.
    requestAnimationFrame(setHistoryDivWidth);

    // 2. Start the process to find the history list container and process its items.
    // This uses requestAnimationFrame to wait for the container, and then sets up the observer.
    waitForContainerAndProcess(); // Call the initial waiting function

    console.log('Script setup complete. Look for ACTION and INFO messages in the console.');

})();