mirror of
https://github.com/Viren070/AIOStreams.git
synced 2025-12-01 23:14:04 +01:00
feat(debrid): don't make proxy required and add public URL field for nzbdav/altmount
This commit is contained in:
@@ -273,153 +273,153 @@ export abstract class BaseDebridAddon<T extends BaseDebridConfig> {
|
||||
|
||||
const results = [...processedTorrents.results, ...processedNzbs.results];
|
||||
|
||||
// Setup auth for both NzbDAV and Altmount
|
||||
let nzbdavAuth;
|
||||
let altmountAuth;
|
||||
// // Setup auth for both NzbDAV and Altmount
|
||||
// let nzbdavAuth;
|
||||
// let altmountAuth;
|
||||
|
||||
const encodedNzbdavAuth = this.userData.services.find(
|
||||
(s) => s.id === 'nzbdav'
|
||||
)?.credential;
|
||||
const encodedAltmountAuth = this.userData.services.find(
|
||||
(s) => s.id === 'altmount'
|
||||
)?.credential;
|
||||
// const encodedNzbdavAuth = this.userData.services.find(
|
||||
// (s) => s.id === 'nzbdav'
|
||||
// )?.credential;
|
||||
// const encodedAltmountAuth = this.userData.services.find(
|
||||
// (s) => s.id === 'altmount'
|
||||
// )?.credential;
|
||||
|
||||
if (encodedNzbdavAuth) {
|
||||
const { success, data } = NzbDavConfig.safeParse(
|
||||
JSON.parse(fromUrlSafeBase64(encodedNzbdavAuth))
|
||||
);
|
||||
if (success) {
|
||||
nzbdavAuth = data;
|
||||
}
|
||||
}
|
||||
// if (encodedNzbdavAuth) {
|
||||
// const { success, data } = NzbDavConfig.safeParse(
|
||||
// JSON.parse(fromUrlSafeBase64(encodedNzbdavAuth))
|
||||
// );
|
||||
// if (success) {
|
||||
// nzbdavAuth = data;
|
||||
// }
|
||||
// }
|
||||
|
||||
if (encodedAltmountAuth) {
|
||||
const { success, data } = AltmountConfig.safeParse(
|
||||
JSON.parse(fromUrlSafeBase64(encodedAltmountAuth))
|
||||
);
|
||||
if (success) {
|
||||
altmountAuth = data;
|
||||
}
|
||||
}
|
||||
// if (encodedAltmountAuth) {
|
||||
// const { success, data } = AltmountConfig.safeParse(
|
||||
// JSON.parse(fromUrlSafeBase64(encodedAltmountAuth))
|
||||
// );
|
||||
// if (success) {
|
||||
// altmountAuth = data;
|
||||
// }
|
||||
// }
|
||||
|
||||
// Collect indices for proxying
|
||||
const nzbdavProxyIndices: number[] = [];
|
||||
const altmountProxyIndices: number[] = [];
|
||||
// // Collect indices for proxying
|
||||
// const nzbdavProxyIndices: number[] = [];
|
||||
// const altmountProxyIndices: number[] = [];
|
||||
|
||||
if (nzbdavAuth) {
|
||||
nzbdavProxyIndices.push(
|
||||
...results
|
||||
.map((result, index) => ({ result, index }))
|
||||
.filter(({ result }) => result.service?.id === 'nzbdav')
|
||||
.map(({ index }) => index)
|
||||
);
|
||||
}
|
||||
// if (nzbdavAuth) {
|
||||
// nzbdavProxyIndices.push(
|
||||
// ...results
|
||||
// .map((result, index) => ({ result, index }))
|
||||
// .filter(({ result }) => result.service?.id === 'nzbdav')
|
||||
// .map(({ index }) => index)
|
||||
// );
|
||||
// }
|
||||
|
||||
if (altmountAuth) {
|
||||
altmountProxyIndices.push(
|
||||
...results
|
||||
.map((result, index) => ({ result, index }))
|
||||
.filter(({ result }) => result.service?.id === 'altmount')
|
||||
.map(({ index }) => index)
|
||||
);
|
||||
}
|
||||
// if (altmountAuth) {
|
||||
// altmountProxyIndices.push(
|
||||
// ...results
|
||||
// .map((result, index) => ({ result, index }))
|
||||
// .filter(({ result }) => result.service?.id === 'altmount')
|
||||
// .map(({ index }) => index)
|
||||
// );
|
||||
// }
|
||||
|
||||
let resultStreams = await Promise.all(
|
||||
results.map((result) =>
|
||||
this._createStream(result, encryptedStoreAuths, metadataId)
|
||||
)
|
||||
);
|
||||
// Proxy NzbDAV streams
|
||||
if (nzbdavProxyIndices.length > 0 && nzbdavAuth) {
|
||||
const proxy = createProxy({
|
||||
id: 'builtin',
|
||||
enabled: true,
|
||||
credentials: nzbdavAuth.aiostreamsAuth,
|
||||
});
|
||||
// // Proxy NzbDAV streams
|
||||
// if (nzbdavProxyIndices.length > 0 && nzbdavAuth) {
|
||||
// const proxy = createProxy({
|
||||
// id: 'builtin',
|
||||
// enabled: true,
|
||||
// credentials: nzbdavAuth.aiostreamsAuth,
|
||||
// });
|
||||
|
||||
const proxiedStreams = await proxy.generateUrls(
|
||||
nzbdavProxyIndices
|
||||
.map((i) => resultStreams[i])
|
||||
.map((stream) => ({
|
||||
url: stream.url!,
|
||||
filename: stream.behaviorHints?.filename ?? undefined,
|
||||
headers: {
|
||||
request: {
|
||||
Authorization: `Basic ${Buffer.from(
|
||||
`${nzbdavAuth.webdavUser}:${nzbdavAuth.webdavPassword}`
|
||||
).toString('base64')}`,
|
||||
},
|
||||
},
|
||||
}))
|
||||
);
|
||||
// const proxiedStreams = await proxy.generateUrls(
|
||||
// nzbdavProxyIndices
|
||||
// .map((i) => resultStreams[i])
|
||||
// .map((stream) => ({
|
||||
// url: stream.url!,
|
||||
// filename: stream.behaviorHints?.filename ?? undefined,
|
||||
// headers: {
|
||||
// request: {
|
||||
// Authorization: `Basic ${Buffer.from(
|
||||
// `${nzbdavAuth.webdavUser}:${nzbdavAuth.webdavPassword}`
|
||||
// ).toString('base64')}`,
|
||||
// },
|
||||
// },
|
||||
// }))
|
||||
// );
|
||||
|
||||
if (proxiedStreams) {
|
||||
for (let i = 0; i < nzbdavProxyIndices.length; i++) {
|
||||
const index = nzbdavProxyIndices[i];
|
||||
const proxiedUrl = proxiedStreams[i];
|
||||
if (proxiedUrl) {
|
||||
resultStreams[index].url = proxiedUrl;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
errorStreams.push(
|
||||
this._createErrorStream({
|
||||
title: `${this.name}`,
|
||||
description: `Failed to proxy NzbDAV streams, ensure your proxy auth is correct.`,
|
||||
})
|
||||
);
|
||||
// remove all nzbdav streams
|
||||
resultStreams = resultStreams.filter(
|
||||
(_, i) => !nzbdavProxyIndices.includes(i)
|
||||
);
|
||||
}
|
||||
}
|
||||
// if (proxiedStreams) {
|
||||
// for (let i = 0; i < nzbdavProxyIndices.length; i++) {
|
||||
// const index = nzbdavProxyIndices[i];
|
||||
// const proxiedUrl = proxiedStreams[i];
|
||||
// if (proxiedUrl) {
|
||||
// resultStreams[index].url = proxiedUrl;
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// errorStreams.push(
|
||||
// this._createErrorStream({
|
||||
// title: `${this.name}`,
|
||||
// description: `Failed to proxy NzbDAV streams, ensure your proxy auth is correct.`,
|
||||
// })
|
||||
// );
|
||||
// // remove all nzbdav streams
|
||||
// resultStreams = resultStreams.filter(
|
||||
// (_, i) => !nzbdavProxyIndices.includes(i)
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// Proxy Altmount streams
|
||||
if (altmountProxyIndices.length > 0 && altmountAuth) {
|
||||
const proxy = createProxy({
|
||||
id: 'builtin',
|
||||
enabled: true,
|
||||
credentials: altmountAuth.aiostreamsAuth,
|
||||
});
|
||||
// // Proxy Altmount streams
|
||||
// if (altmountProxyIndices.length > 0 && altmountAuth) {
|
||||
// const proxy = createProxy({
|
||||
// id: 'builtin',
|
||||
// enabled: true,
|
||||
// credentials: altmountAuth.aiostreamsAuth,
|
||||
// });
|
||||
|
||||
const proxiedStreams = await proxy.generateUrls(
|
||||
altmountProxyIndices
|
||||
.map((i) => resultStreams[i])
|
||||
.map((stream) => ({
|
||||
url: stream.url!,
|
||||
filename: stream.behaviorHints?.filename ?? undefined,
|
||||
headers: {
|
||||
request: {
|
||||
Authorization: `Basic ${Buffer.from(
|
||||
`${altmountAuth.webdavUser}:${altmountAuth.webdavPassword}`
|
||||
).toString('base64')}`,
|
||||
},
|
||||
},
|
||||
}))
|
||||
);
|
||||
// const proxiedStreams = await proxy.generateUrls(
|
||||
// altmountProxyIndices
|
||||
// .map((i) => resultStreams[i])
|
||||
// .map((stream) => ({
|
||||
// url: stream.url!,
|
||||
// filename: stream.behaviorHints?.filename ?? undefined,
|
||||
// headers: {
|
||||
// request: {
|
||||
// Authorization: `Basic ${Buffer.from(
|
||||
// `${altmountAuth.webdavUser}:${altmountAuth.webdavPassword}`
|
||||
// ).toString('base64')}`,
|
||||
// },
|
||||
// },
|
||||
// }))
|
||||
// );
|
||||
|
||||
if (proxiedStreams) {
|
||||
for (let i = 0; i < altmountProxyIndices.length; i++) {
|
||||
const index = altmountProxyIndices[i];
|
||||
const proxiedUrl = proxiedStreams[i];
|
||||
if (proxiedUrl) {
|
||||
resultStreams[index].url = proxiedUrl;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
errorStreams.push(
|
||||
this._createErrorStream({
|
||||
title: `${this.name}`,
|
||||
description: `Failed to proxy Altmount streams, ensure your proxy auth is correct.`,
|
||||
})
|
||||
);
|
||||
// remove all altmount streams
|
||||
resultStreams = resultStreams.filter(
|
||||
(_, i) => !altmountProxyIndices.includes(i)
|
||||
);
|
||||
}
|
||||
}
|
||||
// if (proxiedStreams) {
|
||||
// for (let i = 0; i < altmountProxyIndices.length; i++) {
|
||||
// const index = altmountProxyIndices[i];
|
||||
// const proxiedUrl = proxiedStreams[i];
|
||||
// if (proxiedUrl) {
|
||||
// resultStreams[index].url = proxiedUrl;
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// errorStreams.push(
|
||||
// this._createErrorStream({
|
||||
// title: `${this.name}`,
|
||||
// description: `Failed to proxy Altmount streams, ensure your proxy auth is correct.`,
|
||||
// })
|
||||
// );
|
||||
// // remove all altmount streams
|
||||
// resultStreams = resultStreams.filter(
|
||||
// (_, i) => !altmountProxyIndices.includes(i)
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
[...processedTorrents.errors, ...processedNzbs.errors].forEach((error) => {
|
||||
let errMsg = error.error.message;
|
||||
|
||||
@@ -13,10 +13,13 @@ export const AltmountConfig = z.object({
|
||||
altmountUrl: z
|
||||
.string()
|
||||
.transform((s) => s.trim().replace(/^\/+/, '').replace(/\/+$/, '')),
|
||||
publicAltmountUrl: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform((s) => s?.trim().replace(/^\/+/, '').replace(/\/+$/, '')),
|
||||
altmountApiKey: z.string(),
|
||||
webdavUser: z.string(),
|
||||
webdavPassword: z.string(),
|
||||
aiostreamsAuth: z.string(),
|
||||
});
|
||||
|
||||
export class AltmountService extends UsenetStreamService {
|
||||
@@ -30,11 +33,11 @@ export class AltmountService extends UsenetStreamService {
|
||||
|
||||
const auth: UsenetStreamServiceConfig = {
|
||||
webdavUrl: `${parsedConfig.altmountUrl}/webdav/`,
|
||||
publicWebdavUrl: `${parsedConfig.publicAltmountUrl ?? parsedConfig.altmountUrl}/webdav/`,
|
||||
webdavUser: parsedConfig.webdavUser,
|
||||
webdavPassword: parsedConfig.webdavPassword,
|
||||
apiUrl: `${parsedConfig.altmountUrl}/sabnzbd/api`,
|
||||
apiKey: parsedConfig.altmountApiKey,
|
||||
aiostreamsAuth: parsedConfig.aiostreamsAuth,
|
||||
};
|
||||
|
||||
super(config, auth, 'altmount');
|
||||
@@ -50,11 +53,4 @@ export class AltmountService extends UsenetStreamService {
|
||||
? basename(nzbUrl, '.nzb')
|
||||
: basename(nzbUrl);
|
||||
}
|
||||
|
||||
protected async generatePlaybackLink(filePath: string): Promise<string> {
|
||||
const parsedConfig = AltmountConfig.parse(
|
||||
JSON.parse(fromUrlSafeBase64(this.config.token))
|
||||
);
|
||||
return `${parsedConfig.altmountUrl}/webdav${filePath}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,13 @@ export const NzbDavConfig = z.object({
|
||||
nzbdavUrl: z
|
||||
.string()
|
||||
.transform((s) => s.trim().replace(/^\/+/, '').replace(/\/+$/, '')),
|
||||
publicNzbdavUrl: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform((s) => s?.trim().replace(/^\/+/, '').replace(/\/+$/, '')),
|
||||
nzbdavApiKey: z.string(),
|
||||
webdavUser: z.string(),
|
||||
webdavPassword: z.string(),
|
||||
aiostreamsAuth: z.string(),
|
||||
});
|
||||
|
||||
export class NzbDAVService extends UsenetStreamService {
|
||||
@@ -32,11 +35,11 @@ export class NzbDAVService extends UsenetStreamService {
|
||||
|
||||
const auth: UsenetStreamServiceConfig = {
|
||||
webdavUrl: `${parsedConfig.nzbdavUrl}/`,
|
||||
publicWebdavUrl: `${parsedConfig.publicNzbdavUrl ?? parsedConfig.nzbdavUrl}/`,
|
||||
webdavUser: parsedConfig.webdavUser,
|
||||
webdavPassword: parsedConfig.webdavPassword,
|
||||
apiUrl: `${parsedConfig.nzbdavUrl}/api`,
|
||||
apiKey: parsedConfig.nzbdavApiKey,
|
||||
aiostreamsAuth: parsedConfig.aiostreamsAuth,
|
||||
};
|
||||
|
||||
super(config, auth, 'nzbdav');
|
||||
@@ -50,11 +53,4 @@ export class NzbDAVService extends UsenetStreamService {
|
||||
// NzbDAV uses the filename parameter
|
||||
return filename;
|
||||
}
|
||||
|
||||
protected async generatePlaybackLink(filePath: string): Promise<string> {
|
||||
const parsedConfig = NzbDavConfig.parse(
|
||||
JSON.parse(fromUrlSafeBase64(this.config.token))
|
||||
);
|
||||
return `${parsedConfig.nzbdavUrl}${filePath}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,11 +382,11 @@ export class SABnzbdApi {
|
||||
*/
|
||||
export interface UsenetStreamServiceConfig {
|
||||
webdavUrl: string;
|
||||
publicWebdavUrl: string;
|
||||
webdavUser: string;
|
||||
webdavPassword: string;
|
||||
apiUrl: string;
|
||||
apiKey: string;
|
||||
aiostreamsAuth: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -566,18 +566,6 @@ export abstract class UsenetStreamService implements DebridService {
|
||||
}
|
||||
|
||||
public async checkNzbs(hashes: string[]): Promise<DebridDownload[]> {
|
||||
// validate proxy auth
|
||||
try {
|
||||
BuiltinProxy.validateAuth(this.auth.aiostreamsAuth);
|
||||
} catch (error) {
|
||||
throw new DebridError(`Invalid AIOStreams proxy auth`, {
|
||||
statusCode: 401,
|
||||
statusText: 'Unauthorized',
|
||||
code: 'UNAUTHORIZED',
|
||||
headers: {},
|
||||
type: 'api_error',
|
||||
});
|
||||
}
|
||||
// All NZBs are "cached" since it's streaming-based
|
||||
return hashes.map((h, index) => ({
|
||||
id: index,
|
||||
@@ -785,7 +773,7 @@ export abstract class UsenetStreamService implements DebridService {
|
||||
});
|
||||
|
||||
const filePath = selectedFile.path || `${contentPath}/${selectedFile.name}`;
|
||||
const playbackLink = await this.generatePlaybackLink(filePath);
|
||||
const playbackLink = `${this.getPublicWebdavUrlWithAuth()}${filePath}`;
|
||||
|
||||
this.serviceLogger.debug(`Generated playback link`, { playbackLink });
|
||||
|
||||
@@ -800,9 +788,10 @@ export abstract class UsenetStreamService implements DebridService {
|
||||
return playbackLink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the playback link for a given file path
|
||||
* This method can be overridden by subclasses to customize the URL format
|
||||
*/
|
||||
protected abstract generatePlaybackLink(filePath: string): Promise<string>;
|
||||
protected getPublicWebdavUrlWithAuth(): string {
|
||||
let url = new URL(this.auth.publicWebdavUrl);
|
||||
url.username = this.auth.webdavUser;
|
||||
url.password = encodeURIComponent(this.auth.webdavPassword);
|
||||
return url.toString().replace(/\/+$/, ''); // Remove trailing slash
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,11 +374,7 @@ class StreamParser {
|
||||
stream: Stream,
|
||||
currentParsedStream: ParsedStream
|
||||
): ParsedStream['service'] | undefined {
|
||||
const service = this.parseServiceData(stream.name || '');
|
||||
if (service?.id === 'nzbdav') {
|
||||
currentParsedStream.proxied = true;
|
||||
}
|
||||
return service;
|
||||
return this.parseServiceData(stream.name || '');
|
||||
}
|
||||
|
||||
protected getInfoHash(
|
||||
|
||||
@@ -70,6 +70,7 @@ export class BuiltinAddonPreset extends Preset {
|
||||
toUrlSafeBase64(
|
||||
JSON.stringify({
|
||||
nzbdavUrl: credentials.url,
|
||||
publicNzbdavUrl: credentials.publicUrl,
|
||||
nzbdavApiKey: credentials.apiKey,
|
||||
webdavUser: credentials.username,
|
||||
webdavPassword: credentials.password,
|
||||
@@ -84,6 +85,7 @@ export class BuiltinAddonPreset extends Preset {
|
||||
toUrlSafeBase64(
|
||||
JSON.stringify({
|
||||
altmountUrl: credentials.url,
|
||||
publicAltmountUrl: credentials.publicUrl,
|
||||
altmountApiKey: credentials.apiKey,
|
||||
webdavUser: credentials.username,
|
||||
webdavPassword: credentials.password,
|
||||
|
||||
@@ -393,14 +393,30 @@ const SERVICE_DETAILS: Record<
|
||||
knownNames: ['ND'],
|
||||
signUpText: 'Stream usenet directly from your provider via Nzb DAV.',
|
||||
credentials: [
|
||||
{
|
||||
id: 'note',
|
||||
name: 'What do I put for URL and Public URL?',
|
||||
description: `\n**URL:** Use internal URL for local setups (e.g., http://nzbdav:3000), otherwise use a public URL here.\n\n**Public URL:** Only needed if streams use local URL but you need public access. Leave blank if URL is already public or if using a proxy.\n\n**Note:** WebDAV URL/credentials are exposed in the stream URLs if not using a proxy.`,
|
||||
type: 'alert',
|
||||
intent: 'info',
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
id: 'url',
|
||||
name: 'NzbDAV URL',
|
||||
description:
|
||||
'The base URL of your NZB DAV instance. E.g., http://nzbdav:3000 or https://nzbdav.example.com',
|
||||
'The base URL of your NZB DAV instance. E.g., http://nzbdav:3000',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'publicUrl',
|
||||
name: 'Public NzbDAV URL (Optional)',
|
||||
description:
|
||||
'The public URL of your NzbDAV instance. Optional, see note above for details.',
|
||||
type: 'string',
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
id: 'apiKey',
|
||||
name: 'NzbDAV API Key',
|
||||
@@ -425,14 +441,6 @@ const SERVICE_DETAILS: Record<
|
||||
type: 'password',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'aiostreamsAuth',
|
||||
name: 'AIOStreams Proxy Auth',
|
||||
description:
|
||||
'It is required to proxy the NzbDAV streams through AIOStreams. Provide a username:password pair from the `AIOSTREAMS_AUTH` environment variable.',
|
||||
type: 'password',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
[ALTMOUNT_SERVICE]: {
|
||||
@@ -442,14 +450,30 @@ const SERVICE_DETAILS: Record<
|
||||
knownNames: ['AM'],
|
||||
signUpText: 'Stream usenet directly from your provider via AltMount.',
|
||||
credentials: [
|
||||
{
|
||||
id: 'note',
|
||||
name: 'What do I put for URL and Public URL?',
|
||||
description: `\n**URL:** Use internal URL for local setups (e.g., http://altmount:8000), otherwise use a public URL here.\n\n**Public URL:** Only needed if streams use local URL but you need public access. Leave blank if URL is already public or if using a proxy.\n\n**Note:** WebDAV URL/credentials are exposed in the stream URLs if not using a proxy.`,
|
||||
type: 'alert',
|
||||
intent: 'info',
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
id: 'url',
|
||||
name: 'Altmount URL',
|
||||
description:
|
||||
'The base URL of your AltMount instance. E.g., http://altmount:8080 or https://altmount.example.com',
|
||||
'The base URL of your AltMount instance used for requests. e.g., http://altmount:8080',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'publicUrl',
|
||||
name: 'Public Altmount URL',
|
||||
description:
|
||||
'The public URL of your AltMount instance. Optional, see note above for details.',
|
||||
type: 'string',
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
id: 'apiKey',
|
||||
name: 'AltMount API Key',
|
||||
@@ -474,14 +498,6 @@ const SERVICE_DETAILS: Record<
|
||||
type: 'password',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'aiostreamsAuth',
|
||||
name: 'AIOStreams Proxy Auth',
|
||||
description:
|
||||
'It is required to proxy the AltMount streams through AIOStreams. Provide a username:password pair from the `AIOSTREAMS_AUTH` environment variable.',
|
||||
type: 'password',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user