From f86ec38b2334be1e14efb5d817a2ca6837d10f44 Mon Sep 17 00:00:00 2001 From: Viren070 Date: Sun, 26 Jan 2025 19:05:37 +0000 Subject: [PATCH] refactor: improve base parser - probably breaks a few things --- packages/formatters/src/utils.ts | 54 +++++ packages/parser/src/index.ts | 1 - packages/parser/src/regex.ts | 200 +++++++++--------- packages/parser/src/utils.ts | 31 --- packages/wrappers/src/base.ts | 287 +++++++++++++++++++------- packages/wrappers/src/comet.ts | 48 +---- packages/wrappers/src/debridio.ts | 75 ++----- packages/wrappers/src/dmmCast.ts | 6 +- packages/wrappers/src/easynews.ts | 14 +- packages/wrappers/src/easynewsPlus.ts | 14 +- packages/wrappers/src/jackettio.ts | 70 +------ packages/wrappers/src/mediafusion.ts | 71 +------ packages/wrappers/src/orion.ts | 42 +--- packages/wrappers/src/peerflix.ts | 54 +---- packages/wrappers/src/torbox.ts | 6 +- packages/wrappers/src/torrentio.ts | 73 +------ 16 files changed, 420 insertions(+), 626 deletions(-) delete mode 100644 packages/parser/src/utils.ts diff --git a/packages/formatters/src/utils.ts b/packages/formatters/src/utils.ts index f41e70d9..5d498f3b 100644 --- a/packages/formatters/src/utils.ts +++ b/packages/formatters/src/utils.ts @@ -33,6 +33,9 @@ export function emojiToLanguage(emoji: string): string | undefined { )?.[0]; } +export function codeToLanguage(code: string): string | undefined { + return codeLanguageMap[code]; +} /** * A mapping of language names to their corresponding emoji flags. * @@ -90,3 +93,54 @@ const languageEmojiMap: Record = { latino: '💃🏻', Latino: '🇲🇽', }; + +const codeLanguageMap: Record = { + EN: 'english', + JA: 'japanese', + ZH: 'chinese', + RU: 'russian', + AR: 'arabic', + PT: 'portuguese', + ES: 'spanish', + FR: 'french', + DE: 'german', + IT: 'italian', + KO: 'korean', + HI: 'hindi', + BN: 'bengali', + PA: 'punjabi', + MR: 'marathi', + GU: 'gujarati', + TA: 'tamil', + TE: 'telugu', + KN: 'kannada', + ML: 'malayalam', + TH: 'thai', + VI: 'vietnamese', + ID: 'indonesian', + TR: 'turkish', + HE: 'hebrew', + FA: 'persian', + UK: 'ukrainian', + EL: 'greek', + LT: 'lithuanian', + LV: 'latvian', + ET: 'estonian', + PL: 'polish', + CS: 'czech', + SK: 'slovak', + HU: 'hungarian', + RO: 'romanian', + BG: 'bulgarian', + SR: 'serbian', + HR: 'croatian', + SL: 'slovenian', + NL: 'dutch', + DA: 'danish', + FI: 'finnish', + SV: 'swedish', + NO: 'norwegian', + MS: 'malay', + LA: 'latino', + MX: 'Latino', +}; diff --git a/packages/parser/src/index.ts b/packages/parser/src/index.ts index 4f426d56..06d8c360 100644 --- a/packages/parser/src/index.ts +++ b/packages/parser/src/index.ts @@ -1,3 +1,2 @@ export * from './parser'; export * from './regex'; -export * from './utils'; diff --git a/packages/parser/src/regex.ts b/packages/parser/src/regex.ts index 8f58de84..09b6ccba 100644 --- a/packages/parser/src/regex.ts +++ b/packages/parser/src/regex.ts @@ -1,111 +1,117 @@ +const createRegex = (pattern: string): RegExp => + new RegExp(`(? base > parseStream: No filename found in behaviorHints, attempting to parse from description` + ); + const lines = description.split('\n'); + filename = + lines.find( + (line: string) => + line.match( + /(? base > parseStream: With description: ${description.replace(/\n/g, ' ').trim()}, chose filename as: ${filename.replace(/\n/g, ' ').trim()}` + ); + } else if (!description) { + console.log( + `|WRN| wrappers > base > parseStream: No description found, filename could not be determined` + ); + } + + let stringToParse: string = filename || description || ''; + if ( + !( + filename.match( + /(? + emojiToLanguage(codeOrFlag) || codeToLanguage(codeOrFlag) + ) + .filter((lang) => lang !== undefined) + .map((lang) => + lang + .trim() + .split(' ') + .map( + (word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() + ) + .join(' ') + ) + .forEach((lang) => { + if (lang && !parsedInfo.languages.includes(lang)) { + parsedInfo.languages.push(lang); + } + }); + + const duration = stream.duration || this.extractDurationInMs(description); + // look for providers + let provider: ParsedStream['provider'] = this.parseServiceData( + stream.name || '' + ); + + if (stream.infoHash && provider) { + // if its a p2p result, it is not from a debrid service + provider = undefined; + } + return this.createParsedResult( + parsedInfo, + stream, + filename, + size, + provider, + seeders ? parseInt(seeders) : undefined, + undefined, + indexer, + duration, + stream.personal, + stream.infoHash || this.extractInfoHash(stream.url || '') + ); + } protected parseServiceData( string: string @@ -249,84 +365,105 @@ export class BaseWrapper { } return provider; } - protected parseStream(stream: { - [key: string]: any; - }): ParsedStream | undefined { - // attempt to look for filename in behaviorHints.filename, return undefined if not found - let filename = stream?.behaviorHints?.filename; + protected extractSizeInBytes(string: string, k: number): number { + const sizePattern = /(\d+(\.\d+)?)\s?(KB|MB|GB)/i; + const match = string.match(sizePattern); + if (!match) return 0; - // if filename behaviorHint is not present, attempt to look for a filename in the stream description or title - let description = stream.description || stream.title; + const value = parseFloat(match[1]); + const unit = match[3]; - if (!filename && description) { - console.log( - `|DBG| wrappers > base > parseStream: No filename found in behaviorHints, attempting to parse from description` - ); - const lines = description.split('\n'); - filename = - lines.find( - (line: string) => - line.match( - /(? base > parseStream: With description: ${description}, found filename: ${filename}` - ); - } else if (!description) { - console.log( - `|WRN| wrappers > base > parseStream: No description found, filename could not be determined` - ); + switch (unit.toUpperCase()) { + case 'TB': + return value * k * k * k * k; + case 'GB': + return value * k * k * k; + case 'MB': + return value * k * k; + case 'KB': + return value * k; + default: + return 0; + } + } + + protected extractDurationInMs(input: string): number { + // Regular expression to match different formats of time durations + const regex = + /(\d+)h[:\s]?(\d+)m[:\s]?(\d+)s|(\d+)h[:\s]?(\d+)m|(\d+)h|(\d+)m|(\d+)s/gi; + const match = regex.exec(input); + if (!match) { + return 0; } - let parsedInfo: ParsedNameData = parseFilename(filename || ''); + const hours = parseInt(match[1] || match[4] || match[5] || '0', 10); + const minutes = parseInt(match[2] || match[5] || match[6] || '0', 10); + const seconds = parseInt(match[3] || match[6] || match[7] || '0', 10); - // look for size in one of the many random places it could be - let size: number | undefined; - size = - stream.behaviorHints?.videoSize || - stream.size || - stream.sizebytes || - (description && extractSizeInBytes(description, 1024)) || - (stream.name && extractSizeInBytes(stream.name, 1024)) || - undefined; + // Convert to milliseconds + const totalMilliseconds = (hours * 3600 + minutes * 60 + seconds) * 1000; - // look for seeders - let seeders: string | undefined; - if (description) { - seeders = description.match(/(👥|👤) (\d+)/)?.[2]; - } + return totalMilliseconds; + } - // look for indexer - let indexer: string | undefined; - if (description) { - const indexerMatch = RegExp( - /[🌐⚙️🔗] ([^\s\p{Emoji_Presentation}]+(?:\s[^\s\p{Emoji_Presentation}]+)*)/u - ).exec(description || ''); - indexer = indexerMatch ? indexerMatch[1] : undefined; - } + protected extractStringBetweenEmojis( + startingEmojis: string[], + string: string, + endingEmojis?: string[] + ): string | undefined { + const emojiPattern = /[\p{Emoji_Presentation}]/u; + const startPattern = new RegExp(`(${startingEmojis.join('|')})`, 'u'); + const endPattern = endingEmojis + ? new RegExp(`(${endingEmojis.join('|')}|$|\n)`, 'u') + : new RegExp(`(${emojiPattern.source}|$|\n)`, 'u'); - // look for providers - let provider: ParsedStream['provider'] = this.parseServiceData( - stream.name || '' - ); + const startMatch = string.match(startPattern); + if (!startMatch) return undefined; - if (stream.infoHash && provider) { - // if its a p2p result, it is not from a debrid service - provider = undefined; - } - return this.createParsedResult( - parsedInfo, - stream, - filename, - size, - provider, - seeders ? parseInt(seeders) : undefined, - undefined, - indexer, - stream.duration, - stream.personal, - stream.infoHash || this.extractInfoHash(stream.url || '') - ); + const startIndex = startMatch.index! + startMatch[0].length; + const remainingString = string.slice(startIndex); + + const endMatch = remainingString.match(endPattern); + const endIndex = endMatch ? endMatch.index! : remainingString.length; + + return remainingString.slice(0, endIndex).trim(); + } + + protected extractStringAfter( + startingPattern: string, + string: string, + endingPattern?: string + ) { + const startPattern = new RegExp(startingPattern, 'u'); + const endPattern = endingPattern + ? new RegExp(endingPattern, 'u') + : new RegExp(/$/u); + + const startMatch = string.match(startPattern); + if (!startMatch) return undefined; + + const startIndex = startMatch.index! + startMatch[0].length; + const remainingString = string.slice(startIndex); + + const endMatch = remainingString.match(endPattern); + const endIndex = endMatch ? endMatch.index! : remainingString.length; + + return remainingString.slice(0, endIndex).trim(); + } + + protected extractCountryFlags(string: string): string[] { + const countryFlagPattern = /[\p{Regional_Indicator}]/u; + const matches = string.match(countryFlagPattern); + return matches ? [...new Set(matches)] : []; + } + + protected extractCountryCodes(string: string): string[] { + const countryCodePattern = /\b(?!AC|DV)[A-Z]{2}\b/g; + const matches = string.match(countryCodePattern); + return matches ? [...new Set(matches)] : []; + } + + protected extractInfoHash(url: string): string | undefined { + return url.match(/(?<=[-/[(;&])[a-fA-F0-9]{40}(?=[-\]\)/;&])/)?.[0]; } } diff --git a/packages/wrappers/src/comet.ts b/packages/wrappers/src/comet.ts index c26483b7..29361998 100644 --- a/packages/wrappers/src/comet.ts +++ b/packages/wrappers/src/comet.ts @@ -1,15 +1,9 @@ -import { AddonDetail, ParsedNameData, StreamRequest } from '@aiostreams/types'; -import { parseFilename, extractSizeInBytes } from '@aiostreams/parser'; +import { AddonDetail, StreamRequest } from '@aiostreams/types'; import { ParsedStream, Stream, Config } from '@aiostreams/types'; import { BaseWrapper } from './base'; -import { addonDetails, serviceDetails } from '@aiostreams/utils'; +import { addonDetails } from '@aiostreams/utils'; import { Settings } from '@aiostreams/utils'; -interface CometStream extends Stream { - torrentTitle?: string; - torrentSize?: number; -} - export class Comet extends BaseWrapper { constructor( configString: string | null, @@ -31,44 +25,6 @@ export class Comet extends BaseWrapper { indexerTimeout || Settings.DEFAULT_COMET_TIMEOUT ); } - - protected parseStream(stream: CometStream): ParsedStream { - const filename = - stream.behaviorHints?.filename?.trim() || - stream.description?.split('\n')[0] || - stream.torrentTitle; - - const parsedFilename: ParsedNameData = parseFilename( - filename || stream.description || '' - ); - const sizeInBytes = stream.torrentSize - ? stream.torrentSize - : stream.description - ? extractSizeInBytes(stream.description, 1024) - : undefined; - - const debrid = this.parseServiceData(stream.name || ''); - - const indexerMatch = RegExp(/🔎 ([a-zA-Z0-9]+)/).exec( - stream.description || '' - ); - const indexer = indexerMatch ? indexerMatch[1] : undefined; - - const parsedStream: ParsedStream = this.createParsedResult( - parsedFilename, - stream, - filename, - sizeInBytes, - debrid, - undefined, - undefined, - indexer, - undefined, - undefined, - this.extractInfoHash(stream.url || '') - ); - return parsedStream; - } } const getCometConfig = (debridService: string, debridApiKey: string) => { diff --git a/packages/wrappers/src/debridio.ts b/packages/wrappers/src/debridio.ts index e151cd0b..60296f44 100644 --- a/packages/wrappers/src/debridio.ts +++ b/packages/wrappers/src/debridio.ts @@ -1,10 +1,8 @@ -import { AddonDetail, ParsedNameData, StreamRequest } from '@aiostreams/types'; -import { parseFilename, extractSizeInBytes } from '@aiostreams/parser'; +import { AddonDetail, StreamRequest } from '@aiostreams/types'; import { ParsedStream, Stream, Config } from '@aiostreams/types'; import { BaseWrapper } from './base'; -import { addonDetails, serviceDetails } from '@aiostreams/utils'; +import { addonDetails } from '@aiostreams/utils'; import { Settings } from '@aiostreams/utils'; -import { emojiToLanguage } from '@aiostreams/formatters'; export class Debridio extends BaseWrapper { constructor( @@ -29,66 +27,19 @@ export class Debridio extends BaseWrapper { } protected parseStream(stream: Stream): ParsedStream { - const [filename, metaString, languageString] = - stream.title?.split('\n') || []; - - const parsedFilename: ParsedNameData = parseFilename( - filename || stream.title || '' - ); - - const sizeInBytes = stream.behaviorHints?.videoSize - ? stream.behaviorHints.videoSize - : metaString - ? extractSizeInBytes(metaString, 1024) - : undefined; - - const debrid = this.parseServiceData( - stream.name?.split('\n')?.[0] || '' - ) || { - id: 'easydebrid', - cached: false, - }; - const seedersMatch = RegExp(/👤 (\d+)/).exec(stream.title!); - const seeders = seedersMatch ? parseInt(seedersMatch[1]) : undefined; - - const indexerMatch = RegExp(/⚙️ (.+)/).exec( - stream.title?.split('\n')[1] || '' - ); - const indexer = indexerMatch ? indexerMatch[1] : undefined; - - if (languageString) { - const languages = languageString.replace('🌐', '').split('|'); - languages.forEach((language, index) => { - let convertedLanguage = language.trim(); - if (convertedLanguage === 'Multi Audio') { - convertedLanguage = 'Multi'; - } - - convertedLanguage = - emojiToLanguage(convertedLanguage) || convertedLanguage; - convertedLanguage = convertedLanguage.replace(/\b\w/g, (char) => - char.toUpperCase() - ); - if (!parsedFilename.languages.includes(convertedLanguage)) { - parsedFilename.languages.push(convertedLanguage); - } - }); + const superParsedStream = super.parseStream(stream); + if (!superParsedStream) { + return superParsedStream; } - const parsedStream: ParsedStream = this.createParsedResult( - parsedFilename, - stream, - filename, - sizeInBytes, - debrid, - seeders, - undefined, - indexer, - undefined, - undefined, - this.extractInfoHash(stream.url || '') - ); - return parsedStream; + if (!superParsedStream.provider) { + superParsedStream.provider = { + id: 'easydebrid', + cached: false, + }; + } + + return superParsedStream; } } diff --git a/packages/wrappers/src/dmmCast.ts b/packages/wrappers/src/dmmCast.ts index 7c6176fa..b56f8711 100644 --- a/packages/wrappers/src/dmmCast.ts +++ b/packages/wrappers/src/dmmCast.ts @@ -1,4 +1,4 @@ -import { parseFilename, extractSizeInBytes } from '@aiostreams/parser'; +import { parseFilename } from '@aiostreams/parser'; import { ParsedStream, Stream, @@ -43,8 +43,8 @@ export class DMMCast extends BaseWrapper { : stream.behaviorHints?.filename?.trim(); const parsedFilename: ParsedNameData = parseFilename(filename || ''); - const sizeInBytes = stream.title - ? extractSizeInBytes(stream.title, 1024) + const sizeInBytes = stream.title?.split('\n').pop()?.includes('📦') + ? this.extractSizeInBytes(stream.title.split('\n').pop()!, 1024) : 0; const parsedStream: ParsedStream = this.createParsedResult( diff --git a/packages/wrappers/src/easynews.ts b/packages/wrappers/src/easynews.ts index 43cca297..0c1b0d6b 100644 --- a/packages/wrappers/src/easynews.ts +++ b/packages/wrappers/src/easynews.ts @@ -1,12 +1,8 @@ -import { AddonDetail, ParsedNameData, StreamRequest } from '@aiostreams/types'; -import { - parseFilename, - extractSizeInBytes, - extractDurationInMs, -} from '@aiostreams/parser'; +import { ParsedNameData, StreamRequest } from '@aiostreams/types'; +import { parseFilename } from '@aiostreams/parser'; import { ParsedStream, Stream, Config } from '@aiostreams/types'; import { BaseWrapper } from './base'; -import { addonDetails, serviceDetails } from '@aiostreams/utils'; +import { serviceDetails } from '@aiostreams/utils'; import { Settings } from '@aiostreams/utils'; export class Easynews extends BaseWrapper { @@ -39,7 +35,7 @@ export class Easynews extends BaseWrapper { filename || stream.description || '' ); const sizeInBytes = stream.description - ? extractSizeInBytes(sizeString, 1024) + ? this.extractSizeInBytes(sizeString, 1024) : undefined; const provider = { @@ -47,7 +43,7 @@ export class Easynews extends BaseWrapper { cached: true, }; - const durationInMs = extractDurationInMs(durationString || ''); + const durationInMs = this.extractDurationInMs(durationString || ''); const parsedStream: ParsedStream = this.createParsedResult( parsedFilename, diff --git a/packages/wrappers/src/easynewsPlus.ts b/packages/wrappers/src/easynewsPlus.ts index b5016bf6..0b744629 100644 --- a/packages/wrappers/src/easynewsPlus.ts +++ b/packages/wrappers/src/easynewsPlus.ts @@ -1,12 +1,8 @@ -import { AddonDetail, ParsedNameData, StreamRequest } from '@aiostreams/types'; -import { - parseFilename, - extractSizeInBytes, - extractDurationInMs, -} from '@aiostreams/parser'; +import { ParsedNameData, StreamRequest } from '@aiostreams/types'; +import { parseFilename } from '@aiostreams/parser'; import { ParsedStream, Stream, Config } from '@aiostreams/types'; import { BaseWrapper } from './base'; -import { addonDetails, serviceDetails } from '@aiostreams/utils'; +import { serviceDetails } from '@aiostreams/utils'; import { Settings } from '@aiostreams/utils'; export class EasynewsPlus extends BaseWrapper { @@ -41,7 +37,7 @@ export class EasynewsPlus extends BaseWrapper { const sizeInBytes = stream.behaviorHints?.videoSize ? stream.behaviorHints.videoSize : sizeString - ? extractSizeInBytes(sizeString, 1024) + ? this.extractSizeInBytes(sizeString, 1024) : undefined; const provider = { @@ -49,7 +45,7 @@ export class EasynewsPlus extends BaseWrapper { cached: true, }; - const durationInMs = extractDurationInMs(durationString || ''); + const durationInMs = this.extractDurationInMs(durationString || ''); const parsedStream: ParsedStream = this.createParsedResult( parsedFilename, diff --git a/packages/wrappers/src/jackettio.ts b/packages/wrappers/src/jackettio.ts index 28a9930e..6a6cbcfd 100644 --- a/packages/wrappers/src/jackettio.ts +++ b/packages/wrappers/src/jackettio.ts @@ -1,12 +1,8 @@ -/*{"maxTorrents":30,"priotizePackTorrents":2,"excludeKeywords":[],"debridId":"alldebrid","hideUncached":false,"sortCached":[["quality",true],["size",true]],"sortUncached":[["seeders",true]],"forceCacheNextEpisode":false,"priotizeLanguages":[],"indexerTimeoutSec":60,"metaLanguage":"","enableMediaFlow":false,"mediaflowProxyUrl":"","mediaflowApiPassword":"","mediaflowPublicIp":"","qualities":[0,360,480,720,1080,2160],"indexers":["bitsearch","eztv","thepiratebay","therarbg","yts"],"debridApiKey":"KfejOmW7c5gUC3WNFCCq"}*/ - -import { AddonDetail, ParsedNameData, StreamRequest } from '@aiostreams/types'; -import { parseFilename, extractSizeInBytes } from '@aiostreams/parser'; -import { ParsedStream, Stream, Config } from '@aiostreams/types'; +import { AddonDetail, StreamRequest } from '@aiostreams/types'; +import { ParsedStream, Config } from '@aiostreams/types'; import { BaseWrapper } from './base'; -import { addonDetails, serviceDetails } from '@aiostreams/utils'; +import { addonDetails } from '@aiostreams/utils'; import { Settings } from '@aiostreams/utils'; -import { emojiToLanguage } from '@aiostreams/formatters'; // name, title, url export class Jackettio extends BaseWrapper { @@ -30,66 +26,6 @@ export class Jackettio extends BaseWrapper { indexerTimeout || Settings.DEFAULT_JACKETTIO_TIMEOUT ); } - - protected parseStream(stream: Stream): ParsedStream { - const filename = - stream.behaviorHints?.filename?.trim() || - stream.title?.split('\n')[0] || - undefined; - - const parsedFilename: ParsedNameData = parseFilename( - filename || stream.title || '' - ); - const sizeInBytes = stream.title - ? extractSizeInBytes(stream.title, 1024) - : undefined; - - const debrid = this.parseServiceData(stream.name || ''); - if (debrid?.id && debrid.cached === undefined) { - debrid.cached = false; - } - - const indexerAndLanguages = RegExp( - /⚙️([^\p{Emoji_Presentation}]+)([\p{Emoji_Presentation}\s]*)$/u - ).exec(stream.title || ''); - const indexer = indexerAndLanguages?.[1]; - indexerAndLanguages?.[2] - .trim() - .split(' ') - .filter((lang) => lang) - .map((lang) => { - let convertedLanguage = lang.trim(); - if (convertedLanguage === 'Multi Audio') { - convertedLanguage = 'Multi'; - } - convertedLanguage = - emojiToLanguage(convertedLanguage) || convertedLanguage; - convertedLanguage = convertedLanguage.replace(/\b\w/g, (char) => - char.toUpperCase() - ); - if (!parsedFilename.languages.includes(convertedLanguage)) { - parsedFilename.languages.push(convertedLanguage); - } - }); - - const seederMatch = stream.title?.match(/(👥|👤) (\d+)/)?.[2]; - const seeders = seederMatch ? parseInt(seederMatch[1]) : undefined; - - const parsedStream: ParsedStream = this.createParsedResult( - parsedFilename, - stream, - filename, - sizeInBytes, - debrid, - seeders, - undefined, - indexer, - undefined, - undefined, - this.extractInfoHash(stream.url || '') - ); - return parsedStream; - } } const getJackettioConfigString = ( diff --git a/packages/wrappers/src/mediafusion.ts b/packages/wrappers/src/mediafusion.ts index 70edc40d..9aa0146e 100644 --- a/packages/wrappers/src/mediafusion.ts +++ b/packages/wrappers/src/mediafusion.ts @@ -1,9 +1,7 @@ -import { AddonDetail, ParsedNameData, StreamRequest } from '@aiostreams/types'; -import { parseFilename, extractSizeInBytes } from '@aiostreams/parser'; +import { AddonDetail, StreamRequest } from '@aiostreams/types'; import { ParsedStream, Stream, Config } from '@aiostreams/types'; import { BaseWrapper } from './base'; -import { addonDetails, serviceDetails, Settings } from '@aiostreams/utils'; -import { emojiToLanguage } from '@aiostreams/formatters'; +import { addonDetails, Settings } from '@aiostreams/utils'; export class MediaFusion extends BaseWrapper { constructor( @@ -28,70 +26,15 @@ export class MediaFusion extends BaseWrapper { } protected parseStream(stream: Stream): ParsedStream { - let filename = - stream.behaviorHints?.filename?.trim() || - stream.description?.split('\n')[0].replace('📂 ', ''); - + const parsedStream: ParsedStream = super.parseStream(stream); + // handle content warning streams and join all lines into a single string if ( - filename && + parsedStream.filename && stream.description && - filename.includes('Content Warning') + parsedStream.filename.includes('Content Warning') ) { - filename = stream.description.split('\n').join(' '); + parsedStream.filename = stream.description.split('\n').join(' '); } - - const parsedFilename: ParsedNameData = parseFilename( - filename || stream.behaviorHints?.bingeGroup || stream.description || '' - ); - const sizeInBytes = stream.behaviorHints?.videoSize - ? stream.behaviorHints.videoSize - : stream.description - ? extractSizeInBytes(stream.description, 1024) - : undefined; - - const debrid = this.parseServiceData(stream.name || ''); - - const indexerMatch = RegExp( - /🔗 ([^\s\p{Emoji_Presentation}]+(?:\s[^\s\p{Emoji_Presentation}]+)*)/u - ).exec(stream.description || ''); - const indexer = indexerMatch ? indexerMatch[1] : undefined; - - const seedersMatch = RegExp(/👤 (\d+)/).exec(stream.description || ''); - const seeders = seedersMatch ? parseInt(seedersMatch[1]) : undefined; - - stream.description?.split('\n').forEach((line) => { - if (line.startsWith('🌐')) { - // the line contains the languages separated by ' + '. - // the languages can either be flag emojis or the language name. - const normaliseLanguage = (lang: string) => { - // convert emojis to language names, and uppercase the first letter of each word - return (emojiToLanguage(lang) || lang).replace(/\b\w/g, (char) => - char.toUpperCase() - ); - }; - const languages = line.replace('🌐 ', '').split(' + '); - languages.forEach((lang) => { - const normalisedLanguage = normaliseLanguage(lang); - if (!parsedFilename.languages.includes(normalisedLanguage)) { - parsedFilename.languages.push(normalisedLanguage); - } - }); - } - }); - - const parsedStream: ParsedStream = this.createParsedResult( - parsedFilename, - stream, - filename, - sizeInBytes, - debrid, - seeders, - undefined, - indexer, - undefined, - undefined, - this.extractInfoHash(stream.url || '') - ); return parsedStream; } } diff --git a/packages/wrappers/src/orion.ts b/packages/wrappers/src/orion.ts index 6e170ab6..09ebab2d 100644 --- a/packages/wrappers/src/orion.ts +++ b/packages/wrappers/src/orion.ts @@ -1,10 +1,8 @@ -import { AddonDetail, ParsedNameData, StreamRequest } from '@aiostreams/types'; -import { parseFilename, extractSizeInBytes } from '@aiostreams/parser'; +import { AddonDetail, StreamRequest } from '@aiostreams/types'; import { ParsedStream, Stream, Config } from '@aiostreams/types'; import { BaseWrapper } from './base'; -import { addonDetails, serviceDetails } from '@aiostreams/utils'; +import { addonDetails } from '@aiostreams/utils'; import { Settings } from '@aiostreams/utils'; -import { emojiToLanguage } from '@aiostreams/formatters'; // name, title, url export class OrionStremioAddon extends BaseWrapper { @@ -31,44 +29,14 @@ export class OrionStremioAddon extends BaseWrapper { } protected parseStream(stream: Stream): ParsedStream { + const parsedStream: ParsedStream = super.parseStream(stream); + // handle error streams and join all lines into a single string const filename = stream.title ? stream.title.includes('ERROR') ? `Error: ${stream.title.split('\n')[1]} - ${stream.title.split('\n')[2]}` : stream.title.split('\n')[0] : stream.behaviorHints?.filename?.trim(); - - const parsedFilename: ParsedNameData = parseFilename(filename || ''); - const sizeInBytes = stream.title - ? extractSizeInBytes(stream.title, 1024) - : 0; - - const debrid = this.parseServiceData(stream.name?.split('\n')?.[1] || ''); - if (debrid?.id && !debrid.cached) { - debrid.cached = true; - } - const seedersMatch = RegExp(/👤(\d+)/).exec(stream.title!); - const seeders = seedersMatch ? parseInt(seedersMatch[1]) : undefined; - - const indexerMatch = RegExp(/☁️(.+)/).exec( - stream.title?.split('\n')[1] || '' - ); - const indexer = indexerMatch ? indexerMatch[1] : undefined; - - // TODO: orion returns 2 letter language codes, convert them to full names - - const parsedStream: ParsedStream = this.createParsedResult( - parsedFilename, - stream, - filename, - sizeInBytes, - debrid, - seeders, - undefined, - indexer, - undefined, - undefined, - this.extractInfoHash(stream.url || '') - ); + parsedStream.filename = filename; return parsedStream; } } diff --git a/packages/wrappers/src/peerflix.ts b/packages/wrappers/src/peerflix.ts index 6d7e9ba3..bc75d084 100644 --- a/packages/wrappers/src/peerflix.ts +++ b/packages/wrappers/src/peerflix.ts @@ -1,10 +1,8 @@ -import { AddonDetail, ParsedNameData, StreamRequest } from '@aiostreams/types'; -import { parseFilename, extractSizeInBytes } from '@aiostreams/parser'; +import { AddonDetail, StreamRequest } from '@aiostreams/types'; import { ParsedStream, Stream, Config } from '@aiostreams/types'; import { BaseWrapper } from './base'; -import { addonDetails, serviceDetails } from '@aiostreams/utils'; +import { addonDetails } from '@aiostreams/utils'; import { Settings } from '@aiostreams/utils'; -import { emojiToLanguage } from '@aiostreams/formatters'; interface PeerflixStream extends Stream { seed?: string; @@ -34,54 +32,6 @@ export class Peerflix extends BaseWrapper { indexerTimeout || Settings.DEFAULT_PEERFLIX_TIMEOUT ); } - - protected parseStream(stream: PeerflixStream): ParsedStream { - const filename = stream.title - ? stream.title.split('\n')[0] - : stream.behaviorHints?.filename?.trim(); - - const parsedFilename: ParsedNameData = parseFilename(filename || ''); - const sizeInBytes = - stream.sizeBytes || - (stream.title ? extractSizeInBytes(stream.title, 1024) : 0); - - const debrid = this.parseServiceData(stream.name || ''); - const seedersMatch = RegExp(/👤 (\d+)/).exec(stream.title!); - const seeders = - parseInt(stream.seed || seedersMatch?.[1] || '0') || undefined; - - const indexerMatch = RegExp( - /[🌐] ([^\s\p{Emoji_Presentation}]+(?:\s[^\s\p{Emoji_Presentation}]+)*)/u - ).exec(stream.title || stream.description || ''); - const indexer = indexerMatch ? indexerMatch[1] : undefined; - - if ( - (stream.language == 'en' || stream.name?.includes('🇬🇧')) && - !parsedFilename.languages.includes('English') - ) { - parsedFilename.languages.push('English'); - } else if ( - (stream.language === 'es' || stream.name?.includes('🇪🇸')) && - !parsedFilename.languages.includes('Spanish') - ) { - parsedFilename.languages.push('Spanish'); - } - - const parsedStream: ParsedStream = this.createParsedResult( - parsedFilename, - stream, - filename, - sizeInBytes, - debrid, - seeders, - undefined, - indexer, - undefined, - undefined, - this.extractInfoHash(stream.url || '') - ); - return parsedStream; - } } export async function getPeerflixStreams( diff --git a/packages/wrappers/src/torbox.ts b/packages/wrappers/src/torbox.ts index 525292ce..39f342b5 100644 --- a/packages/wrappers/src/torbox.ts +++ b/packages/wrappers/src/torbox.ts @@ -6,7 +6,7 @@ import { Stream, StreamRequest, } from '@aiostreams/types'; -import { extractSizeInBytes, parseFilename } from '@aiostreams/parser'; +import { parseFilename } from '@aiostreams/parser'; import { Settings } from '@aiostreams/utils'; interface TorboxStream extends Stream { @@ -44,7 +44,7 @@ export class Torbox extends BaseWrapper { ); } - protected parseStream(stream: TorboxStream): ParsedStream | undefined { + protected parseStream(stream: TorboxStream): ParsedStream { let type = stream.type; let personal = false; if (stream.name.includes('Your Media')) { @@ -112,7 +112,7 @@ export class Torbox extends BaseWrapper { const sizeInBytes = stream.size || validateBehaviorHintSize(stream.behaviorHints?.videoSize) || - (dSize ? extractSizeInBytes(dSize, 1000) : undefined); + (dSize ? this.extractSizeInBytes(dSize, 1000) : undefined); const provider = { id: 'torbox', diff --git a/packages/wrappers/src/torrentio.ts b/packages/wrappers/src/torrentio.ts index fa1925da..5fd182b1 100644 --- a/packages/wrappers/src/torrentio.ts +++ b/packages/wrappers/src/torrentio.ts @@ -1,10 +1,8 @@ -import { AddonDetail, ParsedNameData, StreamRequest } from '@aiostreams/types'; -import { parseFilename, extractSizeInBytes } from '@aiostreams/parser'; -import { ParsedStream, Stream, Config } from '@aiostreams/types'; +import { AddonDetail, StreamRequest } from '@aiostreams/types'; +import { ParsedStream, Config } from '@aiostreams/types'; import { BaseWrapper } from './base'; -import { addonDetails, serviceDetails } from '@aiostreams/utils'; +import { addonDetails } from '@aiostreams/utils'; import { Settings } from '@aiostreams/utils'; -import { emojiToLanguage } from '@aiostreams/formatters'; export class Torrentio extends BaseWrapper { constructor( @@ -27,71 +25,6 @@ export class Torrentio extends BaseWrapper { indexerTimeout || Settings.DEFAULT_TORRENTIO_TIMEOUT ); } - - protected parseStream(stream: Stream): ParsedStream { - const filename = stream.title - ? stream.title.split('\n')[0] - : stream.behaviorHints?.filename?.trim(); - - const parsedFilename: ParsedNameData = parseFilename(filename || ''); - const sizeInBytes = stream.title - ? extractSizeInBytes(stream.title, 1024) - : 0; - - const debrid = this.parseServiceData(stream.name?.split('\n')?.[0] || ''); - const seedersMatch = RegExp(/👤 (\d+)/).exec(stream.title!); - const seeders = seedersMatch ? parseInt(seedersMatch[1]) : undefined; - - const indexerMatch = RegExp(/⚙️ (.+)/).exec( - stream.title?.split('\n')[1] || '' - ); - const indexer = indexerMatch ? indexerMatch[1] : undefined; - - const lastLine = stream.title?.split('\n').pop(); - if ( - lastLine && - !( - lastLine.includes('👤') && - lastLine.includes('💾') && - lastLine.includes('⚙️') - ) - ) { - // this line contains languages separated by ' / '. - const languages = lastLine.split(' / '); - // 'Multi Audio' can be converted to 'Multi' - // other ones are flag emojis and need to be converted to languages. - languages.forEach((language, index) => { - let convertedLanguage = language.trim(); - if (convertedLanguage === 'Multi Audio') { - convertedLanguage = 'Multi'; - } else { - convertedLanguage = emojiToLanguage(language) || language; - // uppercase the first letter of each word - convertedLanguage = convertedLanguage.replace(/\b\w/g, (char) => - char.toUpperCase() - ); - } - if (!parsedFilename.languages.includes(convertedLanguage)) { - parsedFilename.languages.push(convertedLanguage); - } - }); - } - - const parsedStream: ParsedStream = this.createParsedResult( - parsedFilename, - stream, - filename, - sizeInBytes, - debrid, - seeders, - undefined, - indexer, - undefined, - undefined, - this.extractInfoHash(stream.url || '') - ); - return parsedStream; - } } export async function getTorrentioStreams(