From 8be78e437f641a3a707c140332daeacea801e437 Mon Sep 17 00:00:00 2001 From: Slowlife Date: Tue, 7 Jan 2025 03:51:01 +0700 Subject: [PATCH] feat(Cineby): add activity (#9087) --- websites/C/Cineby/api.ts | 111 ++++++++++++++++++++++++++++++++ websites/C/Cineby/metadata.json | 43 +++++++++++++ websites/C/Cineby/presence.ts | 93 ++++++++++++++++++++++++++ 3 files changed, 247 insertions(+) create mode 100644 websites/C/Cineby/api.ts create mode 100644 websites/C/Cineby/metadata.json create mode 100644 websites/C/Cineby/presence.ts diff --git a/websites/C/Cineby/api.ts b/websites/C/Cineby/api.ts new file mode 100644 index 000000000000..7fcd88d8e665 --- /dev/null +++ b/websites/C/Cineby/api.ts @@ -0,0 +1,111 @@ +/* eslint-disable camelcase */ +// Hack to resolve Deepscan +const no_op = (a: number) => a + 1; +no_op(0); + +export interface Details { + poster_path?: string; +} + +export interface TvDetails extends Details { + name?: string; + season_poster?: string; + episode_title?: string; + episode_number?: number; + season_number?: number; +} + +export interface EpisodeDetails { + name: string; + episode_number: number; + season_number: number; +} + +export interface MovieDetails extends Details { + title?: string; + release_date?: string; + runtime?: number; +} + +export interface AnimeDetails { + details: { + title: string; + thumbnail: string; + episodes: { + episode: number; + title: string; + }[]; + }; +} + +const cache: Map = new Map(); + +export class CinebyApi { + private static readonly BASE_URL = "https://db.cineby.app/3"; + private static readonly API_KEY = "269890f657dddf4635473cf4cf456576"; + + private static readonly ANIME_URL = "https://api.cineby.app/hianime"; + + public static async getCurrent( + pathname: string + ): Promise { + if (cache.has(pathname)) return cache.get(pathname) as T; + + const [type, id] = pathname.split("/").slice(1), + response = await fetch( + `${this.BASE_URL}/${type}/${id}?language=en&api_key=${this.API_KEY}` + ); + + if (type === "tv") { + const json = await response.json(), + episode = await this.getCurrentEpisode(pathname), + returnData = { + ...json, + season_poster: json.seasons[episode.season_number - 1].poster_path, + episode_title: episode.name, + episode_number: episode.episode_number, + season_number: episode.season_number, + } as T; + + cache.set(pathname, returnData); + + return returnData; + } + + const json = await response.json(); + cache.set(pathname, json); + + return json; + } + + private static async getCurrentEpisode( + pathname: string + ): Promise { + const [id, season, episode] = pathname.split("/").slice(2), + response = await fetch( + `${this.BASE_URL}/tv/${id}/season/${season ?? 1}/episode/${ + episode ?? 1 + }?language=en&api_key=${this.API_KEY}` + ); + + return response.json(); + } + + public static async getCurrentAnime(pathname: string): Promise { + if (cache.has(pathname)) return cache.get(pathname) as AnimeDetails; + + const { search } = document.location, + id = pathname.split("/")[2], + response = await fetch( + `${ + this.ANIME_URL + }/sources-with-id?providerId=${id}&dub=${new URLSearchParams( + search + ).get("dub")}` + ); + + cache.set(pathname, await response.json()); + + return response.json(); + } +} diff --git a/websites/C/Cineby/metadata.json b/websites/C/Cineby/metadata.json new file mode 100644 index 000000000000..0d54118ce6f5 --- /dev/null +++ b/websites/C/Cineby/metadata.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://schemas.premid.app/metadata/1.12", + "apiVersion": 1, + "author": { + "id": "374905512661221377", + "name": "slowlife." + }, + "service": "Cineby", + "description": { + "en": "Watch Free TV Shows Online, Watch Free Movies Online" + }, + "url": "www.cineby.app", + "version": "1.0.0", + "logo": "https://i.imgur.com/zAKeytL.png", + "thumbnail": "https://i.imgur.com/MrvcmcK.png", + "color": "#fb0013", + "category": "videos", + "tags": [ + "series", + "movies", + "anime" + ], + "settings": [ + { + "id": "showBrowsing", + "title": "Show Browsing", + "icon": "fad fa-search", + "value": false + }, + { + "id": "useActivityName", + "title": "Show Title as Activity", + "icon": "fad fa-user-edit", + "value": true + }, + { + "id": "showCover", + "title": "Show Cover", + "icon": "fas fa-images", + "value": true + } + ] +} \ No newline at end of file diff --git a/websites/C/Cineby/presence.ts b/websites/C/Cineby/presence.ts new file mode 100644 index 000000000000..c23d308fc7c8 --- /dev/null +++ b/websites/C/Cineby/presence.ts @@ -0,0 +1,93 @@ +import { CinebyApi, MovieDetails, TvDetails } from "./api"; + +const presence = new Presence({ + clientId: "1325115346696273993", + }), + startTimestamp = Math.floor(Date.now() / 1000); + +presence.on("UpdateData", async () => { + const presenceData: PresenceData = { + largeImageKey: "https://i.imgur.com/zAKeytL.png", + details: "Browsing", + type: ActivityType.Watching, + startTimestamp, + }, + { pathname } = document.location, + [showBrowsing, useActivityName, showCover] = await Promise.all([ + presence.getSetting("showBrowsing"), + presence.getSetting("useActivityName"), + presence.getSetting("showCover"), + ]); + + switch (pathname.split("/")[1]) { + case "movie": { + const { + title, + poster_path: posterPath, + release_date: releaseDate, + runtime, + } = await CinebyApi.getCurrent(pathname); + + if (useActivityName) presenceData.name = title; + presenceData.details = title; + presenceData.state = `${releaseDate + .split("-") + .shift()} • ${runtime} minutes`; + + if (showCover) + presenceData.largeImageKey = `https://image.tmdb.org/t/p/original${posterPath}`; + break; + } + case "tv": { + const { + name: title, + season_poster: seasonPoster, + episode_title: episodeTitle, + season_number: seasonNumber, + episode_number: episodeNumber, + } = await CinebyApi.getCurrent(pathname); + + if (useActivityName) presenceData.name = title; + + presenceData.details = useActivityName ? episodeTitle : title; + presenceData.state = useActivityName + ? `Season ${seasonNumber}, Episode ${episodeNumber}` + : `S${seasonNumber}:E${episodeNumber} ${episodeTitle}`; + + if (showCover) + presenceData.largeImageKey = `https://image.tmdb.org/t/p/original${seasonPoster}`; + + break; + } + case "anime": { + const { details } = await CinebyApi.getCurrentAnime(pathname), + { title, thumbnail, episodes } = details, + { episode, title: episodeTitle } = episodes.find( + ({ episode }) => + episode === (parseInt(pathname.split("/").pop()) || 1) + ); + + if (useActivityName) presenceData.name = title; + + presenceData.details = useActivityName + ? episodeTitle.replace(/E[0-9]{1,}: /, "").trim() + : title; + presenceData.state = `Episode ${episode}`; + + if (showCover) presenceData.largeImageKey = thumbnail; + break; + } + default: + if (!showBrowsing) return presence.clearActivity(); + } + + const video = document.querySelector("video"); + if (video) { + if (!video.paused) { + [presenceData.startTimestamp, presenceData.endTimestamp] = + presence.getTimestampsfromMedia(video); + } else presenceData.smallImageKey = Assets.Pause; + } + + presence.setActivity(presenceData); +});