Skip to content

Instantly share code, notes, and snippets.

@HasanAbbadi
Created November 15, 2024 12:29
Show Gist options
  • Select an option

  • Save HasanAbbadi/f65ea3caca2caff455ee25ebc1b55c0c to your computer and use it in GitHub Desktop.

Select an option

Save HasanAbbadi/f65ea3caca2caff455ee25ebc1b55c0c to your computer and use it in GitHub Desktop.
Tampermonkey userscript to add watch button from Vidsrc on Trakt.tv: dashboard, movie details, season details pages
// ==UserScript==
// @name Vidsrc Trakt.tv Integration
// @namespace http://github.com/HasanAbbadi/
// @version 2024-11-13
// @description Stream Movies and TV Shows directly on trakt in upto 1080p! No more shady websites with horrible ads and malware.
// @author You
// @match https://trakt.tv/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=trakt.tv
// ==/UserScript==
(function () {
'use strict';
// modal structure
const modal = document.createElement('div');
modal.style.display = 'none';
modal.style.position = 'fixed';
modal.style.zIndex = '10004';
modal.style.left = '0';
modal.style.top = '0';
modal.style.width = '100%';
modal.style.height = '100%';
modal.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
const modalContent = document.createElement('div');
modalContent.style.position = 'absolute';
modalContent.style.top = '50%';
modalContent.style.left = '50%';
modalContent.style.transform = 'translate(-50%, -50%)';
modalContent.style.width = '90%';
modalContent.style.maxWidth = '1111px';
modalContent.style.height = '90%';
modalContent.style.maxHeight = '687px';
modalContent.style.backgroundColor = '#000000';
modalContent.style.border = '1px solid #000000';
modalContent.style.borderRadius = '5px';
modalContent.style.boxShadow = '0 2px 10px rgba(0,0,0,0.3)';
const modalHeader = document.createElement('div');
modalHeader.style.padding = '10px';
modalHeader.style.backgroundColor = '#000000';
modalHeader.style.color = '#FFFFFF';
modalHeader.style.borderBottom = '1px solid #000000';
modalHeader.style.display = 'flex';
modalHeader.style.justifyContent = 'space-between';
modalHeader.style.alignItems = 'center';
const closeButton = document.createElement('span');
closeButton.innerHTML = '<i class="fas fa-times"></i>';
closeButton.style.cursor = 'pointer';
closeButton.style.fontSize = '20px';
modalHeader.appendChild(closeButton);
const iframe = document.createElement('iframe');
iframe.style.width = '100%';
iframe.style.height = 'calc(100% - 41px)'; // Subtract header height
iframe.style.border = 'none';
iframe.allowFullscreen = true;
modalContent.appendChild(modalHeader);
modalContent.appendChild(iframe);
modal.appendChild(modalContent);
document.body.appendChild(modal);
// Function to create the Watch Now button content
function createWatchButtonContent() {
let c1 = "red";
let c2 = "rgb(152, 64, 189)";
let deg = "60";
let x = 0;
const button = document.createElement('button');
button.classList.add("vidsrc-watch-btn");
button.style.background = 'red';
button.style.background = `linear-gradient(${deg}deg, ${c1}, ${c2} 51%, ${c1}) ${x}/ 200%`;
button.style.transition = '0.5s ease';
button.style.color = "#fff";
button.style.border = "none";
button.style.cursor = "pointer";
button.style.display = "inline-flex";
button.style.alignItems = "center";
button.style.justifyContent = "center";
button.style.padding = "8px 15px";
button.style.width = "100%";
button.style.height = "100%";
button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" style="overflow:visible;"><path fill="currentColor" d="M8 17.175V6.825q0-.425.3-.713t.7-.287q.125 0 .263.037t.262.113l8.15 5.175q.225.15.338.375t.112.475t-.112.475t-.338.375l-8.15 5.175q-.125.075-.262.113T9 18.175q-.4 0-.7-.288t-.3-.712"/></svg>'
// https://codepen.io/thebabydino/pen/jBbXPP
button.addEventListener("mouseover", () => {
x = "100%";
button.style.background = `linear-gradient(${deg}deg, ${c1}, ${c2} 51%, ${c1}) ${x}/ 200%`;
});
button.addEventListener("mouseout",() => {
x = "0"
button.style.background = `linear-gradient(${deg}deg, ${c1}, ${c2} 51%, ${c1}) ${x}/ 200%`;
});
return button
}
// Function to insert or replace the button
function insertWatchButton() {
const contentType = getContentType();
if (contentType == 'movie') {
const button = createWatchButton();
const links = document.querySelector('.streaming-links')
if (links) {
links.parentNode.insertBefore(button, links)
} else {
console.error('element not found')
}
} else if (contentType == 'tv') {
const pathnames = document.location.pathname.split('/')
const seasonNum = pathnames[pathnames.length - 1]
const epEls = document.querySelectorAll('#seasons-episodes-sortable > .row')
epEls.forEach((ep, i) => {
const button = createWatchButton(seasonNum, (i + 1))
const parentDiv = ep.querySelector(':scope > .grid-item > .quick-icons > .actions')
parentDiv.appendChild(button)
});
} else if (contentType == 'dashboard') {
const epEls = document.querySelectorAll('#ondeck-shows > .grid-item')
epEls.forEach((ep) => {
const url = ep.querySelector(':scope > a')['href']
const button = createWatchButton(ep.getAttribute('data-season-number'), ep.getAttribute('data-episode-number'), url)
const existingWatchButton = ep.querySelector(':scope > .quick-icons > .actions > a.watch-now')
existingWatchButton.replaceWith(button)
console.log('replace button')
});
} else {
console.log('nothing to insert')
}
}
// Function to insert button for TMDB
function createWatchButton(season = null, ep = null, url = null) {
console.log('creating button')
let button
button = document.createElement('a');
button.classList.add("vidsrc-btn-container");
button.title = "Watch Now on Vidsrc";
button.addEventListener('click', () => showModal(season, ep, url));
button.appendChild(createWatchButtonContent());
button.style.position = 'relative';
button.style.zIndex = '2';
button.style.cursor = 'pointer';
return button
}
// Function to get ID (TMDB)
function getId() {
const tmdbLink = document.querySelector('#external-link-tmdb').href
const match = tmdbLink.match(/\/(movie|tv)\/(\d+)/);
return match ? match[2] : null;
}
// Function to fetch ID from page (TMDB)
async function fetchId(url) {
const res = await fetch(url)
const body = await res.text();
const m = body.match(/external-link-tmdb/)
const l = body.slice(m.index, m.index + 70).split('/')
return l[4]
}
// Function to determine if it's a movie or TV show
function getContentType() {
console.log("Current URL:", window.location.href);
console.log("Document title:", document.title);
try {
const isTV = window.location.pathname.includes('/seasons/');
const isDashboard = window.location.pathname.includes('/dashboard')
const type = isTV ? 'tv' : isDashboard ? 'dashboard' : 'movie';
console.log("Content Type is: " + type)
return type
} catch (error) {
console.error(error);
return null;
}
}
// Function to show modal
async function showModal(season = null, ep = null, url = null) {
let contentType = getContentType();
let id;
if (contentType == 'dashboard') {
id = await fetchId(url);
} else {
id = getId();
}
let baseUrl;
// Set baseUrl based on the contentType
baseUrl = 'https://vidsrc.xyz/embed/';
if (contentType == 'dashboard') contentType = 'tv'
if (id) {
baseUrl += `${contentType}?tmdb=${id}`;
}
if (season && ep) {
baseUrl += `&season=${season}&episode=${ep}`
}
console.log('.s.....ep...url...id...if')
console.log(season, ep, url, id, baseUrl)
console.log('.........................')
iframe.src = `${baseUrl}`;
modal.style.display = 'block';
}
// Function to initialize the script
function init() {
insertWatchButton();
}
// Use a MutationObserver to detect when the relevant elements are added to the DOM
const observer = new MutationObserver((mutations, obs) => {
let contentType = getContentType();
let relevantElement;
if (contentType == "dashboard") {
relevantElement = document.querySelector('#ondeck-shows > .grid-item')
} else {
relevantElement = document.querySelector('.poster')
}
if (relevantElement) {
init();
obs.disconnect(); // Stop observing once we've found and modified the relevant element
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
// Close modal on close button click
closeButton.addEventListener('click', () => {
modal.style.display = 'none';
iframe.src = ''; // Clear the iframe source when closing
});
// Close modal when clicking outside of it
modal.addEventListener('click', (event) => {
if (event.target === modal) {
modal.style.display = 'none';
iframe.src = ''; // Clear the iframe source when closing
}
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment