Advertisement
zelman

Sky Grease

Mar 19th, 2025
27
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.61 KB | None | 0 0
  1. // ==UserScript==
  2. // @name SkyShowtime Datum Intercept (Automatski, bez videa) - Greasemonkey
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.13
  5. // @description Presreće XHR i fetch zahtjeve na SkyShowtime te automatski prikazuje offerEndTs (epoch) konvertiran u lokalni datum, 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-end
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. var injectedCode = function() {
  13. 'use strict';
  14. console.log("SkyShowtime Datum Intercept script loaded (injected)");
  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. // Definiramo mape: allowedLocales s HTML zastavicama i languageNames s nazivima jezika
  108. const allowedLocales = {
  109. "hr-HR": '<img src="https://flagcdn.com/24x18/hr.png" style="vertical-align:middle;" title="Hrvatski">',
  110. "sl-SI": '<img src="https://flagcdn.com/24x18/si.png" style="vertical-align:middle;" title="Slovenski">',
  111. "sr-RS": '<img src="https://flagcdn.com/24x18/rs.png" style="vertical-align:middle;" title="Srpski">',
  112. "bs-BA": '<img src="https://flagcdn.com/24x18/ba.png" style="vertical-align:middle;" title="Bosanski">'
  113. };
  114. const languageNames = {
  115. "hr-HR": "Hrvatski",
  116. "sl-SI": "Slovenski",
  117. "sr-RS": "Srpski",
  118. "bs-BA": "Bosanski"
  119. };
  120.  
  121. let audioTracks = assetData.audioTracks || [];
  122. let subtitleTracks = assetData.subtitleTracks || [];
  123. let audioLangs = []; // skupljamo kodove jezika
  124. let subtitleLangs = [];
  125.  
  126. if (Array.isArray(audioTracks)) {
  127. audioTracks.forEach(track => {
  128. if ((track.type === "Stereo" || track.type === "Surround") && allowedLocales[track.locale]) {
  129. if (!audioLangs.includes(track.locale)) {
  130. audioLangs.push(track.locale);
  131. }
  132. }
  133. });
  134. }
  135. if (Array.isArray(subtitleTracks)) {
  136. subtitleTracks.forEach(track => {
  137. if (track.type === "SUB" && allowedLocales[track.locale]) {
  138. if (!subtitleLangs.includes(track.locale)) {
  139. subtitleLangs.push(track.locale);
  140. }
  141. }
  142. });
  143. }
  144.  
  145. // Sortiramo po abecedi koristeći nazive jezika
  146. audioLangs.sort((a, b) => languageNames[a].localeCompare(languageNames[b]));
  147. subtitleLangs.sort((a, b) => languageNames[a].localeCompare(languageNames[b]));
  148.  
  149. // Ažuriraj tipku samo ako pronađemo barem jedan jezik
  150. if (audioLangs.length > 0 || subtitleLangs.length > 0) {
  151. let newHTML = baseText;
  152. if (audioLangs.length > 0) {
  153. let audioHTML = audioLangs.map(code => allowedLocales[code]).join(" ");
  154. newHTML += "<br>Audio: " + audioHTML;
  155. }
  156. if (subtitleLangs.length > 0) {
  157. let subtitleHTML = subtitleLangs.map(code => allowedLocales[code]).join(" ");
  158. newHTML += "<br>Titlovi: " + subtitleHTML;
  159. }
  160. btn.innerHTML = newHTML;
  161. console.log("Processed VOD data, audio:", audioLangs, "titlovi:", subtitleLangs);
  162. } else {
  163. console.log("Processed VOD data ali nisu pronađeni valjani audio ili titlovi.");
  164. }
  165. }
  166.  
  167. // Override window.fetch
  168. const originalFetch = window.fetch;
  169. window.fetch = async function(...args) {
  170. const response = await originalFetch.apply(this, args);
  171. try {
  172. let url = args[0] ? args[0].toString() : "";
  173. if (url.toLowerCase().includes("node?slug=") &&
  174. (url.includes(token) || url.includes(encodeURIComponent(token)))) {
  175. console.log("Intercepted fetch request:", url);
  176. const clonedResponse = response.clone();
  177. clonedResponse.json().then(data => {
  178. if (data && JSON.stringify(data).includes("offerEndTs")) {
  179. console.log("Fetch response intercepted:", data);
  180. processApiData(data);
  181. }
  182. }).catch(e => {
  183. console.log("Fetch response not JSON:", e);
  184. });
  185. } else if (url.toLowerCase().includes("vod")) {
  186. console.log("Intercepted fetch VOD request:", url);
  187. const clonedResponse = response.clone();
  188. clonedResponse.json().then(data => {
  189. console.log("Fetch VOD response intercepted:", data);
  190. processVodData(data);
  191. }).catch(e => {
  192. console.log("Fetch VOD response not JSON:", e);
  193. });
  194. }
  195. } catch(e) {
  196. console.error("Error intercepting fetch response:", e);
  197. }
  198. return response;
  199. };
  200.  
  201. // Override window.XMLHttpRequest
  202. const originalXHR = window.XMLHttpRequest;
  203. function newXHR() {
  204. const xhr = new originalXHR();
  205. const originalOpen = xhr.open;
  206. xhr.open = function(method, url, async, user, password) {
  207. this._url = url;
  208. return originalOpen.apply(this, arguments);
  209. };
  210. xhr.addEventListener("load", function() {
  211. if (this._url && this._url.toLowerCase().includes("node?slug=") &&
  212. (this._url.includes(token) || this._url.includes(encodeURIComponent(token)))) {
  213. console.log("Intercepted XHR request:", this._url);
  214. try {
  215. let data = JSON.parse(this.responseText);
  216. if (data && JSON.stringify(data).includes("offerEndTs")) {
  217. console.log("XHR response intercepted:", data);
  218. processApiData(data);
  219. }
  220. } catch(e) {
  221. console.error("Error parsing XHR response:", e);
  222. }
  223. }
  224. if (this._url && this._url.toLowerCase().includes("vod")) {
  225. console.log("Intercepted XHR VOD request:", this._url);
  226. try {
  227. let data = JSON.parse(this.responseText);
  228. processVodData(data);
  229. } catch(e) {
  230. console.error("Error parsing XHR VOD response:", e);
  231. }
  232. }
  233. });
  234. return xhr;
  235. }
  236. window.XMLHttpRequest = newXHR;
  237.  
  238. // Sakriva tipku kada se pokrene video
  239. document.addEventListener("play", function(e) {
  240. if (e.target.tagName && e.target.tagName.toUpperCase() === "VIDEO") {
  241. console.log("Video started, hiding button");
  242. btn.style.display = "none";
  243. }
  244. }, true);
  245. // Pokaži tipku kada video završi
  246. document.addEventListener("ended", function(e) {
  247. if (e.target.tagName && e.target.tagName.toUpperCase() === "VIDEO") {
  248. console.log("Video ended, showing button");
  249. btn.style.display = "block";
  250. }
  251. }, true);
  252.  
  253. // Reset tipke pri navigaciji unutar SPA
  254. const wrapHistoryMethod = method => {
  255. const original = history[method];
  256. return function(...args) {
  257. const result = original.apply(this, args);
  258. window.dispatchEvent(new Event("locationchange"));
  259. return result;
  260. };
  261. };
  262. history.pushState = wrapHistoryMethod("pushState");
  263. history.replaceState = wrapHistoryMethod("replaceState");
  264. window.addEventListener("popstate", () => window.dispatchEvent(new Event("locationchange")));
  265. window.addEventListener("locationchange", function() {
  266. updateToken();
  267. btn.innerHTML = "Dostupno do:"; // reset tipke
  268. baseText = "";
  269. if (!document.querySelector("video")) {
  270. btn.style.display = "block";
  271. }
  272. });
  273. };
  274. var script = document.createElement('script');
  275. script.textContent = '(' + injectedCode.toString() + ')();';
  276. document.documentElement.appendChild(script);
  277. script.remove();
  278. })();
  279.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement