您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
This script is used to track amex bunos progress
// ==UserScript== // @name Amex Bonus Tracker // @namespace http://tampermonkey.net/ // @version v1.0.6 // @description This script is used to track amex bunos progress // @author You // @match https://global.americanexpress.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=americanexpress.com // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; const excludedDescriptions = [ 'MEMBERSHIP FEE', 'PAYPAL ACCT PAYMENT RECEIVED - THANK YOU', 'AUTOPAY PAYMENT - THANK YOU', 'CR ADJ FOR BALANCE TRA', 'ONLINE PAYMENT - THANK YOU', 'MOBILE PAYMENT - THANK YOU', 'AMEX AIRLINE FEE REIMBURSEMENT', 'SHOP SAKS WITH PLATINUM CREDIT', 'DELL CREDIT', 'AMEX DINING CREDIT', 'HILTON RESORT CREDIT', 'AMEX FLIGHT CREDIT', 'HILTON STATEMENT CREDIT', 'AMEX DUNKIN\' CREDIT', 'RENEWAL MEMBERSHIP FEE', 'ADJUST MEMBERSHIP FEE', 'MEM RWDS AIRLINE TAX OFFSET FEE', 'RETURN PAYMENT FEE', 'AMEX SEND: TRANSFER TO CARD', 'AMEX SEND: ADD MONEY' ]; function clickUntilGone() { // Get all buttons with the specified class var buttons = document.querySelectorAll('.btn.btn-sm.css-19hct2l'); // If buttons exist, click the first one if (buttons.length > 0) { buttons[0].click(); // Wait for a short delay to allow any potential changes in the DOM setTimeout(clickUntilGone, 200); // Adjust delay as needed } else { console.log('No more buttons to click.'); } } function computeAndRender() { let parentElement = document.querySelector('[data-module-name="axp-activity-feed-transactions-table-transactions"]'); let childElements = parentElement.querySelectorAll('.position-relative'); let totalEligibleAmount = 0; let totalAmount = 0; let includedCount = 0; let excludedCount = 0; // Object to store the amount for each kind of excluded transaction let excludedAmounts = {}; excludedDescriptions.forEach(desc => { excludedAmounts[desc] = 0; }); let transactionRows = []; childElements.forEach((childElement) => { let descriptionElement = childElement.querySelector('.description'); let priceElement = childElement.querySelector('.hidden-md-up.col-sm-4.col-sm-4.pad-responsive-r'); if (descriptionElement && priceElement) { let description = descriptionElement.innerText; let price = parseFloat(priceElement.innerText.replace('$', '').replace(',', '')); totalAmount += price; if (!excludedDescriptions.includes(description)) { includedCount++; totalEligibleAmount += price; transactionRows.push(createTransactionRow(description, price, '🟢')); } else { excludedCount++; excludedAmounts[description] += price; transactionRows.push(createTransactionRow(description, price, '🔴')); } } }); let summaryRows = [ createSummaryRow('Total Eligible Spending for Bonus:', totalEligibleAmount, '🟢'), createSummaryRow('Total Amount:', totalAmount, '🟡'), createSummaryRow('Included Transactions:', includedCount, '🟡', false), createSummaryRow('Excluded Transactions:', excludedCount, '🟡', false), ]; let excludedTransactionRows = Object.entries(excludedAmounts) .filter(([desc, amount]) => amount !== 0) .map(([desc, amount]) => createTransactionRow(desc, amount, '🔴')); // Create a new element to display the results in the desired format let resultContainer = document.createElement('div'); resultContainer.className = 'axp-activity-balance card margin-b section-container transactions-data-table-v'; resultContainer.id = 'amex-welcome-bonus-tracker'; resultContainer.innerHTML = ` <div data-module-name="axp-activity-balance/Header" class="card-block pad-1"> <div class="pad-l col-md-9 col-xs-12"> <h2 class="heading-4 heading-5-v">Amex Welcome Bonus Tracker</h2> </div> </div> <div data-module-name="axp-activity-balance/BalancesDataTable"> <table class="table data-table axp-activity-balance__data-table__dataTable___6Mijm"> <thead class="axp-activity-balance__data-table__tableHeader___ZW3ub"> </thead> <tbody> <tr><td colspan="2"><strong>Summary</strong></td></tr> ${summaryRows.join('')} <tr><td colspan="2"><strong>Amount for each kind of excluded transaction:</strong></td></tr> ${excludedTransactionRows.join('')} <tr><td colspan="2"><strong>Transactions</strong></td></tr> ${transactionRows.join('')} </tbody> </table> </div> `; // Find the target element to insert the result container let targetElement = document.querySelector('div[data-module-name="axp-activity/Content/Range/ActivityMultiBalance"]') ?? document.querySelector('div[data-module-name="axp-activity/Content/Range/ActivityBalance"]') ?? document.querySelector('div[data-module-name="axp-activity-search-activity-balance"]'); if (targetElement) { targetElement.parentNode.insertBefore(resultContainer, targetElement.nextSibling); } else { console.log('Target element not found'); } } function createTransactionRow(description, amount, emoji) { return ` <tr class="row-sm-size"> <td headers="header-blank0-0" class="" style="width: 100%;"> <div class="pad-l body-2-v" data-module-name="axp-activity-balance/BalanceName" style="width: 100%; text-wrap: nowrap;"> <span style="padding-right: 5px; text-transform: capitalize;">${emoji} ${description}</span> </div> </td> <td headers="header-blank1-0" class="text-align-right" style="width: 100%;"> <p data-module-name="axp-activity-balance/BalanceAmount" class="axp-activity-balance__data-table__balanceDisplayTag___MdUas body-2-v ${amount < 0 ? 'dls-green' : ''}"> ${amount >= 0 ? '$' : '-$'}${Math.abs(amount).toFixed(2)} </p> </td> </tr> `; } function createSummaryRow(label, value, emoji, isCurrency = true) { return ` <tr class="row-sm-size"> <td headers="header-blank0-0" class="" style="width: 100%;"> <div class="pad-l body-2-v" data-module-name="axp-activity-balance/BalanceName" style="width: 100%; text-wrap: nowrap;"> <span style="padding-right: 5px; text-transform: capitalize;">${emoji} ${label}</span> </div> </td> <td headers="header-blank1-0" class="text-align-right" style="width: 100%;"> <p data-module-name="axp-activity-balance/BalanceAmount" class="axp-activity-balance__data-table__balanceDisplayTag___MdUas body-2-v ${value < 0 && isCurrency ? 'dls-green' : ''}"> ${isCurrency ? (value >= 0 ? '$' : '-$') + Math.abs(value).toFixed(2) : value} </p> </td> </tr> `; } function removeResultContainer() { let elementToRemove = document.getElementById('amex-welcome-bonus-tracker'); if (elementToRemove) { elementToRemove.remove(); } } function handleMutations(mutationsList, observer) { // Check if the target element is now available let targetElement = document.querySelector('div[data-module-name="axp-activity/Content/Range/ActivityMultiBalance"]') ?? document.querySelector('div[data-module-name="axp-activity/Content/Range/ActivityBalance"]') ?? document.querySelector('div[data-module-name="axp-activity-search-activity-balance"]'); let buttonElement = document.getElementById('amex-welcome-bonus-tracker-button'); if (targetElement && !buttonElement) { // Insert the button if the target element is found let button = document.createElement('button'); button.textContent = 'Show Amex Spending Tracker'; button.id = 'amex-welcome-bonus-tracker-button' // Style the button button.style.padding = '12px 24px'; button.style.fontSize = '16px'; button.style.fontWeight = 'bold'; button.style.color = '#fff'; button.style.backgroundColor = '#006fcf'; button.style.border = 'none'; button.style.borderRadius = '6px'; button.style.cursor = 'pointer'; button.style.transition = 'background-color 0.3s ease'; // Add hover effect button.addEventListener('mouseover', function () { button.style.backgroundColor = '#0057a8'; }); button.addEventListener('mouseout', function () { button.style.backgroundColor = '#006fcf'; }); button.onclick = () => { removeResultContainer(); clickUntilGone() computeAndRender(); }; button.style.margin = '10px 0'; // Add some margin for better spacing targetElement.parentNode.insertBefore(button, targetElement.nextSibling); } } const observerOptions = { childList: true, // Observe changes to the child nodes of the observed element subtree: true, // Include all descendant nodes of the observed element }; // Create a MutationObserver instance const observer = new MutationObserver(handleMutations); // Start observing mutations on the body element observer.observe(document.body, observerOptions); })();