Greasy Fork is available in English.

Amex Bonus Tracker

This script is used to track amex bunos progress

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Advertisement:

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

Advertisement:

// ==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);
})();