mirror of
https://github.com/Viren070/AIOStreams.git
synced 2025-12-01 23:14:04 +01:00
feat: changes
This commit removes the 'Move Addon Name to description' setting in favour of the custom formatter Adds the following fields for use in a custom formatter: config.addonName (returns the value of `ADDON_NAME`) config.showDie (returns the value of `SHOW_DIE`) stream.title (string) stream.year (string) stream.season (number) stream.seasons (number[]) stream.episode (number) stream.proxied (boolean) you now control 100% of the stream output, including the game die emoji and the detective emoji for proxied streams. Adds a proxied switch in the preview. Adjusted default addon name to Torrentio to account for config.addonName refactored the way streams were generated.
This commit is contained in:
Generated
+22
-1
@@ -7720,6 +7720,15 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/moment": {
|
||||
"version": "2.30.1",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
||||
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
@@ -8180,6 +8189,17 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-torrent-title": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "git+ssh://git@github.com/TheBeastLT/parse-torrent-title.git#1169487e316a4eed898dcfd900957f89a253604c",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"moment": "^2.24.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/parse5": {
|
||||
"version": "7.2.1",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
|
||||
@@ -11979,7 +11999,8 @@
|
||||
"name": "@aiostreams/parser",
|
||||
"version": "1.17.0",
|
||||
"dependencies": {
|
||||
"@aiostreams/types": "^1.0.0"
|
||||
"@aiostreams/types": "^1.0.0",
|
||||
"parse-torrent-title": "github:TheBeastLT/parse-torrent-title"
|
||||
}
|
||||
},
|
||||
"packages/types": {
|
||||
|
||||
+43
-67
@@ -609,9 +609,46 @@ export class AIOStreams {
|
||||
private async createStreamObjects(
|
||||
parsedStreams: ParsedStream[]
|
||||
): Promise<Stream[]> {
|
||||
// Step 1: Format all stream metadata
|
||||
let streamObjects: Stream[] = await Promise.all(
|
||||
parsedStreams.map(async (parsedStream) => {
|
||||
// Identify streams that require proxying
|
||||
const streamsToProxy = parsedStreams
|
||||
.map((stream, index) => ({ stream, index }))
|
||||
.filter(({ stream }) => stream.url && this.shouldProxyStream(stream));
|
||||
|
||||
const proxiedUrls = streamsToProxy.length
|
||||
? await generateMediaFlowStreams(
|
||||
getMediaFlowConfig(this.config),
|
||||
streamsToProxy.map(({ stream }) => ({
|
||||
url: stream.url!,
|
||||
filename: stream.filename,
|
||||
headers: stream.stream?.behaviorHints?.proxyHeaders,
|
||||
}))
|
||||
)
|
||||
: null;
|
||||
|
||||
const removeIndexes = new Set<number>();
|
||||
|
||||
// Apply proxied URLs and mark as proxied
|
||||
streamsToProxy.forEach(({ stream, index }, i) => {
|
||||
const proxiedUrl = proxiedUrls?.[i];
|
||||
if (proxiedUrl) {
|
||||
stream.url = proxiedUrl;
|
||||
stream.proxied = true;
|
||||
} else {
|
||||
removeIndexes.add(index);
|
||||
}
|
||||
});
|
||||
|
||||
// Remove streams that failed to proxy
|
||||
console.error(
|
||||
`Failed to proxy ${removeIndexes.size} streams, removing them from the final list`
|
||||
);
|
||||
parsedStreams = parsedStreams.filter(
|
||||
(_, index) => !removeIndexes.has(index)
|
||||
);
|
||||
|
||||
// Build final Stream objects
|
||||
const streamObjects: Stream[] = await Promise.all(
|
||||
parsedStreams.map((parsedStream) => {
|
||||
const { name, description } = this.getFormattedText(parsedStream);
|
||||
|
||||
const combinedTags = [
|
||||
@@ -628,14 +665,8 @@ export class AIOStreams {
|
||||
externalUrl: parsedStream.externalUrl,
|
||||
infoHash: parsedStream.torrent?.infoHash,
|
||||
fileIdx: parsedStream.torrent?.fileIdx,
|
||||
name: this.config.addonNameInDescription
|
||||
? Settings.ADDON_NAME
|
||||
: Settings.SHOW_DIE
|
||||
? `🎲 ${name}`
|
||||
: name,
|
||||
description: this.config.addonNameInDescription
|
||||
? `🎲 ${name.split('\n').join(' ')}\n${description}`
|
||||
: description,
|
||||
name,
|
||||
description,
|
||||
subtitles: parsedStream.stream?.subtitles,
|
||||
sources: parsedStream.torrent?.sources,
|
||||
behaviorHints: {
|
||||
@@ -643,7 +674,7 @@ export class AIOStreams {
|
||||
? Math.floor(parsedStream.size)
|
||||
: undefined,
|
||||
filename: parsedStream.filename,
|
||||
bingeGroup: `${Settings.ADDON_ID}|${parsedStream.addon.name}|${combinedTags.join('|')}`,
|
||||
bingeGroup: `${parsedStream.proxied ? 'mfp.' : ''}${Settings.ADDON_ID}|${parsedStream.addon.name}|${combinedTags.join('|')}`,
|
||||
proxyHeaders: parsedStream.stream?.behaviorHints?.proxyHeaders,
|
||||
notWebReady: parsedStream.stream?.behaviorHints?.notWebReady,
|
||||
},
|
||||
@@ -651,61 +682,6 @@ export class AIOStreams {
|
||||
})
|
||||
);
|
||||
|
||||
// Determine which streams need proxying and remember their indexes
|
||||
const streamsToProxy = parsedStreams
|
||||
.map((stream, index) => ({ stream, index }))
|
||||
.filter(({ stream }) => stream.url !== undefined) // cannot proxy a stream without a URL
|
||||
.filter(({ stream }) => this.shouldProxyStream(stream));
|
||||
|
||||
// Generate proxied URLs
|
||||
const proxiedUrls =
|
||||
streamsToProxy.length > 0
|
||||
? await generateMediaFlowStreams(
|
||||
getMediaFlowConfig(this.config),
|
||||
streamsToProxy.map(({ stream }) => ({
|
||||
url: stream.url!,
|
||||
filename: stream.filename,
|
||||
headers: stream.stream?.behaviorHints?.proxyHeaders,
|
||||
}))
|
||||
)
|
||||
: null;
|
||||
|
||||
if (
|
||||
streamsToProxy &&
|
||||
proxiedUrls &&
|
||||
proxiedUrls.length !== streamsToProxy.length
|
||||
) {
|
||||
logger.error(
|
||||
`Proxied URLs length (${proxiedUrls.length}) does not match streamsToProxy length (${streamsToProxy.length})`
|
||||
);
|
||||
return streamObjects;
|
||||
} else if (streamsToProxy.length > 0 && !proxiedUrls) {
|
||||
logger.error(
|
||||
`Proxied URLs is null, but streamsToProxy length is ${streamsToProxy.length}, filtering out streams that needed proxying`
|
||||
);
|
||||
streamObjects = streamObjects.filter(
|
||||
(_, index) => !streamsToProxy.some((s) => s.index === index)
|
||||
);
|
||||
} else if (proxiedUrls) {
|
||||
// inject proxied URLs back into their original positions
|
||||
streamsToProxy.forEach(({ index }, i) => {
|
||||
streamObjects[index].url = proxiedUrls[i] || streamObjects[index].url;
|
||||
|
||||
streamObjects[index].name = this.config.addonNameInDescription
|
||||
? Settings.ADDON_NAME
|
||||
: `🕵️ ${streamObjects[index].name}`;
|
||||
|
||||
streamObjects[index].description = this.config.addonNameInDescription
|
||||
? `🕵️ ${streamObjects[index].name.split('\n').join(' ')}\n${streamObjects[index].description}`
|
||||
: streamObjects[index].description;
|
||||
|
||||
streamObjects[index].behaviorHints = {
|
||||
...streamObjects[index].behaviorHints,
|
||||
bingeGroup: `mfp.${streamObjects[index].behaviorHints?.bingeGroup}`,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return streamObjects;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Config, CustomFormatter, ParsedStream } from '@aiostreams/types';
|
||||
import { serviceDetails } from '@aiostreams/utils';
|
||||
import { serviceDetails, Settings } from '@aiostreams/utils';
|
||||
import { formatDuration, formatSize, languageToEmoji } from './utils';
|
||||
|
||||
/**
|
||||
@@ -65,6 +65,10 @@ export function customFormat(
|
||||
}
|
||||
|
||||
export type ParseValue = {
|
||||
config?: {
|
||||
addonName: string | null;
|
||||
showDie: boolean | null;
|
||||
};
|
||||
stream?: {
|
||||
name: string | null;
|
||||
size: number | null;
|
||||
@@ -78,11 +82,17 @@ export type ParseValue = {
|
||||
releaseGroup: string | null;
|
||||
encode: string | null;
|
||||
indexer: string | null;
|
||||
year: string | null;
|
||||
title: string | null;
|
||||
season: number | null;
|
||||
seasons: number[] | null;
|
||||
episode: number | null;
|
||||
seeders: number | null;
|
||||
age: string | null;
|
||||
duration: number | null;
|
||||
infoHash: string | null;
|
||||
message: string | null;
|
||||
proxied: boolean | null;
|
||||
};
|
||||
provider?: {
|
||||
id: string | null;
|
||||
@@ -102,6 +112,10 @@ export type ParseValue = {
|
||||
|
||||
const convertStreamToParseValue = (stream: ParsedStream): ParseValue => {
|
||||
return {
|
||||
config: {
|
||||
addonName: Settings.ADDON_NAME,
|
||||
showDie: Settings.SHOW_DIE,
|
||||
},
|
||||
stream: {
|
||||
name: stream.filename || null,
|
||||
size: stream.size || null,
|
||||
@@ -121,10 +135,16 @@ const convertStreamToParseValue = (stream: ParsedStream): ParseValue => {
|
||||
encode: stream.encode === 'Unknown' ? null : stream.encode,
|
||||
indexer: stream.indexers || null,
|
||||
seeders: stream.torrent?.seeders || null,
|
||||
year: stream.year || null,
|
||||
title: stream.title || null,
|
||||
season: stream.season || null,
|
||||
seasons: stream.seasons || null,
|
||||
episode: stream.episode || null,
|
||||
age: stream.usenet?.age || null,
|
||||
duration: stream.duration || null,
|
||||
infoHash: stream.torrent?.infoHash || null,
|
||||
message: stream.message || null,
|
||||
proxied: stream.proxied !== undefined ? stream.proxied : null,
|
||||
},
|
||||
addon: {
|
||||
id: stream.addon.id,
|
||||
@@ -157,6 +177,7 @@ function parseString(str: string, value: ParseValue) {
|
||||
stream: value.stream,
|
||||
provider: value.provider,
|
||||
addon: value.addon,
|
||||
config: value.config,
|
||||
};
|
||||
|
||||
value.debug = {
|
||||
@@ -165,7 +186,7 @@ function parseString(str: string, value: ParseValue) {
|
||||
};
|
||||
|
||||
const re =
|
||||
/\{(?<type>stream|provider|debug|addon)\.(?<prop>\w+)(::(?<mod>(\w+(\([^)]*\))?|<|<=|=|>=|>|\^|\$|~|\/)+))?((::(?<mod_tzlocale>\S+?))|(?<mod_check>\[(?<mod_check_true>".*?")\|\|(?<mod_check_false>".*?")\]))?\}/gi;
|
||||
/\{(?<type>stream|provider|debug|addon|config)\.(?<prop>\w+)(::(?<mod>(\w+(\([^)]*\))?|<|<=|=|>=|>|\^|\$|~|\/)+))?((::(?<mod_tzlocale>\S+?))|(?<mod_check>\[(?<mod_check_true>".*?")\|\|(?<mod_check_false>".*?")\]))?\}/gi;
|
||||
let matches: RegExpExecArray | null;
|
||||
|
||||
while ((matches = re.exec(str))) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ParsedStream } from '@aiostreams/types';
|
||||
import { formatDuration, formatSize, languageToEmoji } from './utils';
|
||||
import { serviceDetails } from '@aiostreams/utils';
|
||||
import { serviceDetails, Settings } from '@aiostreams/utils';
|
||||
|
||||
export function gdriveFormat(
|
||||
stream: ParsedStream,
|
||||
@@ -100,6 +100,13 @@ export function gdriveFormat(
|
||||
if (stream.message) {
|
||||
description += `📢 ${stream.message}`;
|
||||
}
|
||||
|
||||
if (stream.proxied) {
|
||||
name = `🕵️♂️ ${name}`;
|
||||
} else if (Settings.SHOW_DIE) {
|
||||
name = `🎲 ${name}`;
|
||||
}
|
||||
|
||||
description = description.trim();
|
||||
name = name.trim();
|
||||
return { name, description };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ParsedStream } from '@aiostreams/types';
|
||||
import { formatSize } from './utils';
|
||||
import { serviceDetails } from '@aiostreams/utils';
|
||||
import { serviceDetails, Settings } from '@aiostreams/utils';
|
||||
|
||||
export function torboxFormat(stream: ParsedStream): {
|
||||
name: string;
|
||||
@@ -41,5 +41,11 @@ export function torboxFormat(stream: ParsedStream): {
|
||||
description += `\n${stream.message}`;
|
||||
}
|
||||
|
||||
if (stream.proxied) {
|
||||
name = `🕵️♂️ ${name}`;
|
||||
} else if (Settings.SHOW_DIE) {
|
||||
name = `🎲 ${name}`;
|
||||
}
|
||||
|
||||
return { name, description };
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ParsedStream } from '@aiostreams/types';
|
||||
import { formatSize, languageToEmoji } from './utils';
|
||||
import { serviceDetails } from '@aiostreams/utils';
|
||||
import { serviceDetails, Settings } from '@aiostreams/utils';
|
||||
|
||||
export function torrentioFormat(stream: ParsedStream): {
|
||||
name: string;
|
||||
@@ -64,5 +64,12 @@ export function torrentioFormat(stream: ParsedStream): {
|
||||
if (languageEmojis.length > 0) {
|
||||
description += `\n${languageEmojis.join(' / ')}`;
|
||||
}
|
||||
|
||||
if (stream.proxied) {
|
||||
name = `🕵️♂️ ${name}`;
|
||||
} else if (Settings.SHOW_DIE) {
|
||||
name = `🎲 ${name}`;
|
||||
}
|
||||
|
||||
return { name, description };
|
||||
}
|
||||
|
||||
@@ -177,8 +177,6 @@ export default function Configure() {
|
||||
const [minMovieSize, setMinMovieSize] = useState<number | null>(null);
|
||||
const [maxEpisodeSize, setMaxEpisodeSize] = useState<number | null>(null);
|
||||
const [minEpisodeSize, setMinEpisodeSize] = useState<number | null>(null);
|
||||
const [addonNameInDescription, setAddonNameInDescription] =
|
||||
useState<boolean>(false);
|
||||
const [cleanResults, setCleanResults] = useState<boolean>(false);
|
||||
const [maxResultsPerResolution, setMaxResultsPerResolution] = useState<
|
||||
number | null
|
||||
@@ -257,7 +255,6 @@ export default function Configure() {
|
||||
minMovieSize,
|
||||
maxEpisodeSize,
|
||||
minEpisodeSize,
|
||||
addonNameInDescription,
|
||||
cleanResults,
|
||||
maxResultsPerResolution,
|
||||
strictIncludeFilters:
|
||||
@@ -562,7 +559,6 @@ export default function Configure() {
|
||||
decodedConfig.minEpisodeSize || decodedConfig.minSize || null
|
||||
);
|
||||
setAddons(loadValidAddons(decodedConfig.addons));
|
||||
setAddonNameInDescription(decodedConfig.addonNameInDescription || false);
|
||||
setCleanResults(decodedConfig.cleanResults || false);
|
||||
setMaxResultsPerResolution(decodedConfig.maxResultsPerResolution || null);
|
||||
setMediaFlowEnabled(
|
||||
@@ -1139,34 +1135,6 @@ export default function Configure() {
|
||||
<FormatterPreview formatter={formatter || 'gdrive'} />
|
||||
</div>
|
||||
|
||||
<div className={styles.section}>
|
||||
<div className={styles.setting}>
|
||||
<div className={styles.settingDescription}>
|
||||
<h2 style={{ padding: '5px' }}>Move Addon Name to Description</h2>
|
||||
<p style={{ padding: '5px' }}>
|
||||
Move the addon name to the description of the stream. This will
|
||||
show <code>AIOStreams</code> as the stream title, but move the
|
||||
name of the addon that the stream is from to the description.
|
||||
This is useful for Vidi users.
|
||||
</p>
|
||||
</div>
|
||||
<div className={styles.checkboxSettingInput}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={addonNameInDescription}
|
||||
onChange={(e) => setAddonNameInDescription(e.target.checked)}
|
||||
// move to the right
|
||||
style={{
|
||||
marginLeft: 'auto',
|
||||
marginRight: '20px',
|
||||
width: '25px',
|
||||
height: '25px',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.section}>
|
||||
<div className={styles.setting}>
|
||||
<div className={styles.ettingDescription}>
|
||||
|
||||
@@ -24,20 +24,21 @@ const FormatterPreview: React.FC<FormatterPreviewProps> = ({ formatter }) => {
|
||||
const [indexers, setIndexers] = React.useState<string>('RARBG');
|
||||
const [seeders, setSeeders] = React.useState<number>(125);
|
||||
const [usenetAge, setUsenetAge] = React.useState<string>('10d'); // Days
|
||||
const [addonName, setAddonName] = React.useState<string>('AIOStreams');
|
||||
const [addonName, setAddonName] = React.useState<string>('Torrentio');
|
||||
const [providerId, setProviderId] = React.useState<string>('realdebrid');
|
||||
const [isCached, setIsCached] = React.useState<boolean>(true);
|
||||
const [isP2P, setIsP2P] = React.useState<boolean>(false);
|
||||
const [isPersonal, setIsPersonal] = React.useState<boolean>(false);
|
||||
const [duration, setDuration] = React.useState<number>(9120000); // 2h 32m
|
||||
const [fileSize, setFileSize] = React.useState<number>(62500000000); // 58.2 GB
|
||||
const [proxied, setProxied] = React.useState<boolean>(false); // Proxied or not
|
||||
const parsedInfo = parseFilename(filename);
|
||||
|
||||
console.log(`Formatter: ${formatter}`);
|
||||
const sampleStream: ParsedStream = {
|
||||
...parsedInfo,
|
||||
addon: {
|
||||
id: 'aiostreams',
|
||||
id: 'test-addon',
|
||||
name: addonName,
|
||||
},
|
||||
filename: filename,
|
||||
@@ -55,6 +56,7 @@ const FormatterPreview: React.FC<FormatterPreviewProps> = ({ formatter }) => {
|
||||
},
|
||||
type: providerId === 'usenet' ? 'usenet' : 'debrid',
|
||||
personal: isPersonal,
|
||||
proxied: proxied,
|
||||
};
|
||||
|
||||
const getFormatterExample = () => {
|
||||
@@ -314,6 +316,12 @@ const FormatterPreview: React.FC<FormatterPreviewProps> = ({ formatter }) => {
|
||||
isChecked={isPersonal}
|
||||
setChecked={setIsPersonal}
|
||||
/>
|
||||
|
||||
<ToggleSwitch
|
||||
label="Proxied"
|
||||
isChecked={proxied}
|
||||
setChecked={setProxied}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
},
|
||||
"description": "Library to parse a torrent filename ",
|
||||
"dependencies": {
|
||||
"@aiostreams/types": "^1.0.0"
|
||||
"@aiostreams/types": "^1.0.0",
|
||||
"parse-torrent-title": "github:TheBeastLT/parse-torrent-title"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ParsedNameData } from '@aiostreams/types';
|
||||
import { PARSE_REGEX } from './regex';
|
||||
import * as PTT from 'parse-torrent-title';
|
||||
|
||||
function matchPattern(
|
||||
filename: string,
|
||||
@@ -33,7 +34,15 @@ export function parseFilename(filename: string): ParsedNameData {
|
||||
const visualTags = matchMultiplePatterns(filename, PARSE_REGEX.visualTags);
|
||||
const audioTags = matchMultiplePatterns(filename, PARSE_REGEX.audioTags);
|
||||
const languages = matchMultiplePatterns(filename, PARSE_REGEX.languages);
|
||||
const releaseGroup = getMatchingPattern(filename, PARSE_REGEX.releaseGroup);
|
||||
// const releaseGroup = getMatchingPattern(filename, PARSE_REGEX.releaseGroup);
|
||||
|
||||
const parsed = PTT.parse(filename);
|
||||
const releaseGroup = parsed.group || 'Unknown';
|
||||
const title = parsed.title;
|
||||
const year = parsed.year ? parsed.year.toString() : undefined;
|
||||
const season = parsed.season;
|
||||
const seasons = parsed.seasons;
|
||||
const episode = parsed.episode;
|
||||
|
||||
return {
|
||||
resolution,
|
||||
@@ -43,5 +52,10 @@ export function parseFilename(filename: string): ParsedNameData {
|
||||
audioTags,
|
||||
visualTags,
|
||||
releaseGroup,
|
||||
title,
|
||||
year,
|
||||
season,
|
||||
seasons,
|
||||
episode,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,10 +7,16 @@ export interface ParsedNameData {
|
||||
visualTags: string[];
|
||||
audioTags: string[];
|
||||
languages: string[];
|
||||
title?: string;
|
||||
year?: string;
|
||||
season?: number;
|
||||
seasons?: number[];
|
||||
episode?: number;
|
||||
}
|
||||
|
||||
// the parsed stream data which is to be used to create the final stream object
|
||||
export interface ParsedStream extends ParsedNameData {
|
||||
proxied: boolean; // if the stream is proxied or not
|
||||
addon: {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -148,7 +154,6 @@ export interface Config {
|
||||
minMovieSize: number | null;
|
||||
maxEpisodeSize: number | null;
|
||||
minEpisodeSize: number | null;
|
||||
addonNameInDescription?: boolean;
|
||||
cleanResults: boolean;
|
||||
maxResultsPerResolution: number | null;
|
||||
excludeFilters: string[] | null;
|
||||
|
||||
@@ -253,6 +253,7 @@ export class BaseWrapper {
|
||||
type: 'stream',
|
||||
result: {
|
||||
...parsedInfo,
|
||||
proxied: false,
|
||||
message: message,
|
||||
addon: { name: this.addonName, id: this.addonId },
|
||||
filename: filename,
|
||||
|
||||
Reference in New Issue
Block a user