diff --git a/websites/S/Stremio/metadata.json b/websites/S/Stremio/metadata.json index 18592af88120..cfbf947f7edc 100644 --- a/websites/S/Stremio/metadata.json +++ b/websites/S/Stremio/metadata.json @@ -1,9 +1,15 @@ { "$schema": "https://schemas.premid.app/metadata/1.9", "author": { - "name": "Dark_Ville", - "id": "638080361179512853" + "name": "Sleeyax", + "id": "226037514954080257" }, + "contributors": [ + { + "name": "Dark_Ville", + "id": "638080361179512853" + } + ], "service": "Stremio", "description": { "en": "Freedom To Stream. Stremio is a modern media center that's a one-stop solution for your video entertainment. Discover, watch and organize video content from easy to install addons.", @@ -11,16 +17,26 @@ }, "url": [ "www.stremio.com", - "app.strem.io" + "stremio.com", + "app.strem.io", + "web.strem.io", + "web.stremio.com" ], - "version": "1.0.6", + "version": "2.0.0", "logo": "https://cdn.rcd.gg/PreMiD/websites/S/Stremio/assets/logo.png", "thumbnail": "https://cdn.rcd.gg/PreMiD/websites/S/Stremio/assets/thumbnail.jpg", "color": "#8A5AAB", "category": "videos", "tags": [ + "streaming", + "channels", "cartoons", - "stream" + "movies", + "series", + "stream", + "shows", + "iptv", + "tv" ], "settings": [ { @@ -46,6 +62,15 @@ "title": "Show Buttons", "icon": "fas fa-compress-arrows-alt", "value": true + }, + { + "id": "search", + "if": { + "privacy": false + }, + "title": "Show Search", + "icon": "fas fa-search", + "value": false } ] } \ No newline at end of file diff --git a/websites/S/Stremio/presence.ts b/websites/S/Stremio/presence.ts index fdb09cc36f27..b6ff92eb6e28 100644 --- a/websites/S/Stremio/presence.ts +++ b/websites/S/Stremio/presence.ts @@ -3,172 +3,405 @@ const presence = new Presence({ }), browsingTimestamp = Math.floor(Date.now() / 1000); -let timestamp: [number, number], - pauseCheck: boolean, - search: HTMLInputElement, - title: string; +enum AppVersion { + Website = -1, + V4 = 4, + V5 = 5, +} + +const enum Assets { + Logo = "https://cdn.rcd.gg/PreMiD/websites/S/Stremio/assets/logo.png", +} + +function getAppVersion(hostname: string) { + switch (hostname) { + case "web.strem.io": + case "web.stremio.com": + return AppVersion.V5; + case "app.strem.io": + return AppVersion.V4; + default: + return AppVersion.Website; + } +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function _eval(js: string): Promise { + return new Promise((resolve, reject) => { + try { + const eventName = "PreMiD_Stremio", + script = document.createElement("script"); + + window.addEventListener( + eventName, + (data: CustomEvent) => { + script.remove(); + resolve(data.detail); + }, + { once: true } + ); + script.id = eventName; + script.appendChild( + document.createTextNode(` + var core = window.services.core; + var pmdEvent = new CustomEvent("${eventName}", {detail: ${js}}); + window.dispatchEvent(pmdEvent); + `) + ); + + document.head.appendChild(script); + } catch (err) { + reject(err); + } + }); +} + +type Video = { + isEmbed: boolean; + isPaused: boolean; + startTimestamp?: number; + endTimestamp?: number; +}; + +function findVideo(presence: Presence): Video | null { + const videoElement = document.querySelector("video"); + + if (videoElement) { + const result: Video = { isEmbed: false, isPaused: videoElement.paused }; + + if (!isNaN(videoElement?.duration)) { + [result.startTimestamp, result.endTimestamp] = + presence.getTimestampsfromMedia(videoElement); + } + + return result; + } else if (document.querySelector("div[class*='player-container']")) { + const result: Video = { + isEmbed: true, + isPaused: !!document.querySelector( + "div[class*='control-bar-button'] > svg[icon='ic_play']" + ), + }, + seekBar = document.querySelector('[class*="seek-bar-container"]'); + [result.startTimestamp, result.endTimestamp] = presence.getTimestamps( + Number( + presence.timestampFromFormat(seekBar?.firstElementChild?.textContent) + ), + Number( + presence.timestampFromFormat(seekBar?.lastElementChild?.textContent) + ) + ); + + return result; + } else return null; +} presence.on("UpdateData", async () => { const presenceData: PresenceData = { - largeImageKey: - "https://cdn.rcd.gg/PreMiD/websites/S/Stremio/assets/logo.png", + largeImageKey: Assets.Logo, startTimestamp: browsingTimestamp, }, - { hash, hostname, href } = document.location, - [privacy, thumbnails, buttons] = await Promise.all([ + { hash, hostname, pathname, href } = document.location, + [privacy, thumbnails, buttons, search] = await Promise.all([ presence.getSetting("privacy"), presence.getSetting("thumbnails"), presence.getSetting("buttons"), + presence.getSetting("search"), ]), - active = document.querySelector( - "[class='ng-binding ng-scope selected']" - )?.textContent; - - if (hostname.includes("app.strem.io")) { - search = document.querySelector("#global-search-field"); - const video = document.querySelector("#videoPlayer"); - if (privacy && !video) presenceData.details = "Browsing..."; - else if (privacy && video) presenceData.details = "Watching..."; - else if (search?.value) { - presenceData.details = "Searching For:"; - presenceData.state = search.value; - } else if (hash.includes("/detail/")) { - title = document.querySelector( - "#detail > div:nth-child(3) > div > div.sidebar-info-container > div > div.logo > div" - ).textContent; - presenceData.details = title; - presenceData.buttons = [ - { - label: "Watch Video", - url: href, - }, - ]; - if (thumbnails) { - presenceData.largeImageKey = - document - .querySelector( - "#detail > div.details-less-info > div.details-top > div:nth-child(1)" - ) - ?.firstElementChild.getAttribute("src") ?? "logo"; - } - } else if (hash.includes("addons")) { - search = document.querySelector( - "#addons > div.filter > form > div.addon-search-input > input" - ); - if (search?.value) { - presenceData.details = "Searching addons for:"; - presenceData.state = search.value; - } else { - presenceData.state = active ?? "All"; - title = document.querySelector( - "[class='ng-scope selected']" - )?.textContent; - - presenceData.buttons = [ - { - label: "Browse Addons", - url: href, - }, - ]; - presenceData.details = `Browsing ${title}`; + appVersion = getAppVersion(hostname); + + if (!privacy && search) { + let searchInput: HTMLInputElement; + + if (appVersion === AppVersion.V4) + searchInput = document.querySelector("#global-search-field"); + else searchInput = document.querySelector("input[class*='search-input']"); + + const searchValue = searchInput?.value; + + if (searchValue) { + presenceData.details = `Searching for ${searchValue}`; + presenceData.smallImageKey = Assets.Search; + presence.setActivity(presenceData); + return; + } + } + + switch (appVersion) { + case AppVersion.V4: + case AppVersion.V5: { + const video = findVideo(presence); + + if (privacy) { + presenceData.details = video !== null ? "Watching" : "Browsing"; + break; } - } else if (hash.includes("settings")) { - presenceData.details = `${ - document.querySelector("[class='ng-scope ng-binding active']") - ?.textContent ?? "General" - } settings`; - } else if (hash.includes("/discover/")) { - if (active) { - presenceData.buttons = [ - { - label: "Browse", - url: href, - }, - ]; - presenceData.details = `Browsing: ${active}`; - } else presenceData.state = "Browsing Movies"; - } else if (hash.includes("/library")) { - if (active) presenceData.details = `${active} Library`; - else presenceData.details = "Library"; - - presenceData.buttons = [ - { - label: "View Library", - url: href, - }, - ]; - } else if (hash.includes("/calendar")) { - presenceData.buttons = [ - { - label: "View Calendar", - url: href, - }, - ]; - presenceData.details = "Calendar"; - } else if (hash.includes("player")) { - if (video?.duration) { - timestamp = presence.getTimestampsfromMedia(video); - pauseCheck = video?.paused ?? true; - } else if (!video.duration && document.querySelector("#controlbar-top")) { - let split = document - .querySelector("#play-progress-text") - ?.textContent.split("/"); - if (split?.[0]) { - split = split.map(s => s.trim()); - timestamp = presence.getTimestamps( - presence.timestampFromFormat(split[0]), - presence.timestampFromFormat(split[1]) - ); + + switch (hash.replace("#/", "").split("/").shift().split("?").shift()) { + case "": + presenceData.details = "Board"; + presenceData.buttons = [ + { + label: "View Board", + url: href, + }, + ]; + break; + case "detail": { + if (appVersion === AppVersion.V4) { + const title = document.querySelector( + "#detail > div:nth-child(3) > div > div.sidebar-info-container > div > div.logo > div" + )?.textContent; + presenceData.state = title; + presenceData.largeImageKey = + document + .querySelector( + "#detail > div.details-less-info > div.details-top > div:nth-child(1)" + ) + ?.firstElementChild.getAttribute("src") ?? Assets.Logo; + } else { + const imgElement = document.querySelector( + "div[class*='meta-info-container'] > img[class*='logo']" + ); + presenceData.largeImageKey = + imgElement?.getAttribute("src") ?? Assets.Logo; + presenceData.state = + imgElement?.getAttribute("title") ?? + document.querySelector( + "div[class*='logo-placeholder']:last-child" + )?.textContent; + } + + presenceData.details = `Viewing a ${hash.split("/")[2]}`; + presenceData.buttons = [ + { + label: "View Metadata", + url: href, + }, + ]; + + break; } + case "addons": { + const title = document.querySelector( + appVersion === AppVersion.V4 + ? "[class='ng-scope selected']" + : "div[class*='addons-content'] > div[class*='selectable-inputs-container'] > div:nth-child(2) > div" + )?.textContent, + type = document.querySelector( + appVersion === AppVersion.V4 + ? "[class='ng-binding ng-scope selected']" + : "div[class*='addons-content'] > div[class*='selectable-inputs-container'] > div:nth-child(3) > div" + )?.textContent; - pauseCheck = !document - .querySelector("#controlbar-top") - ?.firstElementChild.className.includes("pause"); - } - delete presenceData.startTimestamp; - if ( - !pauseCheck && - !document.querySelector("#loading-logo").className.includes("flashing") - ) { - presenceData.endTimestamp = timestamp[1]; - presenceData.smallImageKey = Assets.Play; - } else { - delete presenceData.endTimestamp; - presenceData.smallImageKey = Assets.Pause; + presenceData.details = `Browsing ${title + ?.toLowerCase() + ?.replace(" addons", "")} addons`; + presenceData.state = type ?? "All"; + presenceData.buttons = [ + { + label: "Browse Addons", + url: href, + }, + ]; + break; + } + case "settings": { + const section = + document.querySelector( + appVersion === AppVersion.V4 + ? "[class='ng-scope ng-binding active']" + : "div[class*='settings-content'] div[class*='selected']" + )?.textContent ?? "General"; + presenceData.details = `${section} settings`; + presenceData.buttons = [ + { + label: "View Settings", + url: href, + }, + ]; + break; + } + case "discover": { + const type = document + .querySelector( + appVersion === AppVersion.V4 + ? "[class='ng-binding ng-scope selected']" + : "div[class*='selectable-inputs-container'] > div:nth-child(1) > div" + ) + ?.textContent?.toLowerCase(), + category = document.querySelector( + appVersion === AppVersion.V4 + ? "ul.sort > li.selected" + : "div[class*='selectable-inputs-container'] > div:nth-child(2) > div" + )?.textContent, + genre = + document + .querySelector( + appVersion === AppVersion.V4 + ? "ul.genre-select > li.selected" + : "div[class*='selectable-inputs-container'] > div:nth-child(3) > div" + ) + ?.textContent?.replace("Select genre", "") || null; + + presenceData.buttons = [ + { + label: "Browse", + url: href, + }, + ]; + presenceData.details = `Discovering ${type ?? "content"}${ + type === "series" ? "" : "s" + }`; + presenceData.state = `${category ?? "All"}${ + genre ? ` | ${genre}` : "" + }`; + + break; + } + case "library": { + const type = document.querySelector( + appVersion === AppVersion.V4 + ? "[class='ng-binding ng-scope selected']" + : "div[class*='selectable-inputs-container'] > div > div" + )?.textContent; + + presenceData.details = "Library"; + presenceData.state = type ?? "All"; + presenceData.buttons = [ + { + label: "View Library", + url: href, + }, + ]; + break; + } + case "calendar": + presenceData.details = "Calendar"; + presenceData.buttons = [ + { + label: "View Calendar", + url: href, + }, + ]; + break; + case "search": + presenceData.details = "Search"; + break; + case "player": { + if (video === null) break; + + presenceData.endTimestamp = video.endTimestamp; + delete presenceData.startTimestamp; + + if ( + (appVersion === AppVersion.V4 + ? document + .querySelector("#loading-logo") + .className.includes("flashing") + : !!document.querySelector( + "div[class*='buffering-loader-container']" + )) || + video.isPaused + ) { + presenceData.smallImageKey = Assets.Pause; + presenceData.smallImageText = "Player is paused"; + presenceData.state = "Paused"; + } else { + presenceData.smallImageKey = Assets.Play; + presenceData.smallImageText = "Player is playing"; + presenceData.state = "Watching"; + } + + let metaUrl: string, title: string; + + if (appVersion === AppVersion.V4) { + title = document + .querySelector("head > title") + ?.textContent?.replace("Stremio -", "") + ?.trim(); + metaUrl = href + .substring(0, href.lastIndexOf("/")) + .replace("player", "detail"); + presenceData.largeImageKey = + document + .querySelector("#loading-logo") + ?.getAttribute("data-image") ?? Assets.Logo; + } else { + const playerState = await _eval( + "core.transport.getState('player')" + ); + if (playerState.metaItem.type.toLowerCase() === "ready") { + const { + metaItem: { content }, + seriesInfo: { season, episode }, + } = playerState; + ({ title } = playerState); + metaUrl = `${window.location.origin}/#/detail/${content.type}/${content.id}`; + if (content.type === "series") + metaUrl += `/${content.id}:${season}:${episode}`; + presenceData.largeImageKey = content.logo ?? Assets.Logo; + } + } + + presenceData.details = title ?? "Player"; + if (metaUrl) { + presenceData.buttons = [ + { + label: "Watch", + url: metaUrl, + }, + ]; + } + break; + } } - title = document - .querySelector("head > title") - ?.textContent.replace("Stremio -", ""); - presenceData.details = title; - presenceData.buttons = [ - { - label: "Join View Party", - url: href, - }, - ]; - } else if (hash === "#/") presenceData.details = "Viewing the homepage"; - } else if (hostname === "stremio.com") { - if (hash.includes("addon-sdk")) presenceData.details = "Viewing Addon SDK"; - else if (hash.includes("contribute")) - presenceData.details = "Contributing page"; - else if (hash.includes("community")) - presenceData.details = "Viewing the Community"; - else if (hash.includes("technology")) - presenceData.details = "Viewing the technology"; - else if (document.querySelector("#tos-container > h1 > strong")) { - presenceData.details = `Reading ${ - document.querySelector("#tos-container > h1 > strong").textContent - }`; - } else if ( - document.querySelector("[class='active']")?.textContent !== "EN" - ) { - presenceData.details = - document.querySelector("[class='active']")?.textContent ?? - "Browsing..."; + break; } + case AppVersion.Website: + presenceData.details = "Visiting stremio.com"; + + switch (pathname) { + case "/addon-sdk": + presenceData.state = "Addon SDK"; + break; + + case "/contribute": + presenceData.state = "Contribute"; + break; + + case "/community": + presenceData.state = "Community"; + break; + + case "/technology": + presenceData.state = "Technology"; + break; + + case "/competition": + presenceData.state = "Competition"; + break; + + case "/careers": + presenceData.state = "Careers"; + break; + + default: { + const activeTab = document.querySelector("[class='active']"); + if ( + activeTab === null || + activeTab.parentElement?.className === "langs" + ) + break; + presenceData.state = activeTab.textContent; + } + } + + break; } if (!buttons) delete presenceData.buttons; + if (!thumbnails) presenceData.largeImageKey = Assets.Logo; if (presenceData.details) presence.setActivity(presenceData); else presence.setActivity(); });