Skip to content

Instantly share code, notes, and snippets.

@tmcw
Last active April 9, 2025 05:57
Show Gist options
  • Save tmcw/64498a6d230a6b6014088dd526a6f814 to your computer and use it in GitHub Desktop.
Save tmcw/64498a6d230a6b6014088dd526a6f814 to your computer and use it in GitHub Desktop.

Tampermonkey userscripts for automatically adding credit card offers

// ==UserScript==
// @name Add all Amex offers
// @namespace http://tampermonkey.net/
// @version 2025-03-22
// @description Read all this code don't trust me. This adds a big button to the offers page.
// @author Tom MacWright
// @match https://global.americanexpress.com/offers/eligible
// @icon https://www.google.com/s2/favicons?sz=64&domain=americanexpress.com
// @grant none
// ==/UserScript==
(function() {
'use strict';
function setup() {
const offers = document.querySelector('section#offers');
const button = document.createElement('button');
button.textContent = 'Add all';
button.onclick = addAll;
button.style = 'background:magenta;color:white;padding:10px';
offers.insertBefore(button, offers.firstChild ?? null);
async function addAll() {
for (let i = 0; i < 200; i++) {
const buttons = document.querySelectorAll('button[title="Add to Card"]');
const first = buttons[0];
if (first) {
button.innerText = 'Clicking button';
first.click();
await new Promise(resolve => setTimeout(resolve, 1000));
const closer = document.querySelector('button[aria-expanded="true"]');
if (closer) closer.click();
} else {
button.innerText = 'No buttons found';
}
}
}
}
(async () => {
// Try to set up for 20 seconds.
for (let i = 0; i < 20; i++) {
try {
setup();
return;
} catch (e) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
})()
})();
// ==UserScript==
// @name Chase offers
// @namespace http://tampermonkey.net/
// @version 2024-07-14
// @description Automatically add chase offers
// @author tmcw
// @match https://secure.chase.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=chase.com
// @grant none
// ==/UserScript==
(function() {
'use strict';
const urlPattern = /https:\/\/secure\.chase\.com\/web\/auth\/dashboard#\/dashboard\/merchantOffers\/offer-hub\?accountId=.*/;
const navigateBack = () => {
window.history.back();
setTimeout(scanAndClick, Math.random() * 1000 + 300);
};
const scanAndClick = () => {
const btns = [...document.querySelectorAll('[role="button"][data-cy="commerce-tile"]')]
.filter(b => b.querySelector('[type=ico_add_circle]'));
const b = btns.pop();
if (!b) {
console.log('added all!');
return;
}
b.childNodes[0].click();
setTimeout(navigateBack, Math.random() * 1000 + 300);
};
const waitForElements = () => {
const observer = new MutationObserver((mutations, observer) => {
if (document.querySelectorAll('[role="button"][data-cy="commerce-tile"]').length > 0) {
observer.disconnect();
scanAndClick();
}
});
observer.observe(document.body, { childList: true, subtree: true });
};
const checkUrlAndRun = () => {
if (urlPattern.test(window.location.href)) {
waitForElements();
}
};
// Initial check
checkUrlAndRun();
// Monitor URL changes
let oldHref = document.location.href;
const body = document.querySelector("body");
const observer = new MutationObserver(mutations => {
if (oldHref !== document.location.href) {
oldHref = document.location.href;
checkUrlAndRun();
}
});
observer.observe(body, { childList: true, subtree: true });
})();
// ==UserScript==
// @name Add all Citi offers
// @namespace http://tampermonkey.net/
// @version 2025-03-22
// @description try to take over the world!
// @author You
// @match https://online.citi.com/US/ag/products-offers/merchantoffers
// @icon https://www.google.com/s2/favicons?sz=64&domain=citi.com
// @grant none
// ==/UserScript==
(function() {
'use strict';
function setup() {
const offers = document.querySelector('app-merchant-offer');
const button = document.createElement('button');
button.textContent = 'Add all';
button.onclick = addAll;
button.style = 'background:magenta;color:white;padding:10px';
offers.insertBefore(button, offers.firstChild ?? null);
async function addAll() {
const loadMoreButtons = [...document.querySelectorAll('button.load-more-button')];
for (const button of loadMoreButtons) {
button.click();
await new Promise(resolve => setTimeout(resolve, 1000));
}
for (let i = 0; i < 200; i++) {
const buttons = [...document.querySelectorAll('button.cds-button')].filter(button => button.textContent === 'Enroll');
const first = buttons[0];
if (first) {
button.innerText = 'Clicking button';
first.click();
await new Promise(resolve => setTimeout(resolve, 2000));
const closer = document.querySelector('button[aria-label="Close"]');
if (closer) closer.click();
} else {
button.innerText = 'No buttons found';
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
}
(async () => {
// Try to set up for 20 seconds.
for (let i = 0; i < 20; i++) {
try {
setup();
return;
} catch (e) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
})()
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment