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.

// ==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.');

})();