diff --git a/package-lock.json b/package-lock.json index 926ae7a..9abb124 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "cacheable": "^2.0.0", "cheerio": "^1.0.0", "express": "^5.1.0", + "fast-levenshtein": "^3.0.0", "fetch-socks": "^1.3.2", "http-cache-semantics": "^4.2.0", "minimatch": "^10.0.3", @@ -32,6 +33,7 @@ "@tsconfig/node22": "^22.0.2", "@types/bytes": "^3.1.5", "@types/express": "^5.0.1", + "@types/fast-levenshtein": "^0.0.4", "@types/http-cache-semantics": "^4.0.4", "@types/jest": "^30.0.0", "@types/make-fetch-happen": "^10.0.4", @@ -1721,6 +1723,13 @@ "@types/send": "*" } }, + "node_modules/@types/fast-levenshtein": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/fast-levenshtein/-/fast-levenshtein-0.0.4.tgz", + "integrity": "sha512-tkDveuitddQCxut1Db8eEFfMahTjOumTJGPHmT9E7KUH+DkVq9WTpVvlfenf3S+uCBeu8j5FP2xik/KfxOEjeA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", @@ -4929,11 +4938,22 @@ "license": "MIT" }, "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", + "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==", + "license": "MIT", + "dependencies": { + "fastest-levenshtein": "^1.0.7" + } + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } }, "node_modules/fastq": { "version": "1.19.1", @@ -8268,6 +8288,13 @@ "node": ">= 0.8.0" } }, + "node_modules/optionator/node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, "node_modules/own-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", diff --git a/package.json b/package.json index 62cf66f..bde1f78 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "cacheable": "^2.0.0", "cheerio": "^1.0.0", "express": "^5.1.0", + "fast-levenshtein": "^3.0.0", "fetch-socks": "^1.3.2", "http-cache-semantics": "^4.2.0", "minimatch": "^10.0.3", @@ -54,6 +55,7 @@ "@tsconfig/node22": "^22.0.2", "@types/bytes": "^3.1.5", "@types/express": "^5.0.1", + "@types/fast-levenshtein": "^0.0.4", "@types/http-cache-semantics": "^4.0.4", "@types/jest": "^30.0.0", "@types/make-fetch-happen": "^10.0.4", diff --git a/src/source/FourKHDHub.ts b/src/source/FourKHDHub.ts index 7265e95..0200621 100644 --- a/src/source/FourKHDHub.ts +++ b/src/source/FourKHDHub.ts @@ -2,6 +2,7 @@ import bytes from 'bytes'; import * as cheerio from 'cheerio'; import { BasicAcceptedElems, CheerioAPI } from 'cheerio'; import { AnyNode } from 'domhandler'; +import levenshtein from 'fast-levenshtein'; import rot13Cipher from 'rot13-cipher'; import { ContentType } from 'stremio-addon-sdk'; import { Context, CountryCode, Meta } from '../types'; @@ -68,13 +69,20 @@ export class FourKHDHub extends Source { const $ = cheerio.load(html); - const yearMatches = $(`.movie-card`) - .slice(0, 2) - .filter((_i, el) => parseInt($('.movie-card-meta', el).text()) === year); - const exactTitleMatches = yearMatches - .filter((_i, el) => $('.movie-card-title', el).text().includes(name)); + return $(`.movie-card:has(.movie-card-format:contains("${tmdbId.season ? 'Series' : 'Movies'}"))`) + .filter((_i, el) => { + const movieCardYear = parseInt($('.movie-card-meta', el).text()); - return (exactTitleMatches.length ? exactTitleMatches : yearMatches) + return Math.abs(movieCardYear - year) <= 1; + }) + .filter((_i, el) => { + const movieCardTitle = $('.movie-card-title', el) + .text() + .replace(/\[.*?]/, '') + .trim(); + + return levenshtein.get(movieCardTitle, name, { useCollator: true }) < 5; + }) .map((_i, el) => new URL($(el).attr('href') as string, this.baseUrl)) .get(0); }; diff --git a/src/source/__fixtures__/FourKHDHub/https:4khdhub.fanscrank-movie-1745.html b/src/source/__fixtures__/FourKHDHub/https:4khdhub.fanscrank-movie-1745.html index 13ecf98..21a8951 100644 --- a/src/source/__fixtures__/FourKHDHub/https:4khdhub.fanscrank-movie-1745.html +++ b/src/source/__fixtures__/FourKHDHub/https:4khdhub.fanscrank-movie-1745.html @@ -43,7 +43,7 @@ - + @@ -1420,29 +1420,63 @@ p, .metadata-value, .episode-description, .movie-tagline {
+
+
+
+
+
+
+
+
-
-
-
-
-
- @@ -1264,9 +1424,9 @@ p, .metadata-value, .episode-description, .movie-tagline {
+
-
-
-
-
-
-
+
+
+
+
+
+
+