NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name Twitch Game Search (Steam, ITAD, Nintendo)
// @namespace https://openuserjs.org/users/yoharnu
// @version 2.4.1
// @description Adds Steam, IsThereAnyDeal, and Nintendo eShop search buttons to Twitch stream pages.
// @author yoharnu
// @copyright 2026, yoharnu (https://openuserjs.org/users/yoharnu)
// @license MIT
// @updateURL https://openuserjs.org/meta/yoharnu/Twitch_Game_Search_(Steam,_ITAD,_Nintendo).meta.js
// @downloadURL https://openuserjs.org/install/yoharnu/Twitch_Game_Search_(Steam,_ITAD,_Nintendo).user.js
// @match https://www.twitch.tv/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
const CONTAINER_ID = 'twitch-game-search-container';
// Base64 SVGs for Fallback Protection
const FALLBACKS = {
steam: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iI2M3ZDVlMCI+PHBhdGggZD0iTTEyIDJhMTAgMTAgMCAwIDAtOS42IDYuNkwgMiA4LjFWMTRhMiAyIDAgMCAwIDIgMmgybDQuNCA4LjhBmMTAgMTAgMCAwIDAgMTIgMjJhMTAgMTAgMCAwIDAgMTAtMTBBMTAgMTAgMCAwIDAgMTIgMnpmbTQuNiA1LjdjMS41IDAgMi43IDEuMiAyLjcgMi43cy0xLjIgMi43LTIuNyAyLjctMi43LTEuMi0yLjctMi43IDEuMi0yLjcgMi43LTIuN3oiLz48L3N2Zz4=',
// Rocket shape to match ITAD v2 branding
itad: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iI2ZmZmZmZiI+PHBhdGggZD0iTTE4LjUgMmgtNmMtLjggMC0xLjUuMy0yLjEgOWwtNi40IDYuNGMtMS4yIDEuMi0xLjIgMy4xIDAgNC4ycyTMuMSAxLjIgNC4yIDBsNi40LTYuNGMuNi0uNi45LTEuMy45LTIuMXYtNmMwLTEuMS0uOS0yLTItMnptLTMuNSA1LjVjLS44IDAtMS41LS43LTEuNS0xLjVzLjctMS41IDEuNS0xLjUgMS41LjcgMS41IDEuNS0uNyAxLjUtMS41IDEuNXoiLz48L3N2Zz4=',
nintendo: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iI2ZmZmZmZiI+PHBhdGggZD0iTTIgNmg0djEySDJWMnptMTYgMHYxMmg0VjZoLTR6TTggNnY4bDQtOFY2SDh6Ii8+PC9zdmc+'
};
const PROVIDERS = [
{
id: 'btn-steam',
label: 'Steam',
color: '#171a21', // Steam Dark Blue/Black
textColor: '#c7d5e0',
icon: 'https://store.steampowered.com/favicon.ico',
fallback: FALLBACKS.steam,
urlGen: (game) => `https://store.steampowered.com/search/?term=${encodeURIComponent(game)}`
},
{
id: 'btn-itad',
label: 'ITAD',
// ITAD v2 Brand Blue
color: '#01b2f1',
textColor: '#ffffff',
// Official v2 Icon URL
icon: 'https://isthereanydeal.com/public/icons/favicon-83577ccc.png',
fallback: FALLBACKS.itad,
urlGen: (game) => `https://isthereanydeal.com/search/?q=${encodeURIComponent(game)}`
},
{
id: 'btn-nintendo',
label: 'eShop',
color: '#e60012', // Nintendo Red
textColor: '#ffffff',
icon: 'https://www.nintendo.com/favicon.ico',
fallback: FALLBACKS.nintendo,
// Added &cat=gme to filter strictly for Games
urlGen: (game) => `https://www.nintendo.com/search/?q=${encodeURIComponent(game)}&cat=gme`
}
];
function createButton(provider, gameTitle) {
const btn = document.createElement('a');
btn.href = provider.urlGen(gameTitle);
btn.target = '_blank';
btn.title = `Search ${gameTitle} on ${provider.label}`;
// Button Styles
Object.assign(btn.style, {
display: 'inline-flex',
alignItems: 'center',
marginLeft: '6px',
textDecoration: 'none',
backgroundColor: provider.color,
color: provider.textColor,
padding: '4px 8px',
borderRadius: '4px',
fontSize: '11px',
fontWeight: '600',
fontFamily: 'Helvetica Neue, Helvetica, Arial, sans-serif',
lineHeight: '1',
border: '1px solid rgba(255,255,255,0.1)',
transition: 'all 0.2s ease',
height: '22px',
boxSizing: 'border-box',
cursor: 'pointer'
});
// Hover Effect
btn.onmouseover = () => {
btn.style.opacity = '0.9';
btn.style.transform = 'translateY(-1px)';
};
btn.onmouseout = () => {
btn.style.opacity = '1';
btn.style.transform = 'translateY(0)';
};
// Icon Image
const icon = document.createElement('img');
icon.src = provider.icon;
icon.alt = '';
Object.assign(icon.style, {
width: '14px',
height: '14px',
marginRight: '5px',
verticalAlign: 'middle',
display: 'block'
});
// Fallback Protection
icon.onerror = function() {
console.log(`[TwitchSearch] Icon failed for ${provider.label}, using fallback.`);
this.onerror = null;
this.src = provider.fallback;
};
// Text Span
const text = document.createElement('span');
text.textContent = provider.label;
text.style.verticalAlign = 'middle';
btn.appendChild(icon);
btn.appendChild(text);
return btn;
}
// Helper to find the ACTUAL game link, ignoring "Streaming Together" dashboard links
function getRealGameLink() {
const links = document.querySelectorAll('a[data-a-target="stream-game-link"]');
for (const link of links) {
const href = link.getAttribute('href');
// We only want links that point to a Category or Game directory
if (href && (href.includes('/directory/category/') || href.includes('/directory/game/'))) {
return link;
}
}
return null;
}
function updateLinks() {
// 1. Locate the correct link
const gameLink = getRealGameLink();
if (!gameLink) return;
const gameTitle = gameLink.textContent.trim();
if (!gameTitle) return;
// 2. Manage Container
let container = document.getElementById(CONTAINER_ID);
if (container) {
// Check if we need to update
if (container.getAttribute('data-game') !== gameTitle) {
container.innerHTML = '';
container.setAttribute('data-game', gameTitle);
} else {
// Ensure the container is still in the correct place
if (gameLink.nextSibling !== container) {
gameLink.insertAdjacentElement('afterend', container);
}
return;
}
} else {
// Create new container
container = document.createElement('div');
container.id = CONTAINER_ID;
container.setAttribute('data-game', gameTitle);
Object.assign(container.style, {
display: 'inline-flex',
alignItems: 'center',
marginLeft: '10px'
});
// 3. Insert specificially AFTER the game link
gameLink.insertAdjacentElement('afterend', container);
}
// 4. Populate Buttons
if (container.children.length === 0) {
PROVIDERS.forEach(provider => {
container.appendChild(createButton(provider, gameTitle));
});
}
}
// Observer
const observer = new MutationObserver(() => {
updateLinks();
});
observer.observe(document.body, { childList: true, subtree: true });
})();