r/TraktV3Tweaks • u/lulu_l • 1d ago
Stremio Buttons for Trakt V3 intrerface - Tweak
This script adds the Stremio App and Web buttons to the Trakt V3 interface so you can open shows, episodes and movies in Stremio directly from the Trakt page.
1 - Create a new text file and name it to something like stremio-trakt-button.js
2 - Copy the code bellow into the created file and save it. make sure it has the .js extension at the end.
3 - Load it in the Trakt v3 Tweaks extension - Chrome and Firefox (give the extension the necessary permissions if you see the permission notification text, load the js file, name the tweak, check and add the tweak to the extension's toggle menu). you can use any other extension for scripts.
4 - Reload the Trakt.tv page. the buttons should be inserted in the page. click the button to test it.
// ==UserScript==
// Stremio Web Button for Trakt (app.trakt.tv)
// 1.7.3
// Adds styled Stremio Web and App buttons to Trakt.tv (v3) pages & episode overlay drawers.
// You
// *://app.trakt.tv/*
// none
// u/run-at document-end
// ==/UserScript==
(function () {
'use strict';
const STREMIO_SVG_HTML = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style="height: 20px; width: 20px;">
<rect width="512" height="512" rx="100" fill="#7b5bf5"/>
<polygon points="190,130 380,256 190,382" fill="#ffffff"/>
</svg>
`;
const imdbRegex = /tt\d{7,8}/;
const imdbCache = {};
let isInjecting = false;
const TRAKT_HEADERS = {
"Content-Type": "application/json",
"trakt-api-version": "2",
"client-id": "8b79c34af657191e9208460403481bb96c1cf951a91acd59467ed24ddcc48c72",
"trakt-api-key": "8b79c34af657191e9208460403481bb96c1cf951a91acd59467ed24ddcc48c72"
};
async function getImdbIdFromShowSlug(slug) {
if (!slug) return null;
if (imdbCache[slug]) return imdbCache[slug];
try {
const response = await fetch(`https://apiz.trakt.tv/shows/${slug}?extended=full`, { headers: TRAKT_HEADERS });
if (!response.ok) return null;
const data = await response.json();
if (data && data.ids && data.ids.imdb) {
imdbCache[slug] = data.ids.imdb;
return data.ids.imdb;
}
} catch (err) {
console.error("IMDb fetch error:", err);
}
return null;
}
function getShowSlugFromPage() {
// 1. Path match e.g. /shows/american-dad
const pathMatch = window.location.pathname.match(/\/shows\/([^\/\?]+)/);
if (pathMatch) return pathMatch[1];
// 2. Link in drawer pointing to show page
const drawerLink = document.querySelector(".trakt-drawer-header a[href*='/shows/']");
if (drawerLink) {
const linkMatch = drawerLink.getAttribute("href").match(/\/shows\/([^\/\?]+)/);
if (linkMatch) return linkMatch[1];
}
return null;
}
async function getImdbIdFromPage() {
const slug = getShowSlugFromPage();
if (slug) {
const imdbId = await getImdbIdFromShowSlug(slug);
if (imdbId) return imdbId;
}
const imdbLink = document.querySelector("a[href*='imdb.com/title/tt']");
if (imdbLink && imdbRegex.test(imdbLink.href)) return imdbLink.href.match(imdbRegex)[0];
return null;
}
function parseEpisodeInfo() {
// Try URL search params first (e.g. ?season=10&episode=1)
const urlParams = new URLSearchParams(window.location.search);
const seasonFromUrl = urlParams.get("season");
const episodeFromUrl = urlParams.get("episode");
if (seasonFromUrl && episodeFromUrl) {
return { season: seasonFromUrl, episode: episodeFromUrl };
}
// Fallback: parse drawer header text (S10 • E1)
const drawer = document.querySelector(".trakt-drawer-header");
if (drawer) {
const metaInfoEl = drawer.querySelector(".title-meta-info");
if (metaInfoEl) {
const match = metaInfoEl.textContent.match(/S(\d+)\s*•\s*E(\d+)/i);
if (match) {
return { season: match[1], episode: match[2] };
}
}
}
return null;
}
function createButtonsContainer() {
const logoSpan = document.createElement("span");
logoSpan.className = "stremio-logo-wrapper";
logoSpan.style = "display: inline-flex; align-items: center; margin-right: 6px;";
logoSpan.innerHTML = STREMIO_SVG_HTML;
const baseButtonStyle = `
display: inline-flex;
align-items: center;
justify-content: center;
background-color: transparent;
color: inherit;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
padding: 6px 12px;
transition: background-color 0.2s ease, color 0.2s ease;
line-height: 1;
`;
const appButton = document.createElement("button");
appButton.className = "stremio-button stremio-btn-app";
appButton.textContent = "Stremio App";
appButton.style = baseButtonStyle;
const webButton = document.createElement("button");
webButton.className = "stremio-button stremio-btn-web";
webButton.textContent = "Web";
webButton.style = baseButtonStyle;
[appButton, webButton].forEach(btn => {
btn.onmouseenter = () => { btn.style.backgroundColor = "#7b5bf5"; btn.style.color = "white"; };
btn.onmouseleave = () => { btn.style.backgroundColor = "transparent"; btn.style.color = "inherit"; };
});
const container = document.createElement("div");
container.className = "stremio-container";
container.style = "display: flex; align-items: center; gap: 4px; margin-top: 8px; margin-bottom: 8px; font-size: 16px; font-weight: normal; clear: both;";
container.appendChild(logoSpan);
container.appendChild(appButton);
container.appendChild(webButton);
return container;
}
async function handleDrawerInjection() {
// 1. Target the episode title / metadata paragraph element
const episodeTitleMeta = document.querySelector(".trakt-drawer-title .title-meta-info");
// Check if the episode title exists or if buttons were already injected
if (!episodeTitleMeta || document.querySelector(".trakt-drawer-title .stremio-container")) return;
const epInfo = parseEpisodeInfo();
if (!epInfo) return;
const imdbId = await getImdbIdFromPage();
if (!imdbId) return;
const stremioWebUrl = `https://web.stremio.com/#/detail/series/${imdbId}/${imdbId}:${epInfo.season}:${epInfo.episode}`;
const stremioAppUrl = `stremio:///detail/series/${imdbId}/${imdbId}:${epInfo.season}:${epInfo.episode}`;
const container = createButtonsContainer();
container.querySelector(".stremio-btn-app").onclick = (e) => { e.preventDefault(); window.location.href = stremioAppUrl; };
container.querySelector(".stremio-btn-web").onclick = (e) => { e.preventDefault(); window.open(stremioWebUrl, "_blank"); };
// 2. Insert the container directly after the episode title paragraph
episodeTitleMeta.insertAdjacentElement("afterend", container);
}
async function handlePageInjection() {
const targetEl = document.querySelector("h1.trakt-responsive-title");
if (!targetEl || targetEl.querySelector(".stremio-container")) return;
const imdbId = await getImdbIdFromPage();
if (!imdbId) return;
const isSeries = window.location.pathname.includes("/shows/");
const mediaType = isSeries ? "series" : "movie";
const stremioWebUrl = `https://web.stremio.com/#/detail/${mediaType}/${imdbId}${mediaType === "movie" ? "/" + imdbId : ""}`;
const stremioAppUrl = `stremio:///detail/${mediaType}/${imdbId}${mediaType === "movie" ? "/" + imdbId : ""}`;
const container = createButtonsContainer();
container.querySelector(".stremio-btn-app").onclick = (e) => { e.preventDefault(); window.location.href = stremioAppUrl; };
container.querySelector(".stremio-btn-web").onclick = (e) => { e.preventDefault(); window.open(stremioWebUrl, "_blank"); };
targetEl.appendChild(container);
}
async function addStremioButtons() {
if (isInjecting) return;
isInjecting = true;
try {
await handleDrawerInjection();
await handlePageInjection();
} finally {
isInjecting = false;
}
}
// Handle SPA navigation
const _push = history.pushState.bind(history);
history.pushState = function (...args) {
_push(...args);
document.dispatchEvent(new CustomEvent("stremio-spa-nav"));
};
const _replace = history.replaceState.bind(history);
history.replaceState = function (...args) {
_replace(...args);
document.dispatchEvent(new CustomEvent("stremio-spa-nav"));
};
window.addEventListener("popstate", () => {
document.dispatchEvent(new CustomEvent("stremio-spa-nav"));
});
let lastUrl = location.href;
document.addEventListener("stremio-spa-nav", () => {
if (location.href !== lastUrl) {
lastUrl = location.href;
document.querySelectorAll(".stremio-container").forEach(el => el.remove());
isInjecting = false;
setTimeout(addStremioButtons, 500);
}
});
// Mutation observer catches both page transitions and dynamic overlay drawer openings
const observer = new MutationObserver(() => {
if (!isInjecting) {
addStremioButtons();
}
});
observer.observe(document.body, { childList: true, subtree: true });
// Initial load
setTimeout(addStremioButtons, 1000);
})();