Skip to content

Commit

Permalink
update: add support for configurable manifest.json
Browse files Browse the repository at this point in the history
  • Loading branch information
hexdecimal16 committed Aug 21, 2024
1 parent c4c1b0f commit 3f72e8b
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 65 deletions.
3 changes: 2 additions & 1 deletion src/constants/costants.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export const MAX_PUPPETEER_REQUESTS = 2;
export const NETWORK_TIMEOUT = 2000;
export const NETWORK_TIMEOUT = 2000;
export const DEFAULT_PROVIDERS = ["imdb", "rotten_tomatoes", "metacritic", "crunchyroll", "filmaffinity", "all"];
70 changes: 60 additions & 10 deletions src/handlers/catalogHandler.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,81 @@
import axios from 'axios';
import { scrapeRatings } from '../utils/ratingScrapers';
import { CINEMETA_BASE_URL, CINEMETA_CATALOG_URL } from '../constants/urls';
import { closeCacheClient, getCacheClient } from '../cache';
import { DEFAULT_PROVIDERS } from '../constants/costants';

async function fetchCatalog(url: string): Promise<any> {
async function fetchCatalog(url: string, providers: string[]): Promise<any> {
const response = await axios.get(url);
response.data.metas = await Promise.all(response.data.metas.map(async (meta: any) => {
return scrapeRatings(meta.id, meta.type);
return scrapeRatings(meta.id, meta.type, providers);
}));
return response.data;
}

export async function trendingCatalog(type: string, extra: any): Promise<any> {
export async function trendingCatalog(type: string, extra: any, providers: string[]): Promise<any> {
const url = `${CINEMETA_CATALOG_URL}/top/catalog/${type}/top/genre=${extra.genre || ''}&skip=${extra.skip || 0}.json`;
return fetchCatalog(url);
return fetchCatalog(url, providers);
}

export async function featuredCataloge(type: string, extra: any): Promise<any> {
export async function featuredCataloge(type: string, extra: any, providers: string[]): Promise<any> {
const url = `${CINEMETA_CATALOG_URL}/imdbRating/catalog/${type}/imdbRating/genre=${extra.genre || ''}&skip=${extra.skip || 0}.json`;
return fetchCatalog(url);
return fetchCatalog(url, providers);
}

export async function searchCatalog(type: string, extra: any): Promise<any> {
export async function searchCatalog(type: string, extra: any, providers: string[]): Promise<any> {
const url = `${CINEMETA_BASE_URL}/catalog/${type}/search=${extra.search || ''}&skip=${extra.skip || 0}.json`;
return fetchCatalog(url);
return fetchCatalog(url, providers);
}

export async function bestYearByYearCatalog(type: string, extra: any): Promise<any> {
export async function bestYearByYearCatalog(type: string, extra: any, providers: string[]): Promise<any> {
const url = `${CINEMETA_CATALOG_URL}/year/catalog/${type}/year/genre=${extra.genre || ''}&skip=${extra.skip || 0}.json`;
return fetchCatalog(url);
return fetchCatalog(url, providers);
}

export async function handleCatalogRequest({ id, type, extra, config }: any) {
let providers = DEFAULT_PROVIDERS;
if (config && config.providers) {
providers = config.providers;
}
let cacheClient = await getCacheClient();
const key = id + JSON.stringify(extra);
if (cacheClient != null && !cacheClient.isOpen) {
console.log("Cache is not open, opening it again");
cacheClient = await getCacheClient();
}
try {
// Check if the response is cached
const cachedResponse = await cacheClient?.get(key);
if (cachedResponse) {
const response = JSON.parse(cachedResponse);
return response;
}
let response = { metas: [] };
switch (id) {
case "trending":
response = await trendingCatalog(type, extra, providers);
break;
case "featured":
response = await featuredCataloge(type, extra, providers);
break;
case "search":
response = await searchCatalog(type, extra, providers);
break;
case "best_yoy":
response = await bestYearByYearCatalog(type, extra, providers);
break;
default:
response = { metas: [] };
}

// Cache the response for 6 hours
cacheClient?.set(key, JSON.stringify(response));
cacheClient?.expire(key, 21600);
return response;
} catch (error) {
console.error("Error in CatalogHandler:", error);
return { metas: [] };
} finally {
closeCacheClient();
}
}
9 changes: 7 additions & 2 deletions src/handlers/metaHandler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { DEFAULT_PROVIDERS } from '../constants/costants';
import { scrapeRatings } from '../utils/ratingScrapers';

export async function handleMetaRequest(id: string, type: string) {
export async function handleMetaRequest({ id, type, extra, config }: any) {
let providers = DEFAULT_PROVIDERS;
if (config && config.providers) {
providers = config.providers;
}
console.log('Handling meta request for:', id, type);
return scrapeRatings(id, type);
return scrapeRatings(id, type, providers);
}
45 changes: 4 additions & 41 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { addonBuilder, Args, ContentType, MetaDetail, serveHTTP } from "stremio-addon-sdk";
import { addonBuilder, Args, ContentType, serveHTTP } from "stremio-addon-sdk-next";
import { handleMetaRequest } from "./handlers/metaHandler";
import { trendingCatalog, featuredCataloge, searchCatalog, bestYearByYearCatalog } from "./handlers/catalogHandler";
import { handleCatalogRequest } from "./handlers/catalogHandler";
import manifest from "./manifest";
import dotenv from "dotenv";
import { closeCacheClient, getCacheClient } from "./cache";
import fs from "fs";

dotenv.config();

Expand All @@ -13,53 +11,18 @@ const builder = new addonBuilder(manifest);
// Catalog Handlers
builder.defineCatalogHandler(async (args: Args) => {
console.log("CatalogHandler args:", args);
let cacheClient = await getCacheClient();
const key = args.id + JSON.stringify(args.extra);
if (cacheClient != null && !cacheClient.isOpen) {
console.log("Cache is not open, opening it again");
cacheClient = await getCacheClient();
}
try {
// Check if the response is cached
const cachedResponse = await cacheClient?.get(key);
if (cachedResponse) {
const response = JSON.parse(cachedResponse);
return response;
}
let response = { metas: [] };
switch (args.id) {
case "trending":
response = await trendingCatalog(args.type, args.extra);
break;
case "featured":
response = await featuredCataloge(args.type, args.extra);
break;
case "search":
response = await searchCatalog(args.type, args.extra);
break;
case "best_yoy":
response = await bestYearByYearCatalog(args.type, args.extra);
break;
default:
response = { metas: [] };
}

// Cache the response for 6 hours
cacheClient?.set(key, JSON.stringify(response));
cacheClient?.expire(key, 21600);
return response;
return await handleCatalogRequest(args);
} catch (error) {
console.error("Error in CatalogHandler:", error);
return { metas: [] };
} finally {
closeCacheClient();
}
});

// Meta Handlers
builder.defineMetaHandler(async (args: { type: ContentType, id: string }) => {
try {
return { meta: await handleMetaRequest(args.id, args.type) };
return { meta: await handleMetaRequest(args) };
} catch (error) {
console.error("Error in MetaHandler:", error);
return { meta: {} as any };
Expand Down
8 changes: 7 additions & 1 deletion src/manifest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Manifest } from "stremio-addon-sdk";
import { Providers } from "./utils/filter";

const manifest: Manifest = {
id: "org.stremio.ratings",
Expand Down Expand Up @@ -189,7 +190,12 @@ const manifest: Manifest = {
}
]
}
]
],
behaviorHints: {
configurable: true,
configurationRequired: true,
},
config: Providers,
};


Expand Down
10 changes: 10 additions & 0 deletions src/utils/filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ManifestConfig } from "stremio-addon-sdk"

export const Providers: ManifestConfig[] = [{
key: 'providers',
title: 'Select Providers to Fetch Ratings From',
type: 'multiselect' as any,
options: ["imdb", "rotten_tomatoes", "metacritic", "crunchyroll", "filmaffinity", "all"] as any,
default: 'all',
required: true as any
}]
10 changes: 7 additions & 3 deletions src/utils/ratingScrapers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export async function getRatingsFromYahoo(query: string, imdbId: string, cacheCl
}
}

export async function scrapeRatings(imdbId: string, type: string): Promise<MetaDetail> {
export async function scrapeRatings(imdbId: string, type: string, providers: string[]): Promise<MetaDetail> {
const cacheClient = await getCacheClient();
const metadata = await getMetadata(imdbId, type);
let ratingMap: Record<string, string> = {};
Expand All @@ -93,15 +93,19 @@ export async function scrapeRatings(imdbId: string, type: string): Promise<MetaD

// Update description with ratings
metadata.description = metadata.description || '';
const filteredRatings: Record<string, string> = {};
for (const [key, value] of Object.entries(ratingMap)) {
metadata.description += `(${key.replace('_', ' ')}: ${value}) `;
if (providers.includes('all') || providers.includes(key)) {
metadata.description += `(${key.replace('_', ' ')}: ${value}) `;
filteredRatings[key] = value;
}
}

// Modify the poster if available
if (metadata.poster && Object.keys(ratingMap).length > 0) {
const response = await axios.get(metadata.poster, { responseType: 'arraybuffer' });
const posterBase64 = Buffer.from(response.data).toString('base64');
const modifiedPoster = await addRatingToImage(posterBase64, ratingMap);
const modifiedPoster = await addRatingToImage(posterBase64, filteredRatings);
metadata.poster = modifiedPoster;
}

Expand Down
86 changes: 79 additions & 7 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -243,11 +243,56 @@
resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz"
integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==

"@types/body-parser@*":
version "1.19.5"
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4"
integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==
dependencies:
"@types/connect" "*"
"@types/node" "*"

"@types/connect@*":
version "3.4.38"
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858"
integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==
dependencies:
"@types/node" "*"

"@types/express-serve-static-core@^4.17.33":
version "4.19.5"
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz#218064e321126fcf9048d1ca25dd2465da55d9c6"
integrity sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==
dependencies:
"@types/node" "*"
"@types/qs" "*"
"@types/range-parser" "*"
"@types/send" "*"

"@types/express@^4.17.21":
version "4.17.21"
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d"
integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==
dependencies:
"@types/body-parser" "*"
"@types/express-serve-static-core" "^4.17.33"
"@types/qs" "*"
"@types/serve-static" "*"

"@types/fake-useragent@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/fake-useragent/-/fake-useragent-1.0.0.tgz#6600b6125f7d924c2d4597dda30e9ede4b2723ee"
integrity sha512-T+GsW6umr2E27FwubiKrPsjIyqx526AQH1dc1Sky31EnVW5YJ4aNV/KCWeCr9LBioSMGHi7zjNxAteHPQtzeDw==

"@types/http-errors@*":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f"
integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==

"@types/mime@^1":
version "1.3.5"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690"
integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==

"@types/node-fetch@^2.6.4":
version "2.6.11"
resolved "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz"
Expand All @@ -270,6 +315,33 @@
dependencies:
undici-types "~5.26.4"

"@types/qs@*":
version "6.9.15"
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.15.tgz#adde8a060ec9c305a82de1babc1056e73bd64dce"
integrity sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==

"@types/range-parser@*":
version "1.2.7"
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb"
integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==

"@types/send@*":
version "0.17.4"
resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a"
integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==
dependencies:
"@types/mime" "^1"
"@types/node" "*"

"@types/serve-static@*":
version "1.15.7"
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714"
integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==
dependencies:
"@types/http-errors" "*"
"@types/node" "*"
"@types/send" "*"

"@types/stremio-addon-sdk@^1.6.11":
version "1.6.11"
resolved "https://registry.npmjs.org/@types/stremio-addon-sdk/-/stremio-addon-sdk-1.6.11.tgz"
Expand Down Expand Up @@ -703,9 +775,9 @@ [email protected]:
resolved "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz"
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==

cors@^2.8.4:
cors@^2.8.4, cors@^2.8.5:
version "2.8.5"
resolved "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz"
resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
dependencies:
object-assign "^4"
Expand Down Expand Up @@ -954,9 +1026,9 @@ event-target-shim@^5.0.0:
resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz"
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==

express@^4.16.3:
express@^4.16.3, express@^4.19.2:
version "4.19.2"
resolved "https://registry.npmjs.org/express/-/express-4.19.2.tgz"
resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465"
integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==
dependencies:
accepts "~1.3.8"
Expand Down Expand Up @@ -2025,10 +2097,10 @@ stremio-addon-linter@^1.7.0:
dependencies:
semver "^5.5.0"

stremio-addon-sdk@^1.6.10:
stremio-addon-sdk-next@^1.6.10:
version "1.6.10"
resolved "https://registry.npmjs.org/stremio-addon-sdk/-/stremio-addon-sdk-1.6.10.tgz"
integrity sha512-+U/lDGv73JPZa7OOy8eMb+SkUFhnHuZGBRXuKNeXcz706oDdwC/sQe9r8Wxw2A7Cw05+f/CQIJSl4zIcmKBkGg==
resolved "https://registry.yarnpkg.com/stremio-addon-sdk-next/-/stremio-addon-sdk-next-1.6.10.tgz#bd4f784398a8e4f8af4f7ebd7950dbee5664051c"
integrity sha512-PyRklHAbQTuGp7AFiabPfBepirE49ya98JbUKuWsRyVz1Ejnyj/tK5UfjPuEPCARyQy5+00DE8eLUjGNz69ThA==
dependencies:
chalk "^2.4.2"
cors "^2.8.4"
Expand Down

0 comments on commit 3f72e8b

Please sign in to comment.