// ==UserScript==
// @name Twitch Enhancements
// @namespace http://tampermonkey.net/
// @version 0.5.3
// @description Automatically claim channel points, enable theater mode, claim prime rewards, claim drops, and add redeem buttons for GOG and Legacy Games on Twitch and Amazon Gaming websites.
// @author JJJ
// @match https://www.twitch.tv/*
// @match https://gaming.amazon.com/*
// @match https://www.twitch.tv/drops/inventory*
// @match https://www.gog.com/en/redeem
// @match https://promo.legacygames.com/*
// @icon https://th.bing.com/th/id/R.d71be224f193da01e7e499165a8981c5?rik=uBYlAxJ4XyXmJg&riu=http%3a%2f%2fpngimg.com%2fuploads%2ftwitch%2ftwitch_PNG28.png&ehk=PMc5m5Fil%2bhyq1zilk3F3cuzxSluXFBE80XgxVIG0rM%3d&risl=&pid=ImgRaw&r=0
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// Add logger configuration
const Logger = {
styles: {
info: 'color: #2196F3; font-weight: bold',
warning: 'color: #FFC107; font-weight: bold',
success: 'color: #4CAF50; font-weight: bold',
error: 'color: #F44336; font-weight: bold'
},
prefix: '[TwitchEnhancements]',
getTimestamp() {
return new Date().toISOString().split('T')[1].slice(0, -1);
},
info(msg) {
console.log(`%c${this.prefix} ${this.getTimestamp()} - ${msg}`, this.styles.info);
},
warning(msg) {
console.warn(`%c${this.prefix} ${this.getTimestamp()} - ${msg}`, this.styles.warning);
},
success(msg) {
console.log(`%c${this.prefix} ${this.getTimestamp()} - ${msg}`, this.styles.success);
},
error(msg) {
console.error(`%c${this.prefix} ${this.getTimestamp()} - ${msg}`, this.styles.error);
}
};
// Twitch Constants
const PLAYER_SELECTOR = '.video-player';
const THEATER_MODE_BUTTON_SELECTOR = 'button[aria-label="Modo cine (alt+t)"], button[aria-label="Theatre Mode (alt+t)"]';
const CLOSE_MENU_BUTTON_SELECTOR = 'button[aria-label="Close Menu"]';
const CLOSE_MODAL_BUTTON_SELECTOR = 'button[aria-label="Close modal"]';
const THEATER_MODE_CLASS = 'theatre-mode';
const CLAIMABLE_BONUS_SELECTOR = '.claimable-bonus__icon';
const CLAIM_DROPS_SELECTOR = 'button.ScCoreButton-sc-ocjdkq-0.eWlfQB';
const PRIME_REWARD_SELECTOR = 'button.tw-interactive.tw-button.tw-button--full-width[data-a-target="buy-box_call-to-action"] span.tw-button__text div.tw-inline-block p.tw-font-size-5.tw-md-font-size-4[title="Get game"]';
const PRIME_REWARD_SELECTOR_2 = 'p.tw-font-size-5.tw-md-font-size-4[data-a-target="buy-box_call-to-action-text"][title="Get game"]';
// Redeem on GOG Constants
const GOG_REDEEM_CODE_INPUT_SELECTOR = '#codeInput';
const GOG_CONTINUE_BUTTON_SELECTOR = 'button[type="submit"][aria-label="Proceed to the next step"]';
const GOG_FINAL_REDEEM_BUTTON_SELECTOR = 'button[type="submit"][aria-label="Redeem the code"]';
// Redeem on Legacy Games Constants
const LEGACY_GAMES_REDEEM_URL = 'https://promo.legacygames.com/royal-romances-cursed-hearts-ce-prime-deal/';
const LEGACY_GAMES_CODE_INPUT_SELECTOR = '#primedeal_game_code';
const LEGACY_GAMES_EMAIL_INPUT_SELECTOR = '#primedeal_email';
const LEGACY_GAMES_EMAIL_VALIDATE_INPUT_SELECTOR = '#primedeal_email_validate';
const LEGACY_GAMES_SUBMIT_BUTTON_SELECTOR = '#submitbutton';
const LEGACY_GAMES_NEWSLETTER_CHECKBOX_SELECTOR = '#primedeal_newsletter';
let claiming = false;
// Check if MutationObserver is supported
const MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
// Function to click a button
function clickButton(buttonSelector) {
const observer = new MutationObserver((mutationsList, observer) => {
for (let mutation of mutationsList) {
if (mutation.addedNodes.length) {
const button = document.querySelector(buttonSelector);
if (button) {
button.click();
observer.disconnect();
return;
}
}
}
});
observer.observe(document, { childList: true, subtree: true });
}
// Function to enable theater mode
function enableTheaterMode() {
const player = document.querySelector(PLAYER_SELECTOR);
if (player) {
if (!player.classList.contains(THEATER_MODE_CLASS)) {
clickButton(THEATER_MODE_BUTTON_SELECTOR);
}
} else {
Logger.error('Player not found');
}
}
// Function to hide the global menu
function hideGlobalMenu() {
const GLOBAL_MENU_SELECTOR = 'div.ScBalloonWrapper-sc-14jr088-0.eEhNFm';
const globalMenu = document.querySelector(GLOBAL_MENU_SELECTOR);
if (globalMenu) {
globalMenu.style.display = 'none';
} else {
Logger.error('Global menu not found');
}
}
// Function to automatically claim channel points
function autoClaimBonus() {
if (MutationObserver) {
Logger.info('Auto claimer is enabled.');
let observer = new MutationObserver(mutationsList => {
for (let mutation of mutationsList) {
if (mutation.type === 'childList') {
let bonus = document.querySelector(CLAIMABLE_BONUS_SELECTOR);
if (bonus && !claiming) {
bonus.click();
let date = new Date();
claiming = true;
setTimeout(() => {
Logger.success('Claimed at ' + date.toLocaleString());
claiming = false;
}, Math.random() * 1000 + 2000);
}
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
} else {
Logger.warning('MutationObserver is not supported in this browser.');
}
}
// Function to claim prime rewards with retry
function claimPrimeReward() {
const maxAttempts = 5;
let attempts = 0;
const tryClaim = () => {
if (attempts >= maxAttempts) {
Logger.warning('Max attempts reached for claiming prime reward');
return;
}
attempts++;
const element = document.querySelector(PRIME_REWARD_SELECTOR) || document.querySelector(PRIME_REWARD_SELECTOR_2);
if (element) {
element.click();
Logger.success('Prime reward claimed');
} else {
Logger.info(`Attempt ${attempts}/${maxAttempts}: Waiting for prime reward button...`);
setTimeout(tryClaim, 1000);
}
};
setTimeout(tryClaim, 2000);
}
// Function to claim drops
function claimDrops() {
var onMutate = function (mutationsList) {
mutationsList.forEach(mutation => {
if (document.querySelector(CLAIM_DROPS_SELECTOR)) document.querySelector(CLAIM_DROPS_SELECTOR).click();
})
}
var observer = new MutationObserver(onMutate);
observer.observe(document.body, { childList: true, subtree: true });
}
// Function to add the "Redeem on GOG" button
function addGogRedeemButton() {
const claimCodeButton = document.querySelector('p[title="Claim Code"]');
if (claimCodeButton && !document.querySelector('.gog-redeem-button')) {
const claimCodeWrapper = claimCodeButton.closest('.claim-button-wrapper');
if (claimCodeWrapper) {
const gogRedeemButtonDiv = document.createElement('div');
gogRedeemButtonDiv.className = 'claim-button tw-align-self-center gog-redeem-button';
const gogRedeemButton = document.createElement('a');
gogRedeemButton.href = 'https://www.gog.com/en/redeem';
gogRedeemButton.rel = 'noopener noreferrer';
gogRedeemButton.className = 'tw-interactive tw-button tw-button--full-width';
gogRedeemButton.dataset.aTarget = 'redeem-on-gog';
gogRedeemButton.innerHTML = '<span class="tw-button__text" data-a-target="tw-button-text"><div class="tw-inline-flex"><p class="" title="Redeem on GOG">Redeem on GOG</p> <figure aria-label="ExternalLinkWithBox" class="tw-svg"><svg class="tw-svg__asset tw-svg__asset--externallinkwithbox tw-svg__asset--inherit" width="12px" height="12px" version="1.1" viewBox="0 0 11 11" x="0px" y="0px"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.3125 6.875V9.625C10.3125 10.3844 9.69689 11 8.9375 11H1.375C0.615608 11 0 10.3844 0 9.625V2.0625C0 1.30311 0.615608 0.6875 1.375 0.6875H4.125V2.0625H1.375V9.625H8.9375V6.875H10.3125ZM9.62301 2.34727L5.29664 6.67364L4.32437 5.70136L8.65073 1.375H6.18551V0H10.998V4.8125H9.62301V2.34727Z"></path></svg></figure></div></span>';
gogRedeemButtonDiv.appendChild(gogRedeemButton);
claimCodeWrapper.appendChild(gogRedeemButtonDiv);
gogRedeemButton.addEventListener('click', function (e) {
e.preventDefault();
const codeInput = document.querySelector('input[aria-label]');
if (codeInput) {
const code = codeInput.value;
if (code) {
navigator.clipboard.writeText(code).then(function () {
window.location.href = 'https://www.gog.com/en/redeem';
});
}
}
});
const style = document.createElement('style');
style.innerHTML = `
.claim-button-wrapper {
display: flex;
flex-direction: column;
margin-top: 15px;
}
.claim-button,
.gog-redeem-button {
margin: 5px 0;
}
.tw-mg-l-1 {
margin-top: 10px;
}
.claimable-item {
flex-direction: column !important;
gap: 15px;
}
.tw-flex-grow-1 {
width: 100%;
}
`;
document.head.appendChild(style);
}
}
}
// Function to redeem code on GOG
function redeemCodeOnGOG() {
navigator.clipboard.readText().then(function (code) {
const codeInput = document.querySelector(GOG_REDEEM_CODE_INPUT_SELECTOR);
if (codeInput) {
codeInput.value = code;
// Simulate input event to ensure any listeners are triggered
const inputEvent = new Event('input', { bubbles: true });
codeInput.dispatchEvent(inputEvent);
// Click the continue button after a short delay
setTimeout(() => {
const continueButton = document.querySelector(GOG_CONTINUE_BUTTON_SELECTOR);
if (continueButton) {
continueButton.click();
// Wait for the "Redeem" button to appear and click it
const checkRedeemButton = setInterval(() => {
const redeemButton = document.querySelector(GOG_FINAL_REDEEM_BUTTON_SELECTOR);
if (redeemButton) {
clearInterval(checkRedeemButton);
redeemButton.click();
}
}, 500); // Check every 500ms for the Redeem button
}
}, 500); // Adjust the delay as needed
}
}).catch(function (err) {
Logger.error('Failed to read clipboard contents: ' + err);
});
}
// Function to add the "Redeem on Legacy Games" button
function addLegacyGamesRedeemButton() {
const copyCodeButton = document.querySelector('button[aria-label="Copy code to your clipboard"]');
if (copyCodeButton && !document.querySelector('.legacy-games-redeem-button')) {
const copyCodeWrapper = copyCodeButton.closest('.copy-button-wrapper');
if (copyCodeWrapper) {
const legacyGamesRedeemButtonDiv = document.createElement('div');
legacyGamesRedeemButtonDiv.className = 'copy-button tw-align-self-center legacy-games-redeem-button';
const legacyGamesRedeemButton = document.createElement('button');
legacyGamesRedeemButton.ariaLabel = 'Redeem on Legacy Games';
legacyGamesRedeemButton.className = 'tw-interactive tw-button tw-button--full-width';
legacyGamesRedeemButton.dataset.aTarget = 'redeem-on-legacy-games';
legacyGamesRedeemButton.innerHTML = '<span class="tw-button__text" data-a-target="tw-button-text">Redeem on Legacy Games</span>';
legacyGamesRedeemButtonDiv.appendChild(legacyGamesRedeemButton);
copyCodeWrapper.appendChild(legacyGamesRedeemButtonDiv);
legacyGamesRedeemButton.addEventListener('click', function (e) {
e.preventDefault();
const codeInput = document.querySelector('input[aria-label]');
if (codeInput) {
const code = codeInput.value;
if (code) {
navigator.clipboard.writeText(code).then(function () {
const email = GM_getValue('legacyGamesEmail', null);
if (!email) {
const userEmail = prompt('Please enter your email address:');
if (userEmail) {
GM_setValue('legacyGamesEmail', userEmail);
window.location.href = LEGACY_GAMES_REDEEM_URL;
}
} else {
window.location.href = LEGACY_GAMES_REDEEM_URL;
}
});
}
}
});
const style = document.createElement('style');
style.innerHTML = `
.copy-button-wrapper {
display: flex;
flex-direction: column;
margin-top: 15px;
}
.copy-button,
.legacy-games-redeem-button {
margin: 5px 0;
}
.tw-mg-l-1 {
margin-top: 10px;
}
.claimable-item {
flex-direction: column !important;
gap: 15px;
}
.tw-flex-grow-1 {
width: 100%;
}
`;
document.head.appendChild(style);
}
}
}
// Function to redeem code on Legacy Games
function redeemCodeOnLegacyGames() {
const maxAttempts = 5;
let attempts = 0;
const tryRedeem = () => {
if (attempts >= maxAttempts) return;
attempts++;
navigator.clipboard.readText().then(function (code) {
const codeInput = document.querySelector(LEGACY_GAMES_CODE_INPUT_SELECTOR);
const emailInput = document.querySelector(LEGACY_GAMES_EMAIL_INPUT_SELECTOR);
const emailValidateInput = document.querySelector(LEGACY_GAMES_EMAIL_VALIDATE_INPUT_SELECTOR);
const submitButton = document.querySelector(LEGACY_GAMES_SUBMIT_BUTTON_SELECTOR);
const newsletterCheckbox = document.querySelector(LEGACY_GAMES_NEWSLETTER_CHECKBOX_SELECTOR);
const email = GM_getValue('legacyGamesEmail', null);
if (!codeInput || !emailInput || !emailValidateInput || !submitButton) {
Logger.info('Waiting for elements to load...');
setTimeout(tryRedeem, 1000);
return;
}
if (email && code) {
// Fill in the form
codeInput.value = code;
emailInput.value = email;
emailValidateInput.value = email;
// Ensure newsletter checkbox is unchecked
if (newsletterCheckbox) {
newsletterCheckbox.checked = false;
}
// Trigger input events
[codeInput, emailInput, emailValidateInput].forEach(input => {
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
});
// Submit the form
setTimeout(() => {
submitButton.click();
Logger.success('Form submitted with code: ' + code + ' and email: ' + email);
}, 500);
}
}).catch(function (err) {
Logger.error('Failed to read clipboard contents: ' + err);
});
};
// Start the redemption process
setTimeout(tryRedeem, 2000);
}
// Function to open all "Claim Game" buttons in new tabs
function openClaimGameTabs() {
const claimGameButtons = document.querySelectorAll('div[data-a-target="tw-core-button-label-text"].Layout-sc-1xcs6mc-0.bFxzAY');
claimGameButtons.forEach(button => {
const parentButton = button.closest('a');
if (parentButton) {
window.open(parentButton.href, '_blank');
}
});
}
if (window.location.hostname === 'gaming.amazon.com') {
const observer = new MutationObserver((mutations, obs) => {
const claimCodeButton = document.querySelector('p[title="Claim Code"]');
if (claimCodeButton) {
addGogRedeemButton();
}
const copyCodeButton = document.querySelector('button[aria-label="Copy code to your clipboard"]');
if (copyCodeButton) {
addLegacyGamesRedeemButton();
}
});
observer.observe(document, {
childList: true,
subtree: true
});
addGogRedeemButton();
addLegacyGamesRedeemButton();
}
if (window.location.hostname === 'www.gog.com' && window.location.pathname === '/en/redeem') {
window.addEventListener('load', redeemCodeOnGOG);
}
if (window.location.hostname === 'promo.legacygames.com') {
window.addEventListener('load', redeemCodeOnLegacyGames);
}
setTimeout(enableTheaterMode, 1000);
setTimeout(autoClaimBonus, 1000);
setTimeout(claimPrimeReward, 1000);
setTimeout(() => clickButton(CLOSE_MENU_BUTTON_SELECTOR), 1000);
setTimeout(() => clickButton(CLOSE_MODAL_BUTTON_SELECTOR), 1000);
setTimeout(hideGlobalMenu, 1000);
setTimeout(claimDrops, 1000);
setInterval(function () {
if (window.location.href.startsWith('https://www.twitch.tv/drops/inventory')) {
window.location.reload();
}
}, 15 * 60000);
let o = new MutationObserver((m) => {
let script = document.createElement("script");
script.innerHTML = `
// Add logger configuration for client-side script
const Logger = {
styles: {
info: 'color: #2196F3; font-weight: bold',
warning: 'color: #FFC107; font-weight: bold',
success: 'color: #4CAF50; font-weight: bold',
error: 'color: #F44336; font-weight: bold'
},
prefix: '[TwitchEnhancements]',
getTimestamp() {
return new Date().toISOString().split('T')[1].slice(0, -1);
},
info(msg) {
console.log(\`%c\${this.prefix} \${this.getTimestamp()} - \${msg}\`, this.styles.info);
},
warning(msg) {
console.warn(\`%c\${this.prefix} \${this.getTimestamp()} - \${msg}\`, this.styles.warning);
},
success(msg) {
console.log(\`%c\${this.prefix} \${this.getTimestamp()} - \${msg}\`, this.styles.success);
},
error(msg) {
console.error(\`%c\${this.prefix} \${this.getTimestamp()} - \${msg}\`, this.styles.error);
}
};
const openClaimGameTabs = () => {
// More specific selector targeting only prime offer buttons
const allButtonTexts = document.querySelectorAll('div[data-a-target="tw-core-button-label-text"]');
// Filter buttons to only include those with text "Claim Game" or just "Claim"
const claimGameButtons = Array.from(allButtonTexts).filter(button => {
const text = button.textContent.trim();
return (text === "Claim Game" || text === "Claim") &&
button.closest('a') && // Must be inside an anchor tag
button.closest('.prime-offer'); // Must be inside a prime offer
});
Logger.info(\`Found \${claimGameButtons.length} valid claim buttons\`);
// Open each valid claim button in a new tab
claimGameButtons.forEach(button => {
const parentButton = button.closest('a');
if (parentButton && parentButton.href &&
(parentButton.href.includes('gaming.amazon.com') ||
parentButton.href.includes('?ingress=twch'))) {
window.open(parentButton.href, '_blank');
}
});
};
const removeClaimedItems = () => {
// Find ALL items in the list, not just claimed ones
const allItems = document.querySelectorAll('.prime-offer');
let dismissedCount = 0;
let dismissButtons = [];
Logger.info(\`Found \${allItems.length} total items to dismiss\`);
// First collect all dismiss buttons - use multiple methods to ensure we catch all
// Method 1: Find buttons by attribute and data target
document.querySelectorAll('button[aria-label="Dismiss"][data-a-target="prime-offer-dismiss-button"]').forEach(btn => {
dismissButtons.push(btn);
});
// Method 2: Find buttons by test selector attribute as backup
document.querySelectorAll('button[data-test-selector="prime-offer-dismiss-button"]').forEach(btn => {
if (!dismissButtons.includes(btn)) {
dismissButtons.push(btn);
}
});
// Method 3: Find by class and structure if the above methods miss any
document.querySelectorAll('.prime-offer__dismiss button').forEach(btn => {
if (!dismissButtons.includes(btn)) {
dismissButtons.push(btn);
}
});
// Deduplicate just in case
dismissButtons = [...new Set(dismissButtons)];
Logger.info(\`Found \${dismissButtons.length} dismiss buttons to click\`);
// Process dismiss buttons with a delay to avoid UI lockups
if (dismissButtons.length > 0) {
const clickNextButton = (index) => {
if (index < dismissButtons.length) {
try {
dismissButtons[index].click();
dismissedCount++;
// Show progress in console
if (dismissedCount % 5 === 0 || dismissedCount === dismissButtons.length) {
Logger.info(\`Dismissed \${dismissedCount} of \${dismissButtons.length} items...\`);
}
} catch (e) {
Logger.error(\`Error clicking button \${index}: \` + e);
}
// Schedule next button click with a small delay
setTimeout(() => clickNextButton(index + 1), 75);
} else {
Logger.success(\`Completed! Dismissed \${dismissedCount} items total.\`);
// Look for any dismiss buttons that might have been missed
const remainingButtons = document.querySelectorAll('button[aria-label="Dismiss"]');
if (remainingButtons.length > 0) {
Logger.warning(\`Found \${remainingButtons.length} additional buttons to try\`);
// Try to click any remaining dismiss buttons as a final pass
remainingButtons.forEach(btn => {
try {
btn.click();
dismissedCount++;
} catch(e) {}
});
Logger.success(\`Final dismissal count: \${dismissedCount}\`);
}
}
};
// Start the dismissal process
clickNextButton(0);
} else {
Logger.warning('No dismiss buttons found to click');
// Last attempt fallback - try to find any button with "Dismiss" in aria-label
const fallbackButtons = document.querySelectorAll('button[aria-label="Dismiss"]');
if (fallbackButtons.length > 0) {
Logger.warning(\`Fallback: Found \${fallbackButtons.length} buttons with aria-label="Dismiss"\`);
fallbackButtons.forEach(btn => {
try {
btn.click();
dismissedCount++;
} catch(e) {}
});
Logger.success(\`Fallback dismissal completed: \${dismissedCount} items dismissed\`);
}
}
};
`;
document.getElementById("PrimeOfferPopover-header").innerHTML = "";
document.getElementById("PrimeOfferPopover-header").appendChild(script);
document.getElementById("PrimeOfferPopover-header").innerHTML += `
<div style="display: flex; gap: 10px; margin-bottom: 10px;">
<input type='button' style='border: none; background-color: #9147ff; color: white; padding: 10px 20px; font-size: 14px; border-radius: 4px; cursor: pointer; flex: 1;'
class='tw-align-items-center tw-align-middle tw-border-bottom-left-radius-medium tw-border-bottom-right-radius-medium tw-border-top-left-radius-medium tw-border-top-right-radius-medium tw-core-button tw-core-button--primary tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative'
value='Claim All'
onclick='openClaimGameTabs();'>
<input type='button' style='border: none; background-color: #772ce8; color: white; padding: 10px 20px; font-size: 14px; border-radius: 4px; cursor: pointer; flex: 1;'
class='tw-align-items-center tw-align-middle tw-border-bottom-left-radius-medium tw-border-bottom-right-radius-medium tw-border-top-left-radius-medium tw-border-top-right-radius-medium tw-core-button tw-core-button--primary tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative'
value='Remove All'
onclick='removeClaimedItems();'>
</div>
`;
});
o.observe(document.body, { childList: true });
})();