🎉 Amex Bonus Tracker - 追踪Amex开卡奖励所需消费!【支持油猴】

為啥要這麼麻煩 Amex不都給你了嗎?:full_moon_with_face:

https://global.americanexpress.com/card-benefits/detail/year-end-summary/optima-legacy

Like dis 網圖

3 个赞

对,可以有一个保守数值

感觉不如我问客服 :troll:

+1 客服可以给算的

:grinning: 楼主好,请问将来有计划实现针对不同消费类别的统计吗?

理论上可以支持,不过不能只能从html上去读element。要尝试发request去拿完整的数据,逻辑会很不一样,等我有空研究下,哈哈哈!

1 个赞

谢谢楼主!

谢谢lz!

厉害厉害

我反正直接sum(还掉的所有钱) - 年费
就完事了 这样直接确保net付给银行的钱达到要求,避免各种几块几块的credit作妖

2 个赞

image

这个应该也该exclude吧

1 个赞

是的是的,可以更新下list。找到一个source of truth的方法,可以看request里的transaction,会比前端这个方法更准确。不过最近太忙了,没时间更新:rofl:

1 个赞

大佬,新的chrome不知道为啥不work了,firefox 还行。隔壁elon的script 也一样,有什么办法吗?

一摸一样。我也是 貌似是自动更新的? 不知道去下载旧版本的chrome 行不行

可以试试直接paste油猴里的代码到console试试看。没带电脑旅行,等我回去再研究研究!

1 个赞

简单加了一个分刷卡人统计的,可以用于 track 副卡消费,有缘人自取(我这个 code 里面挪了一下位置,把 button 和统计显示在 search 下面而不是页面底部了,有时候 transaction 太多了不想翻下去)

// ==UserScript==
// @name         Amex Bonus Tracker
// @namespace    http://tampermonkey.net/
// @version      v1.0.5
// @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

// @downloadURL https://update.greasyfork.org/scripts/495405/Amex%20Bonus%20Tracker.user.js
// @updateURL https://update.greasyfork.org/scripts/495405/Amex%20Bonus%20Tracker.meta.js
// ==/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', // For Gold
    'AMEX DUNKIN\' CREDIT', // For Gold
    'HILTON RESORT CREDIT', // For Aspire
    'AMEX FLIGHT CREDIT', // For Aspire
    'HILTON STATEMENT CREDIT', // For Surpass
  ];

  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 perUserAmounts = {};

    let transactionRows = [];

    childElements.forEach((childElement) => {
      let descriptionElement = childElement.querySelector('.description');
      let userElement = childElement.querySelector('#tooltip-badge-supp-badge');
      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(',', ''));
        let user = userElement && userElement.children[1] && userElement.children[1].innerHTML ? userElement.children[1].innerHTML : 'YOU';
        totalAmount += price;

        if (!excludedDescriptions.includes(description)) {
          includedCount++;
          totalEligibleAmount += price;
          perUserAmounts[user] = (user in perUserAmounts ? perUserAmounts[user] : 0) + price;
          transactionRows.push(createTransactionRow(description, price, '🟢', user));
        } else {
          excludedCount++;
          excludedAmounts[description] += price;
          transactionRows.push(createTransactionRow(description, price, '🔴', user));
        }
      }
    });

    let summaryRows = [
      createSummaryRow('Total Eligible Spending for Bonus:', totalEligibleAmount, '🟢', ''),
      createSummaryRow('Total Amount:', totalAmount, '🟡', ''),
      createSummaryRow('Included Transactions:', includedCount, '🟡', '', false),
      createSummaryRow('Excluded Transactions:', excludedCount, '🟡', '', false),
    ];

    Object.keys(perUserAmounts).forEach(user => {
      summaryRows.push(createSummaryRow('Per User Eligible Spending', perUserAmounts[user], '🟢', user));
    });

    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-control-wrapper"]');

    if (targetElement) {
      targetElement.parentNode.insertBefore(resultContainer, targetElement.nextSibling);
    } else {
      console.log('Target element not found');
    }
  }

  function createTransactionRow(description, amount, emoji, user) {
    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-middle" style="width: 100%;">
    <p data-module-name="axp-activity-balance/User" class="body-2-v">
      ${user}
    </p>
  </td>
  <td headers="header-blank2-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, user, 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-middle" style="width: 100%;">
    <p data-module-name="axp-activity-balance/User" class="body-2-v">
      ${user}
    </p>
  </td>
  <td headers="header-blank2-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-control-wrapper"]');
    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);
})();

没测,反正我这里 work :yaoming: 效果图:

另外还把 Gold 的 Dunkin credit 也给加进去了

3 个赞

测试了下好像可以用

楼主想不想冲击钛金,有个可以用request算出精准spend的方法 :troll:

还有这种操作? :jingya: 是类似邮件里面实时更新的tracker那种吗,之前研究过邮件里面的URL,没研究明白

这个好像是amex自己track各种benefit的js 但实在是太长了看看大佬(们)能不能操作一下 :doge:

https://www.aexp-static.com/cdaas/one-app/modules/axp-benefits-trackers/4.3.18/axp-benefits-trackers.browser.js