feat: add easynews and easynews+ addons

This commit is contained in:
Viren070
2025-01-10 11:43:22 +00:00
parent 7174a17c24
commit 2ddac6b071
8 changed files with 307 additions and 1 deletions
+17
View File
@@ -6,6 +6,23 @@ export function formatSize(bytes: number): string {
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
export function formatDuration(durationInMs: number): string {
const seconds = Math.floor(durationInMs / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const formattedSeconds = seconds % 60;
const formattedMinutes = minutes % 60;
if (hours > 0) {
return `${hours}h:${formattedMinutes}m:${formattedSeconds}s`;
} else if (formattedSeconds > 0) {
return `${formattedMinutes}m:${formattedSeconds}s`;
} else {
return `${formattedMinutes}m`;
}
}
export function languageToEmoji(language: string): string | undefined {
return languageEmojiMap[language.toLowerCase()];
}
+12
View File
@@ -17,3 +17,15 @@ export function extractSizeInBytes(string: string, k: number): number {
return 0;
}
}
export function extractDurationInMs(string: string): number {
const durationPattern = /(?:(\d+)h:)?(?:(\d+)m:)?(\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;
}
+1
View File
@@ -29,6 +29,7 @@ export interface ParsedStream extends ParsedNameData {
usenet?: {
age?: string;
};
duration?: number;
url?: string;
externalUrl?: string;
indexers?: string;
+72
View File
@@ -203,6 +203,78 @@ export const addonDetails: AddonDetail[] = [
},
],
},
{
name: 'Easynews',
id: 'easynews',
requiresService: true,
supportedServices: ['easynews'],
options: [
{
id: 'overrideName',
required: false,
label: 'Override Addon Name',
description:
"Override the name of the addon that shows up in the results. Leave it empty to use the default name of 'Easynews'.",
type: 'text',
},
{
id: 'overrideUrl',
required: false,
label: 'Override URL',
description:
'Override the URL used to fetch streams from the Easynews addon. By default, the URL is generated based on the username and password provided for the Easynews service. Use this option to override the URL with a custom URL.',
type: 'text',
},
{
id: 'indexerTimeout',
required: false,
label: 'Override Indexer Timeout',
description:
'The timeout for fetching streams from the Easynews addon in milliseconds. This is the time in milliseconds that the addon will wait for a response from Easynews before timing out. Leave it empty to use the recommended timeout.',
type: 'number',
constraints: {
min: Settings.MIN_TIMEOUT,
max: Settings.MAX_TIMEOUT,
},
}
]
},
{
name: 'Easynews Plus',
id: 'easynewsplus',
requiresService: true,
supportedServices: ['easynews'],
options: [
{
id: 'overrideName',
required: false,
label: 'Override Addon Name',
description:
"Override the name of the addon that shows up in the results. Leave it empty to use the default name of 'Easynews Plus'.",
type: 'text',
},
{
id: 'overrideUrl',
required: false,
label: 'Override URL',
description:
'Override the URL used to fetch streams from the Easynews Plus addon. By default, the URL is generated based on the username and password provided for the Easynews service. Use this option to override the URL with a custom URL.',
type: 'text',
},
{
id: 'indexerTimeout',
required: false,
label: 'Override Indexer Timeout',
description:
'The timeout for fetching streams from the Easynews Plus addon in milliseconds. This is the time in milliseconds that the addon will wait for a response from Easynews Plus before timing out. Leave it empty to use the recommended timeout.',
type: 'number',
constraints: {
min: Settings.MIN_TIMEOUT,
max: Settings.MAX_TIMEOUT,
},
}
]
},
{
name: 'Stremio GDrive',
id: 'gdrive',
+4
View File
@@ -18,6 +18,8 @@ export class Settings {
public static readonly MEDIAFUSION_URL = process.env.MEDIAFUSION_URL ?? 'https://mediafusion.elfhosted.com/';
public static readonly TORRENTIO_URL = process.env.TORRENTIO_URL ?? 'https://torrentio.strem.fun/';
public static readonly TORBOX_STREMIO_URL = process.env.TORBOX_STREMIO_URL ?? 'https://stremio.torbox.app/';
public static readonly EASYNEWS_URL = process.env.EASYNEWS_URL ?? 'https://ea627ddf0ee7-easynews.baby-beamup.club/';
public static readonly EASYNEWS_PLUS_URL = process.env.EASYNEWS_PLUS_URL ?? 'https://b89262c192b0-stremio-easynews-addon.baby-beamup.club/';
public static readonly MAX_ADDONS = process.env.MAX_ADDONS ? parseInt(process.env.MAX_ADDONS) : 15;
public static readonly MAX_MOVIE_SIZE = process.env.MAX_MOVIE_SIZE ? parseInt(process.env.MAX_MOVIE_SIZE) : 150000000000; // 150GB
public static readonly MAX_EPISODE_SIZE = process.env.MAX_EPISODE_SIZE ? parseInt(process.env.MAX_EPISODE_SIZE) : 15000000000; // 15GB
@@ -28,6 +30,8 @@ export class Settings {
public static readonly DEFAULT_TORBOX_TIMEOUT = process.env.DEFAULT_TORBOX_TIMEOUT ? parseInt(process.env.DEFAULT_TORBOX_TIMEOUT) : 15000;
public static readonly DEFAULT_COMET_TIMEOUT = process.env.DEFAULT_COMET_TIMEOUT ? parseInt(process.env.DEFAULT_COMET_TIMEOUT) : 15000;
public static readonly DEFAULT_MEDIAFUSION_TIMEOUT = process.env.DEFAULT_MEDIAFUSION_TIMEOUT ? parseInt(process.env.DEFAULT_MEDIAFUSION_TIMEOUT) : 15000;
public static readonly DEFAULT_EASYNEWS_TIMEMOUT = process.env.DEFAULT_EASYNEWS_TIMEMOUT ? parseInt(process.env.DEFAULT_EASYNEWS_TIMEMOUT) : 15000;
public static readonly DEFAULT_EASYNEWS_PLUS_TIMEMOUT = process.env.DEFAULT_EASYNEWS_PLUS_TIMEMOUT ? parseInt(process.env.DEFAULT_EASYNEWS_PLUS_TIMEMOUT) : 15000;
public static readonly SHOW_DIE = process.env.SHOW_DIE ? process.env.SHOW_DIE === 'true' : true;
}
+3 -1
View File
@@ -98,7 +98,8 @@ export class BaseWrapper {
provider?: ParsedStream['provider'],
seeders?: number,
usenetAge?: string,
indexer?: string
indexer?: string,
duration?: number
): ParsedStream {
return {
...parsedInfo,
@@ -118,6 +119,7 @@ export class BaseWrapper {
age: usenetAge,
},
indexers: indexer,
duration: duration,
stream: {
subtitles: stream.subtitles,
behaviorHints: {
+98
View File
@@ -0,0 +1,98 @@
import { AddonDetail, ParsedNameData, StreamRequest } from '@aiostreams/types';
import { parseFilename, extractSizeInBytes, extractDurationInMs } from '@aiostreams/parser';
import { ParsedStream, Stream, Config } from '@aiostreams/types';
import { BaseWrapper } from './base';
import { addonDetails, serviceDetails } from '@aiostreams/utils';
import { Settings } from '@aiostreams/utils';
export class Easynews extends BaseWrapper {
constructor(
configString: string | null,
overrideUrl: string | null,
indexerTimeout: number = Settings.DEFAULT_EASYNEWS_TIMEMOUT,
addonName: string = 'Easynews',
addonId: string,
) {
let url = overrideUrl
? overrideUrl
: Settings.EASYNEWS_URL +
(configString ? configString + '/' : '');
super(addonName, url, indexerTimeout, addonId);
}
protected parseStream(stream: Stream): ParsedStream {
const [filename, sizeString, durationString] = stream.description?.split('\n') || [];
const parsedFilename: ParsedNameData = parseFilename(
filename || stream.description || ''
);
const sizeInBytes = stream.description
? extractSizeInBytes(sizeString, 1024)
: undefined;
const provider = {
id: 'easynews',
cached: true,
}
const durationInMs = extractDurationInMs(durationString || '');
const parsedStream: ParsedStream = this.createParsedResult(
parsedFilename,
stream,
filename,
sizeInBytes,
provider,
undefined,
undefined,
undefined,
durationInMs
);
return parsedStream;
}
}
const getEasynewsConfigString = (username: string, password: string) => {
return `%7B%22username%22%3A%22${username}%22%2C%22password%22%3A%22${password}%22%7D`
};
export async function getEasynewsStreams(
config: Config,
easynewsOptions: {
overrideName?: string;
overrideUrl?: string;
indexerTimeout?: string;
},
streamRequest: StreamRequest,
addonId: string,
): Promise<ParsedStream[]> {
// look for the 'easynews' id in the services array and destructure the username and password
// if we cant find it, throw an error
const easynewsService = serviceDetails.find((service) => service.id === 'easynews');
if (!easynewsService) {
throw new Error('Easynews service not found');
}
// check for the presence of the username and password in teh easynewsService.credentials object
// if not found, throw an error
const credentails = config.services.find((service) => service.id === 'easynews')?.credentials;
if (!credentails || !credentails.username || !credentails.password) {
throw new Error('Easynews credentials not found');
}
const easynewsConfigString = getEasynewsConfigString(credentails.username, credentails.password);
const easynews = new Easynews(
easynewsConfigString,
easynewsOptions.overrideUrl ?? null,
easynewsOptions.indexerTimeout ? parseInt(easynewsOptions.indexerTimeout) : undefined,
easynewsOptions.overrideName,
addonId
);
const streams = await easynews.getParsedStreams(streamRequest);
return streams;
}
+100
View File
@@ -0,0 +1,100 @@
import { AddonDetail, ParsedNameData, StreamRequest } from '@aiostreams/types';
import { parseFilename, extractSizeInBytes, extractDurationInMs } from '@aiostreams/parser';
import { ParsedStream, Stream, Config } from '@aiostreams/types';
import { BaseWrapper } from './base';
import { addonDetails, serviceDetails } from '@aiostreams/utils';
import { Settings } from '@aiostreams/utils';
export class EasynewsPlus extends BaseWrapper {
constructor(
configString: string | null,
overrideUrl: string | null,
indexerTimeout: number = Settings.DEFAULT_EASYNEWS_PLUS_TIMEMOUT,
addonName: string = 'Easynews Plus',
addonId: string,
) {
let url = overrideUrl
? overrideUrl
: Settings.EASYNEWS_PLUS_URL +
(configString ? configString + '/' : '');
super(addonName, url, indexerTimeout, addonId);
}
protected parseStream(stream: Stream): ParsedStream {
const [filename, durationString, sizeString] = stream.description?.split('\n') || [];
const parsedFilename: ParsedNameData = parseFilename(
filename || stream.description || ''
);
const sizeInBytes = stream.behaviorHints?.videoSize
? stream.behaviorHints.videoSize
: sizeString
? extractSizeInBytes(sizeString, 1024)
: undefined;
const provider = {
id: 'easynews',
cached: true,
}
const durationInMs = extractDurationInMs(durationString || '');
const parsedStream: ParsedStream = this.createParsedResult(
parsedFilename,
stream,
filename,
sizeInBytes,
provider,
undefined,
undefined,
undefined,
durationInMs
);
return parsedStream;
}
}
const getEasynewsPlusConfigString = (username: string, password: string) => {
return `%7B%22username%22%3A%22${username}%22%2C%22password%22%3A%22${password}%22%2C%22sort1%22%3A%22Size%22%2C%22sort1Direction%22%3A%22Descending%22%2C%22sort2%22%3A%22Relevance%22%2C%22sort2Direction%22%3A%22Descending%22%2C%22sort3%22%3A%22Date%20%26%20Time%22%2C%22sort3Direction%22%3A%22Descending%22%7D`
};
export async function getEasynewsPlusStreams(
config: Config,
easynewsPlusOptions: {
overrideName?: string;
overrideUrl?: string;
indexerTimeout?: string;
},
streamRequest: StreamRequest,
addonId: string,
): Promise<ParsedStream[]> {
// look for the 'easynews' id in the services array and destructure the username and password
// if we cant find it, throw an error
const easynewsService = serviceDetails.find((service) => service.id === 'easynews');
if (!easynewsService) {
throw new Error('Easynews service not found');
}
// check for the presence of the username and password in teh easynewsService.credentials object
// if not found, throw an error
const credentails = config.services.find((service) => service.id === 'easynews')?.credentials;
if (!credentails || !credentails.username || !credentails.password) {
throw new Error('Easynews credentials not found');
}
const easynewsPlusConfigString = getEasynewsPlusConfigString(credentails.username, credentails.password);
const easynews = new EasynewsPlus(
easynewsPlusConfigString,
easynewsPlusOptions.overrideUrl ?? null,
easynewsPlusOptions.indexerTimeout ? parseInt(easynewsPlusOptions.indexerTimeout) : undefined,
easynewsPlusOptions.overrideName,
addonId
);
const streams = await easynews.getParsedStreams(streamRequest);
return streams;
}