feat: add Amvstrm as provider for stream URL
Summary: Test Plan:
This commit is contained in:
75
src/controllers/episodes/getEpisodeUrl/amvstrm.ts
Normal file
75
src/controllers/episodes/getEpisodeUrl/amvstrm.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import type { FetchUrlResponse } from "./responseType";
|
||||
|
||||
export async function getSourcesFromAmvstrm(
|
||||
watchId: string,
|
||||
): Promise<FetchUrlResponse | null> {
|
||||
const source = await fetch(
|
||||
`https://api-amvstrm.nyt92.eu.org/api/v2/stream/${watchId}`,
|
||||
)
|
||||
.then((res) => res.json<AmvstrmStreamResponse>())
|
||||
.then(({ stream }) => {
|
||||
const streamObj = stream?.multi;
|
||||
if (!!streamObj) {
|
||||
return streamObj.main ?? streamObj.backup;
|
||||
}
|
||||
})
|
||||
.then((streamObj) => streamObj?.url);
|
||||
|
||||
if (!source) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
source,
|
||||
subtitles: [],
|
||||
audio: [],
|
||||
};
|
||||
}
|
||||
|
||||
interface AmvstrmStreamResponse {
|
||||
code: number;
|
||||
message: string;
|
||||
info: Info;
|
||||
stream: Stream;
|
||||
iframe: Iframe[];
|
||||
plyr: Nspl;
|
||||
nspl: Nspl;
|
||||
}
|
||||
|
||||
interface Iframe {
|
||||
name: string;
|
||||
iframe: string;
|
||||
}
|
||||
|
||||
interface Info {
|
||||
title: string;
|
||||
id: string;
|
||||
episode: string;
|
||||
}
|
||||
|
||||
interface Nspl {
|
||||
main: string;
|
||||
backup: string;
|
||||
}
|
||||
|
||||
interface Stream {
|
||||
multi: Multi;
|
||||
tracks: Tracks;
|
||||
}
|
||||
|
||||
interface Multi {
|
||||
main: Backup;
|
||||
backup: Backup;
|
||||
}
|
||||
|
||||
interface Backup {
|
||||
url: string;
|
||||
label: string;
|
||||
isM3U8: boolean;
|
||||
quality: string;
|
||||
}
|
||||
|
||||
interface Tracks {
|
||||
file: string;
|
||||
kind: string;
|
||||
}
|
||||
@@ -90,4 +90,45 @@ describe('requests the "/episodes/:id/url" route', () => {
|
||||
expect(response.json()).resolves.toEqual({ success: false });
|
||||
expect(response.status).toBe(404);
|
||||
});
|
||||
|
||||
it("with sources from Amvstrm", async () => {
|
||||
const response = await app.request(
|
||||
"/episodes/1/url",
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
provider: "amvstrm",
|
||||
id: "ore-dake-level-up-na-ken-episode-2",
|
||||
}),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
},
|
||||
{
|
||||
ENABLE_ANIFY: "true",
|
||||
},
|
||||
);
|
||||
|
||||
expect(response.json()).resolves.toEqual({
|
||||
success: true,
|
||||
result: {
|
||||
source:
|
||||
"https://www032.vipanicdn.net/streamhls/aa804a2400535d84dd59454b28d329fb/ep.1.1712504065.m3u8",
|
||||
subtitles: [],
|
||||
audio: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("with no URL from Amvstrm source", async () => {
|
||||
const response = await app.request("/episodes/-1/url", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
provider: "amvstrm",
|
||||
id: "unknown",
|
||||
}),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
|
||||
expect(response.json()).resolves.toEqual({ success: false });
|
||||
expect(response.status).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -112,6 +112,26 @@ app.openapi(route, async (c) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (provider === "amvstrm") {
|
||||
try {
|
||||
const result = await import("./amvstrm").then(
|
||||
({ getSourcesFromAmvstrm }) => getSourcesFromAmvstrm(id),
|
||||
);
|
||||
if (!result) {
|
||||
return c.json({ success: false }, { status: 404 });
|
||||
}
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
result,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch download URL from Amvstrm", e);
|
||||
|
||||
return c.json(ErrorResponse, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
return c.json(ErrorResponse, { status: 400 });
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user