refactor: improve base parser

- probably breaks a few things
This commit is contained in:
Viren070
2025-01-26 19:05:37 +00:00
parent 266167331f
commit f86ec38b23
16 changed files with 420 additions and 626 deletions
+54
View File
@@ -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<string, string> = {
latino: '💃🏻',
Latino: '🇲🇽',
};
const codeLanguageMap: Record<string, string> = {
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',
};
-1
View File
@@ -1,3 +1,2 @@
export * from './parser';
export * from './regex';
export * from './utils';
+103 -97
View File
@@ -1,111 +1,117 @@
const createRegex = (pattern: string): RegExp =>
new RegExp(`(?<![^\\s\\[(_\\-.,])(${pattern})(?=[\\s\\)\\]_.\\-,]|$)`, 'i');
export const PARSE_REGEX = {
resolutions: {
'2160p': /(?<![^ [(_\-.])(4k|2160p|uhd)(?=[ \)\]_.-]|$)/i,
'1080p': /(?<![^ [(_\-.])(1080p|fhd)(?=[ \)\]_.-]|$)/i,
'720p': /(?<![^ [(_\-.])(720p|hd)(?=[ \)\]_.-]|$)/i,
'480p': /(?<![^ [(_\-.])(480p|sd)(?=[ \)\]_.-]|$)/i,
'2160p': createRegex('4k|2160p|u(ltra)?hd'),
'1080p': createRegex('1080p|f(ull)?hd'),
'720p': createRegex('720p|hd'),
'480p': createRegex('480p|sd'),
},
qualities: {
'BluRay REMUX':
/(?<![^ [(_\-.])((blu[ .\-_]?ray|bd|br|b|uhd)[ .\-_]?remux)(?=[ \)\]_.-]|$)/i,
BluRay:
/(?<![^ [(_\-.])(blu[ .\-_]?ray|((bd|br|b)[ .\-_]?(rip|r)?))(?![ .\-_]?remux)(?=[ \)\]_.-]|$)/i,
'WEB-DL':
/(?<![^ [(_\-.])(web[ .\-_]?(dl)?)(?![ .\-_]?DLRip)(?=[ \)\]_.-]|$)/i,
WEBRip: /(?<![^ [(_\-.])(web[ .\-_]?rip)(?=[ \)\]_.-]|$)/i,
HDRip:
/(?<![^ [(_\-.])(hd[ .\-_]?rip|web[ .\-_]?dl[ .\-_]?rip)(?=[ \)\]_.-]|$)/i,
'HC HD-Rip': /(?<![^ [(_\-.])(hc|hd[ .\-_]?rip)(?=[ \)\]_.-]|$)/i,
DVDRip: /(?<![^ [(_\-.])(dvd[ .\-_]?(rip|mux|r|full|5|9))(?=[ \)\]_.-]|$)/i,
HDTV: /(?<![^ [(_\-.])((hd|pd)tv|tv[ .\-_]?rip|hdtv[ .\-_]?rip|dsr(ip)?|sat[ .\-_]?rip)(?=[ \)\]_.-]|$)/i,
CAM: /(?<![^ [(_\-.])(cam|hdcam|cam[ .\-_]?rip)(?=[ \)\]_.-]|$)/i,
TS: /(?<![^ [(_\-.])(telesync|ts|hd[ .\-_]?ts|pdvd|predvd(rip)?)(?=[ \)\]_.-]|$)/i,
TC: /(?<![^ [(_\-.])(telecine|tc|hd[ .\-_]?tc)(?=[ \)\]_.-]|$)/i,
SCR: /(?<![^ [(_\-.])(((dvd|bd|web)?[ .\-_]?)?(scr(eener)?))(?=[ \)\]_.-]|$)/i,
'BluRay REMUX': createRegex('(blu[ .\\-_]?ray|bd|br|b|uhd)[ .\\-_]?remux'),
BluRay: createRegex(
'blu[ .\\-_]?ray|((bd|br|b)[ .\\-_]?(rip|r)?)(?![ .\\-_]?remux)'
),
'WEB-DL': createRegex('web[ .\\-_]?(dl)?(?![ .\\-_]?(DLRip|cam))'),
WEBRip: createRegex('web[ .\\-_]?rip'),
HDRip: createRegex('hd[ .\\-_]?rip|web[ .\\-_]?dl[ .\\-_]?rip'),
'HC HD-Rip': createRegex('hc|hd[ .\\-_]?rip'),
DVDRip: createRegex('dvd[ .\\-_]?(rip|mux|r|full|5|9)'),
HDTV: createRegex(
'(hd|pd)tv|tv[ .\\-_]?rip|hdtv[ .\\-_]?rip|dsr(ip)?|sat[ .\\-_]?rip'
),
CAM: createRegex('cam|hdcam|cam[ .\\-_]?rip'),
TS: createRegex('telesync|ts|hd[ .\\-_]?ts|pdvd|predvd(rip)?'),
TC: createRegex('telecine|tc|hd[ .\\-_]?tc'),
SCR: createRegex('((dvd|bd|web)?[ .\\-_]?)?(scr(eener)?)'),
},
visualTags: {
'HDR10+':
/(?<![^ [(_\-.])(hdr[ .\-_]?10[ .\-_]?(plus|[+]))(?=[ \)\]_.-]|$)/i,
HDR10:
/(?<![^ [(_\-.])(hdr[ .\-_]?10)(?![ .\-_]?(?:\+|plus))(?=[ \)\]_.-]|$)/i,
HDR: /(?<![^ [(_\-.])(hdr)(?![ .\-_]?10)(?![ .\-_]?(?:\+|plus))(?=[ \)\]_.-]|$)/i,
DV: /(?<![^ [(_\-.])(dolby[ .\-_]?vision(?:[ .\-_]?atmos)?|dv)(?=[ \)\]_.-]|$)/i,
'3D': /(?<![^ [(_\-.])((3|three)[ .\-_]?(d(imension)?(al)?))(?=[ \)\]_.-]|$)/i,
IMAX: /(?<![^ [(_\-.])(imax)(?=[ \)\]_.-]|$)/i,
AI: /(?<![^ [(_\-.])(ai[ .\-_]?(upscale|enhanced|remaster)?)(?=[ \)\]_.-]|$)/i,
'HDR10+': createRegex('hdr[ .\\-_]?10[ .\\-_]?(plus|[+])'),
HDR10: createRegex('hdr[ .\\-_]?10(?![ .\\-_]?(?:\\+|plus))'),
HDR: createRegex('hdr(?![ .\\-_]?10)(?![ .\\-_]?(?:\\+|plus))'),
DV: createRegex('dolby[ .\\-_]?vision(?:[ .\\-_]?atmos)?|dv'),
'3D': createRegex('(3|three)[ .\\-_]?(d(imension)?(al)?)'),
IMAX: createRegex('imax'),
AI: createRegex('ai[ .\\-_]?(upscale|enhanced|remaster)?'),
},
audioTags: {
Atmos: /(?<![^ [(_\-.])(atmos)(?=[ \)\]_.-]|$)/i,
'DD+':
/(?<![^ [(_\-.])((?:d(olby)?[ .\-_]?d(igital)?[ .\-_]?(p(lus)?|\+))(?:[ .\-_]?(5[ .\-_]?1|7[ .\-_]?1))?)(?=[ \)\]_.-]|$)/i,
DD: /(?<![^ [(_\-.])((?:d(olby)?[ .\-_]?d(igital)?)(?:[ .\-_]?(5[ .\-_]?1|7[ .\-_]?1))?)(?=[ \)\]_.-]|$)/i,
'DTS-HD MA': /(?<![^ [(_\-.])(dts[ .\-_]?hd[ .\-_]?ma)(?=[ \)\]_.-]|$)/i,
'DTS-HD': /(?<![^ [(_\-.])(dts[ .\-_]?hd)(?![ .\-_]?ma)(?=[ \)\]_.-]|$)/i,
DTS: /(?<![^ [(_\-.])(dts(?![ .\-_]?hd[ .\-_]?ma|[ .\-_]?hd))(?=[ \)\]_.-]|$)/i,
TrueHD: /(?<![^ [(_\-.])(true[ .\-_]?hd)(?=[ \)\]_.-]|$)/i,
5.1: /(?<![^ [(_\-.])((?:d(olby)?[ .\-_]?d(igital)?[ .\-_]?(p(lus)?|\+)?)?[ .\-_]?5[ .\-_]?1)(?=[ \)\]_.-]|$)/i,
7.1: /(?<![^ [(_\-.])((?:d(olby)?[ .\-_]?d(igital)?[ .\-_]?(p(lus)?|\+)?)?[ .\-_]?7[ .\-_]?1)(?=[ \)\]_.-]|$)/i,
AC3: /(?<![^ [(_\-.])(ac[ .\-_]?3)(?=[ \)\]_.-]|$)/i,
AAC: /(?<![^ [(_\-.])(aac)(?=[ \)\]_.-]|$)/i,
Atmos: createRegex('atmos'),
'DD+': createRegex(
'd(olby)?[ .\\-_]?d(igital)?[ .\\-_]?(p(lus)?|\\+)(?:[ .\\-_]?(5[ .\\-_]?1|7[ .\\-_]?1))?'
),
DD: createRegex(
'd(olby)?[ .\\-_]?d(igital)?(?:[ .\\-_]?(5[ .\\-_]?1|7[ .\\-_]?1))?'
),
'DTS-HD MA': createRegex('dts[ .\\-_]?hd[ .\\-_]?ma'),
'DTS-HD': createRegex('dts[ .\\-_]?hd(?![ .\\-_]?ma)'),
DTS: createRegex('dts(?![ .\\-_]?hd[ .\\-_]?ma|[ .\\-_]?hd)'),
TrueHD: createRegex('true[ .\\-_]?hd'),
5.1: createRegex(
'd(olby)?[ .\\-_]?d(igital)?[ .\\-_]?(p(lus)?|\\+)?[ .\\-_]?5[ .\\-_]?1'
),
7.1: createRegex(
'd(olby)?[ .\\-_]?d(igital)?[ .\\-_]?(p(lus)?|\\+)?[ .\\-_]?7[ .\\-_]?1'
),
AC3: createRegex('ac[ .\\-_]?3'),
AAC: createRegex('aac'),
},
encodes: {
HEVC: /(?<![^ [(_\-.])(hevc|x265|h265|h\.265)(?=[ \)\]_.-]|$)/i,
AVC: /(?<![^ [(_\-.])(avc|x264|h264|h\.264)(?=[ \)\]_.-]|$)/i,
AV1: /(?<![^ [(_\-.])(av1)(?=[ \)\]_.-]|$)/i,
'H-OU':
/(?<![^ [(_\-.])(h?(alf)?[ .\-_]?(ou|over[ .\-_]?under))(?=[ \)\]_.-]|$)/i,
'H-SBS':
/(?<![^ [(_\-.])(h?(alf)?[ .\-_]?(sbs|side[ .\-_]?by[ .\-_]?side))(?=[ \)\]_.-]|$)/i,
HEVC: createRegex('hevc|x265|h265|h\\.265'),
AVC: createRegex('avc|x264|h264|h\\.264'),
AV1: createRegex('av1'),
'H-OU': createRegex('h?(alf)?[ .\\-_]?(ou|over[ .\\-_]?under)'),
'H-SBS': createRegex('h?(alf)?[ .\\-_]?(sbs|side[ .\\-_]?by[ .\\-_]?side)'),
},
languages: {
Multi: /(?<![^ [(_\-.])(multi|multi[ .\-_]?audio)(?=[ \)\]_.-]|$)/i,
'Dual Audio': /(?<![^ [(_\-.])(dual[ .\-_]?audio)(?=[ \)\]_.-]|$)/i,
English: /(?<![^ [(_\-.])(english|eng)(?=[ \)\]_.-]|$)/i,
Japanese: /(?<![^ [(_\-.])(japanese|jap)(?=[ \)\]_.-]|$)/i,
Chinese: /(?<![^ [(_\-.])(chinese|chi)(?=[ \)\]_.-]|$)/i,
Russian: /(?<![^ [(_\-.])(russian|rus)(?=[ \)\]_.-]|$)/i,
Arabic: /(?<![^ [(_\-.])(arabic|ara)(?=[ \)\]_.-]|$)/i,
Portuguese: /(?<![^ [(_\-.])(portuguese|por)(?=[ \)\]_.-]|$)/i,
Spanish: /(?<![^ [(_\-.])(spanish|spa|esp)(?=[ \)\]_.-]|$)/i,
French: /(?<![^ [(_\-.])(french|fra)(?=[ \)\]_.-]|$)/i,
German: /(?<![^ [(_\-.])(german|ger)(?=[ \)\]_.-]|$)/i,
Italian: /(?<![^ [(_\-.])(italian|ita)(?=[ \)\]_.-]|$)/i,
Korean: /(?<![^ [(_\-.])(korean|kor)(?=[ \)\]_.-]|$)/i,
Hindi: /(?<![^ [(_\-.])(hindi|hin)(?=[ \)\]_.-]|$)/i,
Bengali: /(?<![^ [(_\-.])(bengali|ben)(?=[ \)\]_.-]|$)/i,
Punjabi: /(?<![^ [(_\-.])(punjabi|pan)(?=[ \)\]_.-]|$)/i,
Marathi: /(?<![^ [(_\-.])(marathi|mar)(?=[ \)\]_.-]|$)/i,
Gujarati: /(?<![^ [(_\-.])(gujarati|guj)(?=[ \)\]_.-]|$)/i,
Tamil: /(?<![^ [(_\-.])(tamil|tam)(?=[ \)\]_.-]|$)/i,
Telugu: /(?<![^ [(_\-.])(telugu|tel)(?=[ \)\]_.-]|$)/i,
Kannada: /(?<![^ [(_\-.])(kannada|kan)(?=[ \)\]_.-]|$)/i,
Malayalam: /(?<![^ [(_\-.])(malayalam|mal)(?=[ \)\]_.-]|$)/i,
Thai: /(?<![^ [(_\-.])(thai|tha)(?=[ \)\]_.-]|$)/i,
Vietnamese: /(?<![^ [(_\-.])(vietnamese|vie)(?=[ \)\]_.-]|$)/i,
Indonesian: /(?<![^ [(_\-.])(indonesian|ind)(?=[ \)\]_.-]|$)/i,
Turkish: /(?<![^ [(_\-.])(turkish|tur)(?=[ \)\]_.-]|$)/i,
Hebrew: /(?<![^ [(_\-.])(hebrew|heb)(?=[ \)\]_.-]|$)/i,
Persian: /(?<![^ [(_\-.])(persian|per)(?=[ \)\]_.-]|$)/i,
Ukrainian: /(?<![^ [(_\-.])(ukrainian|ukr)(?=[ \)\]_.-]|$)/i,
Greek: /(?<![^ [(_\-.])(greek|ell)(?=[ \)\]_.-]|$)/i,
Lithuanian: /(?<![^ [(_\-.])(lithuanian|lit)(?=[ \)\]_.-]|$)/i,
Latvian: /(?<![^ [(_\-.])(latvian|lav)(?=[ \)\]_.-]|$)/i,
Estonian: /(?<![^ [(_\-.])(estonian|est)(?=[ \)\]_.-]|$)/i,
Polish: /(?<![^ [(_\-.])(polish|pol)(?=[ \)\]_.-]|$)/i,
Czech: /(?<![^ [(_\-.])(czech|cze)(?=[ \)\]_.-]|$)/i,
Slovak: /(?<![^ [(_\-.])(slovak|slo)(?=[ \)\]_.-]|$)/i,
Hungarian: /(?<![^ [(_\-.])(hungarian|hun)(?=[ \)\]_.-]|$)/i,
Romanian: /(?<![^ [(_\-.])(romanian|rum)(?=[ \)\]_.-]|$)/i,
Bulgarian: /(?<![^ [(_\-.])(bulgarian|bul)(?=[ \)\]_.-]|$)/i,
Serbian: /(?<![^ [(_\-.])(serbian|srp)(?=[ \)\]_.-]|$)/i,
Croatian: /(?<![^ [(_\-.])(croatian|hrv)(?=[ \)\]_.-]|$)/i,
Slovenian: /(?<![^ [(_\-.])(slovenian|slv)(?=[ \)\]_.-]|$)/i,
Dutch: /(?<![^ [(_\-.])(dutch|dut)(?=[ \)\]_.-]|$)/i,
Danish: /(?<![^ [(_\-.])(danish|dan)(?=[ \)\]_.-]|$)/i,
Finnish: /(?<![^ [(_\-.])(finnish|fin)(?=[ \)\]_.-]|$)/i,
Swedish: /(?<![^ [(_\-.])(swedish|swe)(?=[ \)\]_.-]|$)/i,
Norwegian: /(?<![^ [(_\-.])(norwegian|nor)(?=[ \)\]_.-]|$)/i,
Malay: /(?<![^ [(_\-.])(malay)(?=[ \)\]_.-]|$)/i,
Latino: /(?<![^ [(_\-.])(latino|lat)(?=[ \)\]_.-]|$)/i,
Multi: createRegex('multi|multi[ .\\-_]?audio'),
'Dual Audio': createRegex('dual[ .\\-_]?audio'),
English: createRegex('english|eng'),
Japanese: createRegex('japanese|jap'),
Chinese: createRegex('chinese|chi'),
Russian: createRegex('russian|rus'),
Arabic: createRegex('arabic|ara'),
Portuguese: createRegex('portuguese|por'),
Spanish: createRegex('spanish|spa|esp'),
French: createRegex('french|fra'),
German: createRegex('german|ger'),
Italian: createRegex('italian|ita'),
Korean: createRegex('korean|kor'),
Hindi: createRegex('hindi|hin'),
Bengali: createRegex('bengali|ben'),
Punjabi: createRegex('punjabi|pan'),
Marathi: createRegex('marathi|mar'),
Gujarati: createRegex('gujarati|guj'),
Tamil: createRegex('tamil|tam'),
Telugu: createRegex('telugu|tel'),
Kannada: createRegex('kannada|kan'),
Malayalam: createRegex('malayalam|mal'),
Thai: createRegex('thai|tha'),
Vietnamese: createRegex('vietnamese|vie'),
Indonesian: createRegex('indonesian|ind'),
Turkish: createRegex('turkish|tur'),
Hebrew: createRegex('hebrew|heb'),
Persian: createRegex('persian|per'),
Ukrainian: createRegex('ukrainian|ukr'),
Greek: createRegex('greek|ell'),
Lithuanian: createRegex('lithuanian|lit'),
Latvian: createRegex('latvian|lav'),
Estonian: createRegex('estonian|est'),
Polish: createRegex('polish|pol'),
Czech: createRegex('czech|cze'),
Slovak: createRegex('slovak|slo'),
Hungarian: createRegex('hungarian|hun'),
Romanian: createRegex('romanian|rum'),
Bulgarian: createRegex('bulgarian|bul'),
Serbian: createRegex('serbian|srp'),
Croatian: createRegex('croatian|hrv'),
Slovenian: createRegex('slovenian|slv'),
Dutch: createRegex('dutch|dut'),
Danish: createRegex('danish|dan'),
Finnish: createRegex('finnish|fin'),
Swedish: createRegex('swedish|swe'),
Norwegian: createRegex('norwegian|nor'),
Malay: createRegex('malay'),
Latino: createRegex('latino|lat'),
},
};
-31
View File
@@ -1,31 +0,0 @@
export function extractSizeInBytes(string: string, k: number): number {
const sizePattern = /(\d+(\.\d+)?)\s?(KB|MB|GB)/;
const match = string.match(sizePattern);
if (!match) return 0;
const value = parseFloat(match[1]);
const unit = match[3];
switch (unit) {
case 'GB':
return value * k * k * k;
case 'MB':
return value * k * k;
case 'KB':
return value * k;
default:
return 0;
}
}
export function extractDurationInMs(string: string): number {
const durationPattern = /(?:(\d+)h[:\s])?(?:(\d+)m[:\s])?(\d+)s/;
const match = string.match(durationPattern);
if (!match) return 0;
const hours = parseInt(match[1] || '0');
const minutes = parseInt(match[2] || '0');
const seconds = parseInt(match[3]);
return (hours * 60 * 60 + minutes * 60 + seconds) * 1000;
}
+212 -75
View File
@@ -5,7 +5,7 @@ import {
ParsedNameData,
Config,
} from '@aiostreams/types';
import { extractSizeInBytes, parseFilename } from '@aiostreams/parser';
import { parseFilename } from '@aiostreams/parser';
import {
getMediaFlowConfig,
getMediaFlowPublicIp,
@@ -13,6 +13,7 @@ import {
Settings,
} from '@aiostreams/utils';
import { fetch as uFetch, ProxyAgent } from 'undici';
import { emojiToLanguage, codeToLanguage } from '@aiostreams/formatters';
export class BaseWrapper {
private readonly streamPath: string = 'stream/{type}/{id}.json';
@@ -153,10 +154,6 @@ export class BaseWrapper {
}
}
protected extractInfoHash(url: string): string | undefined {
return url.match(/(?<=[-/[(;&])[a-fA-F0-9]{40}(?=[-\]\)/;&])/)?.[0];
}
protected createParsedResult(
parsedInfo: ParsedNameData,
stream: Stream,
@@ -209,6 +206,125 @@ export class BaseWrapper {
},
};
}
protected parseStream(stream: { [key: string]: any }): ParsedStream {
// attempt to look for filename in behaviorHints.filename
let filename =
stream?.behaviorHints?.filename || stream.torrentTitle || stream.filename;
// if filename behaviorHint is not present, attempt to look for a filename in the stream description or title
let description = stream.description || stream.title;
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(
/(?<![^ [_(\-.]])(?:s(?:eason)?[ .\-_]?(\d+)[ .\-_]?(?:e(?:pisode)?[ .\-_]?(\d+))?|(\d+)[xX](\d+))(?![^ \])_.-])/
) || line.match(/(?<![^ [_(\-.])(\d{4})(?=[ \])_.-]|$)/i)
) || lines[0];
console.log(
`|DBG| wrappers > 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(
/(?<![^ [_(\-.]])(?:s(?:eason)?[ .\-_]?(\d+)[ .\-_]?(?:e(?:pisode)?[ .\-_]?(\d+))?|(\d+)[xX](\d+))(?![^ \])_.-])/
) || filename.match(/(?<![^ [_(\-.])(\d{4})(?=[ \])_.-]|$)/i)
)
) {
stringToParse = description.replace(/\n/g, ' ').trim();
}
let parsedInfo: ParsedNameData = parseFilename(stringToParse);
// 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 ||
stream.sizeBytes ||
stream.torrentSize ||
(description && this.extractSizeInBytes(description, 1024)) ||
(stream.name && this.extractSizeInBytes(stream.name, 1024)) ||
undefined;
if (typeof size === 'string') {
size = parseInt(size);
}
// look for seeders
let seeders: string | undefined;
if (description) {
seeders = this.extractStringBetweenEmojis(['👥', '👤'], description);
}
// look for indexer
let indexer: string | undefined;
if (description) {
indexer = this.extractStringBetweenEmojis(
['🌐', '⚙️', '🔗', '🔎', '☁️'],
description
);
}
[
...this.extractCountryFlags(description),
...this.extractCountryCodes(description),
]
.map(
(codeOrFlag) =>
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(
/(?<![^ [_(\-.]])(?:s(?:eason)?[ .\-_]?(\d+)[ .\-_]?(?:e(?:pisode)?[ .\-_]?(\d+))?|(\d+)[xX](\d+))(?![^ \])_.-])/
) || line.match(/(?<![^ [_(\-.])(\d{4})(?=[ \])_.-]|$)/i)
) || lines[0];
console.log(
`|DBG| wrappers > 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];
}
}
+2 -46
View File
@@ -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) => {
+13 -62
View File
@@ -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;
}
}
+3 -3
View File
@@ -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(
+5 -9
View File
@@ -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,
+5 -9
View File
@@ -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,
+3 -67
View File
@@ -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 = (
+7 -64
View File
@@ -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;
}
}
+5 -37
View File
@@ -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;
}
}
+2 -52
View File
@@ -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(
+3 -3
View File
@@ -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',
+3 -70
View File
@@ -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(