Advertisement
zelman

Sky Tamper

Mar 19th, 2025
45
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.54 KB | None | 0 0
  1. // ==UserScript==
  2. // @name SkyShowtime Date and Language
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.0
  5. // @description Presreće XHR i fetch zahtjeve na SkyShowtime te automatski prikazuje offerEndTs (epoch) konvertiran u lokalni datum unutar tipke, formatiran kao dd.mm.yyyy. HH:mm:ss. Također, kad se u xhr sadrži "vod", ispod datuma se ispisuju zastavice za balkanske jezike, sortirane abecedno (Bosanski, Hrvatski, Slovenski, Srpski).
  6. // @match https://www.skyshowtime.com/*
  7. // @grant none
  8. // @run-at document-idle
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. console.log("SkyShowtime Datum Intercept script loaded");
  15.  
  16. // Dodajemo tipku u donji desni kut
  17. let btn = document.createElement("button");
  18. btn.id = "skyDatumBtn";
  19. btn.innerText = "Dostupno do:"; // Početni tekst
  20. btn.style.position = "fixed";
  21. btn.style.bottom = "20px";
  22. btn.style.right = "20px";
  23. btn.style.zIndex = "9999";
  24. btn.style.padding = "10px";
  25. btn.style.backgroundColor = "#795FE3";
  26. btn.style.color = "#fff";
  27. btn.style.border = "none";
  28. btn.style.borderRadius = "5px";
  29. btn.style.cursor = "default";
  30. document.documentElement.appendChild(btn);
  31.  
  32. // Globalna varijabla za čuvanje osnovnog teksta (npr. datum)
  33. let baseText = "";
  34.  
  35. // Funkcija za formatiranje datuma u oblik "dd.mm.yyyy. HH:mm:ss"
  36. function formatDate(date) {
  37. const dd = ("0" + date.getDate()).slice(-2);
  38. const mm = ("0" + (date.getMonth() + 1)).slice(-2);
  39. const yyyy = date.getFullYear();
  40. const hh = ("0" + date.getHours()).slice(-2);
  41. const min = ("0" + date.getMinutes()).slice(-2);
  42. const ss = ("0" + date.getSeconds()).slice(-2);
  43. return `${dd}.${mm}.${yyyy}. ${hh}:${min}:${ss}`;
  44. }
  45.  
  46. // Izdvajamo token iz URL-a – dio nakon "/watch/asset"
  47. const prefix = "/watch/asset";
  48. let token = "";
  49. function updateToken(){
  50. let path = window.location.pathname;
  51. if (path.startsWith(prefix)) {
  52. token = path.slice(prefix.length);
  53. console.log("Extracted token:", token);
  54. } else {
  55. token = "";
  56. console.log("URL ne sadrži očekivani prefiks:", prefix);
  57. }
  58. }
  59. updateToken();
  60.  
  61. // Funkcija koja obrađuje API podatke: traži offerEndTs i konvertira epoch u lokalni datum
  62. function processApiData(data) {
  63. if (!data) return;
  64. let offerVal = data.offerEndTs;
  65. if (offerVal === undefined || offerVal === null) {
  66. offerVal = findOfferEndTs(data);
  67. }
  68. if (offerVal !== undefined && offerVal !== null) {
  69. let epoch;
  70. if (typeof offerVal === "number") {
  71. epoch = offerVal;
  72. } else if (typeof offerVal === "string") {
  73. let epochStr = offerVal.replace(/[{}]/g, '').trim();
  74. epoch = parseInt(epochStr, 10);
  75. } else {
  76. console.error("Nepodržani tip offerEndTs:", typeof offerVal);
  77. return;
  78. }
  79. if (!isNaN(epoch)) {
  80. let dateObj = new Date(epoch);
  81. let localDate = formatDate(dateObj);
  82. baseText = "Dostupno do: " + localDate;
  83. btn.innerHTML = baseText;
  84. console.log("OfferEndTs processed, local date:", localDate);
  85. }
  86. }
  87. }
  88.  
  89. // Rekurzivna pretraga za offerEndTs u objektu
  90. function findOfferEndTs(obj) {
  91. if (typeof obj !== 'object' || obj === null) return null;
  92. if (obj.hasOwnProperty('offerEndTs')) return obj.offerEndTs;
  93. for (let key in obj) {
  94. if (obj.hasOwnProperty(key)) {
  95. let result = findOfferEndTs(obj[key]);
  96. if (result) return result;
  97. }
  98. }
  99. return null;
  100. }
  101.  
  102. // Funkcija koja obrađuje podatke iz "vod" zahtjeva - filtrira audio i titlove
  103. function processVodData(data) {
  104. if (!data) return;
  105. // Ako je podatak unutar "asset", koristimo ga, inače direktno data
  106. let assetData = data.asset || data;
  107.  
  108. // Definiramo mape: allowedLocales s HTML zastavicama i languageNames s nazivima jezika
  109. const allowedLocales = {
  110. "hr-HR": '<img src="https://flagcdn.com/24x18/hr.png" style="vertical-align:middle;" title="Hrvatski">',
  111. "sl-SI": '<img src="https://flagcdn.com/24x18/si.png" style="vertical-align:middle;" title="Slovenski">',
  112. "sr-RS": '<img src="https://flagcdn.com/24x18/rs.png" style="vertical-align:middle;" title="Srpski">',
  113. "bs-BA": '<img src="https://flagcdn.com/24x18/ba.png" style="vertical-align:middle;" title="Bosanski">'
  114. };
  115. const languageNames = {
  116. "hr-HR": "Hrvatski",
  117. "sl-SI": "Slovenski",
  118. "sr-RS": "Srpski",
  119. "bs-BA": "Bosanski"
  120. };
  121.  
  122. let audioTracks = assetData.audioTracks || [];
  123. let subtitleTracks = assetData.subtitleTracks || [];
  124. let audioLangs = []; // ovdje ćemo spremati kodove jezika
  125. let subtitleLangs = [];
  126.  
  127. if (Array.isArray(audioTracks)) {
  128. audioTracks.forEach(track => {
  129. if ((track.type === "Stereo" || track.type === "Surround") && allowedLocales[track.locale]) {
  130. if (!audioLangs.includes(track.locale)) {
  131. audioLangs.push(track.locale);
  132. }
  133. }
  134. });
  135. }
  136. if (Array.isArray(subtitleTracks)) {
  137. subtitleTracks.forEach(track => {
  138. if (track.type === "SUB" && allowedLocales[track.locale]) {
  139. if (!subtitleLangs.includes(track.locale)) {
  140. subtitleLangs.push(track.locale);
  141. }
  142. }
  143. });
  144. }
  145.  
  146. // Sortiramo po abecedi koristeći nazive jezika
  147. audioLangs.sort((a, b) => languageNames[a].localeCompare(languageNames[b]));
  148. subtitleLangs.sort((a, b) => languageNames[a].localeCompare(languageNames[b]));
  149.  
  150. // Ažuriraj tipku samo ako pronađemo barem jedan jezik
  151. if (audioLangs.length > 0 || subtitleLangs.length > 0) {
  152. let newHTML = baseText;
  153. if (audioLangs.length > 0) {
  154. // Za audio, spajamo zastavice
  155. let audioHTML = audioLangs.map(code => allowedLocales[code]).join(" ");
  156. newHTML += "<br>Audio: " + audioHTML;
  157. }
  158. if (subtitleLangs.length > 0) {
  159. let subtitleHTML = subtitleLangs.map(code => allowedLocales[code]).join(" ");
  160. newHTML += "<br>Titlovi: " + subtitleHTML;
  161. }
  162. btn.innerHTML = newHTML;
  163. console.log("Processed VOD data, audio:", audioLangs, "titlovi:", subtitleLangs);
  164. } else {
  165. console.log("Processed VOD data ali nisu pronađeni valjani audio ili titlovi.");
  166. }
  167. }
  168.  
  169. // Overrideamo window.fetch da presretnemo API odgovore
  170. const originalFetch = window.fetch;
  171. window.fetch = async function(...args) {
  172. const response = await originalFetch.apply(this, args);
  173. try {
  174. let url = args[0] ? args[0].toString() : "";
  175. if (url.toLowerCase().includes("node?slug=") &&
  176. (url.includes(token) || url.includes(encodeURIComponent(token)))) {
  177. console.log("Intercepted fetch request:", url);
  178. const clonedResponse = response.clone();
  179. clonedResponse.json().then(data => {
  180. if (data && JSON.stringify(data).includes("offerEndTs")) {
  181. console.log("Fetch response intercepted:", data);
  182. processApiData(data);
  183. }
  184. }).catch(e => {
  185. console.log("Fetch response not JSON:", e);
  186. });
  187. } else if (url.toLowerCase().includes("vod")) {
  188. console.log("Intercepted fetch VOD request:", url);
  189. const clonedResponse = response.clone();
  190. clonedResponse.json().then(data => {
  191. console.log("Fetch VOD response intercepted:", data);
  192. processVodData(data);
  193. }).catch(e => {
  194. console.log("Fetch VOD response not JSON:", e);
  195. });
  196. }
  197. } catch(e) {
  198. console.error("Error intercepting fetch response:", e);
  199. }
  200. return response;
  201. };
  202.  
  203. // Overrideamo XMLHttpRequest da presretnemo njegove odgovore
  204. const originalXHR = window.XMLHttpRequest;
  205. function newXHR() {
  206. const xhr = new originalXHR();
  207. const originalOpen = xhr.open;
  208. xhr.open = function(method, url, async, user, password) {
  209. this._url = url;
  210. return originalOpen.apply(this, arguments);
  211. };
  212. xhr.addEventListener("load", function() {
  213. if (this._url && this._url.toLowerCase().includes("node?slug=") &&
  214. (this._url.includes(token) || this._url.includes(encodeURIComponent(token)))) {
  215. console.log("Intercepted XHR request:", this._url);
  216. try {
  217. let data = JSON.parse(this.responseText);
  218. if (data && JSON.stringify(data).includes("offerEndTs")) {
  219. console.log("XHR response intercepted:", data);
  220. processApiData(data);
  221. }
  222. } catch(e) {
  223. console.error("Error parsing XHR response:", e);
  224. }
  225. }
  226. if (this._url && this._url.toLowerCase().includes("vod")) {
  227. console.log("Intercepted XHR VOD request:", this._url);
  228. try {
  229. let data = JSON.parse(this.responseText);
  230. processVodData(data);
  231. } catch(e) {
  232. console.error("Error parsing XHR VOD response:", e);
  233. }
  234. }
  235. });
  236. return xhr;
  237. }
  238. window.XMLHttpRequest = newXHR;
  239.  
  240. // Sakriva tipku kada se pokrene video
  241. document.addEventListener("play", function(e) {
  242. if (e.target.tagName && e.target.tagName.toUpperCase() === "VIDEO") {
  243. console.log("Video started, hiding button");
  244. btn.style.display = "none";
  245. }
  246. }, true);
  247.  
  248. // Pokaži tipku kada video završi
  249. document.addEventListener("ended", function(e) {
  250. if (e.target.tagName && e.target.tagName.toUpperCase() === "VIDEO") {
  251. console.log("Video ended, showing button");
  252. btn.style.display = "block";
  253. }
  254. }, true);
  255.  
  256. // Ako se navigira na novi sadržaj unutar SPA, resetiramo token i tipku
  257. const wrapHistoryMethod = method => {
  258. const original = history[method];
  259. return function(...args) {
  260. const result = original.apply(this, args);
  261. window.dispatchEvent(new Event("locationchange"));
  262. return result;
  263. };
  264. };
  265. history.pushState = wrapHistoryMethod("pushState");
  266. history.replaceState = wrapHistoryMethod("replaceState");
  267. window.addEventListener("popstate", () => window.dispatchEvent(new Event("locationchange")));
  268. window.addEventListener("locationchange", function() {
  269. updateToken();
  270. btn.innerHTML = "Dostupno do:"; // reset tipke
  271. baseText = "";
  272. if (!document.querySelector("video")) {
  273. btn.style.display = "block";
  274. }
  275. });
  276. })();
  277.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement