為啥要這麼麻煩 Amex不都給你了嗎?
https://global.americanexpress.com/card-benefits/detail/year-end-summary/optima-legacy
Like dis 網圖
為啥要這麼麻煩 Amex不都給你了嗎?
https://global.americanexpress.com/card-benefits/detail/year-end-summary/optima-legacy
Like dis 網圖
对,可以有一个保守数值
感觉不如我问客服
+1 客服可以给算的
楼主好,请问将来有计划实现针对不同消费类别的统计吗?
理论上可以支持,不过不能只能从html上去读element。要尝试发request去拿完整的数据,逻辑会很不一样,等我有空研究下,哈哈哈!
谢谢楼主!
谢谢lz!
厉害厉害
我反正直接sum(还掉的所有钱) - 年费
就完事了 这样直接确保net付给银行的钱达到要求,避免各种几块几块的credit作妖
是的是的,可以更新下list。找到一个source of truth的方法,可以看request里的transaction,会比前端这个方法更准确。不过最近太忙了,没时间更新
大佬,新的chrome不知道为啥不work了,firefox 还行。隔壁elon的script 也一样,有什么办法吗?
一摸一样。我也是 貌似是自动更新的? 不知道去下载旧版本的chrome 行不行
可以试试直接paste油猴里的代码到console试试看。没带电脑旅行,等我回去再研究研究!
简单加了一个分刷卡人统计的,可以用于 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 效果图:
另外还把 Gold 的 Dunkin credit 也给加进去了
测试了下好像可以用
楼主想不想冲击钛金,有个可以用request算出精准spend的方法
还有这种操作? 是类似邮件里面实时更新的tracker那种吗,之前研究过邮件里面的URL,没研究明白
这个好像是amex自己track各种benefit的js 但实在是太长了看看大佬(们)能不能操作一下