mirror of
https://github.com/p-stream/providers.git
synced 2026-03-11 17:55:36 +00:00
add myanime
This commit is contained in:
parent
8b667bd776
commit
9f22349271
4 changed files with 205 additions and 0 deletions
|
|
@ -27,6 +27,8 @@ import { cinemaosEmbeds } from './embeds/cinemaos';
|
|||
import { closeLoadScraper } from './embeds/closeload';
|
||||
import { madplayBaseEmbed, madplayNsapiEmbed, madplayNsapiVidFastEmbed, madplayRoperEmbed } from './embeds/madplay';
|
||||
import { mp4hydraServer1Scraper, mp4hydraServer2Scraper } from './embeds/mp4hydra';
|
||||
import { myanimedubScraper } from './embeds/myanimedub';
|
||||
import { myanimesubScraper } from './embeds/myanimesub';
|
||||
import { ridooScraper } from './embeds/ridoo';
|
||||
import { streamtapeLatinoScraper, streamtapeScraper } from './embeds/streamtape';
|
||||
import { streamvidScraper } from './embeds/streamvid';
|
||||
|
|
@ -76,6 +78,7 @@ import { iosmirrorScraper } from './sources/iosmirror';
|
|||
import { iosmirrorPVScraper } from './sources/iosmirrorpv';
|
||||
import { lookmovieScraper } from './sources/lookmovie';
|
||||
import { madplayScraper } from './sources/madplay';
|
||||
import { myanimeScraper } from './sources/myanime';
|
||||
import { nunflixScraper } from './sources/nunflix';
|
||||
import { rgshowsScraper } from './sources/rgshows';
|
||||
import { ridooMoviesScraper } from './sources/ridomovies';
|
||||
|
|
@ -99,6 +102,7 @@ export function gatherAllSources(): Array<Sourcerer> {
|
|||
insertunitScraper,
|
||||
soaperTvScraper,
|
||||
autoembedScraper,
|
||||
myanimeScraper,
|
||||
tugaflixScraper,
|
||||
ee3Scraper,
|
||||
fsharetvScraper,
|
||||
|
|
@ -187,5 +191,7 @@ export function gatherAllEmbeds(): Array<Embed> {
|
|||
vidnestAllmoviesEmbed,
|
||||
vidnestFlixhqEmbed,
|
||||
vidnestOfficialEmbed,
|
||||
myanimesubScraper,
|
||||
myanimedubScraper,
|
||||
];
|
||||
}
|
||||
|
|
|
|||
61
src/providers/embeds/myanimedub.ts
Normal file
61
src/providers/embeds/myanimedub.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import { flags } from '@/entrypoint/utils/targets';
|
||||
import { makeEmbed } from '@/providers/base';
|
||||
import { Caption, getCaptionTypeFromUrl, labelToLanguageCode } from '@/providers/captions';
|
||||
import { NotFoundError } from '@/utils/errors';
|
||||
import { createM3U8ProxyUrl } from '@/utils/proxy';
|
||||
|
||||
export const myanimedubScraper = makeEmbed({
|
||||
id: 'myanimedub',
|
||||
name: 'MyAnime (Dub)',
|
||||
rank: 205,
|
||||
async scrape(ctx) {
|
||||
const streamData = await ctx.proxiedFetcher<any>(
|
||||
`https://anime.aether.mom/api/stream?id=${ctx.url}&server=HD-2&type=dub`,
|
||||
);
|
||||
|
||||
if (!streamData.results.streamingLink?.link?.file) {
|
||||
throw new NotFoundError('No watchable sources found');
|
||||
}
|
||||
|
||||
const getValidTimestamp = (timestamp: any) => {
|
||||
if (!timestamp || typeof timestamp !== 'object') return null;
|
||||
const start = parseInt(timestamp.start, 10);
|
||||
const end = parseInt(timestamp.end, 10);
|
||||
if (Number.isNaN(start) || Number.isNaN(end) || start <= 0 || end <= 0 || start >= end) return null;
|
||||
return { start, end };
|
||||
};
|
||||
|
||||
const intro = getValidTimestamp(streamData.results.streamingLink.intro);
|
||||
const outro = getValidTimestamp(streamData.results.streamingLink.outro);
|
||||
|
||||
return {
|
||||
stream: [
|
||||
{
|
||||
id: 'dub',
|
||||
type: 'hls',
|
||||
playlist: createM3U8ProxyUrl(streamData.results.streamingLink.link.file, {
|
||||
Referer: 'https://rapid-cloud.co/',
|
||||
}),
|
||||
flags: [flags.CORS_ALLOWED],
|
||||
captions:
|
||||
(streamData.results.streamingLink.tracks
|
||||
?.map((track: any) => {
|
||||
const lang = labelToLanguageCode(track.label);
|
||||
const type = getCaptionTypeFromUrl(track.file);
|
||||
if (!lang || !type) return null;
|
||||
return {
|
||||
id: track.file,
|
||||
url: track.file,
|
||||
language: lang,
|
||||
type,
|
||||
hasCorsRestrictions: true,
|
||||
};
|
||||
})
|
||||
.filter((x: any) => x) as Caption[]) ?? [],
|
||||
intro,
|
||||
outro,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
});
|
||||
61
src/providers/embeds/myanimesub.ts
Normal file
61
src/providers/embeds/myanimesub.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import { flags } from '@/entrypoint/utils/targets';
|
||||
import { makeEmbed } from '@/providers/base';
|
||||
import { Caption, getCaptionTypeFromUrl, labelToLanguageCode } from '@/providers/captions';
|
||||
import { NotFoundError } from '@/utils/errors';
|
||||
import { createM3U8ProxyUrl } from '@/utils/proxy';
|
||||
|
||||
export const myanimesubScraper = makeEmbed({
|
||||
id: 'myanimesub',
|
||||
name: 'MyAnime (Sub)',
|
||||
rank: 204,
|
||||
async scrape(ctx) {
|
||||
const streamData = await ctx.proxiedFetcher<any>(
|
||||
`https://anime.aether.mom/api/stream?id=${ctx.url}&server=HD-2&type=sub`,
|
||||
);
|
||||
|
||||
if (!streamData.results.streamingLink?.link?.file) {
|
||||
throw new NotFoundError('No watchable sources found');
|
||||
}
|
||||
|
||||
const getValidTimestamp = (timestamp: any) => {
|
||||
if (!timestamp || typeof timestamp !== 'object') return null;
|
||||
const start = parseInt(timestamp.start, 10);
|
||||
const end = parseInt(timestamp.end, 10);
|
||||
if (Number.isNaN(start) || Number.isNaN(end) || start <= 0 || end <= 0 || start >= end) return null;
|
||||
return { start, end };
|
||||
};
|
||||
|
||||
const intro = getValidTimestamp(streamData.results.streamingLink.intro);
|
||||
const outro = getValidTimestamp(streamData.results.streamingLink.outro);
|
||||
|
||||
return {
|
||||
stream: [
|
||||
{
|
||||
id: 'sub',
|
||||
type: 'hls',
|
||||
playlist: createM3U8ProxyUrl(streamData.results.streamingLink.link.file, {
|
||||
Referer: 'https://rapid-cloud.co/',
|
||||
}),
|
||||
flags: [flags.CORS_ALLOWED],
|
||||
captions:
|
||||
(streamData.results.streamingLink.tracks
|
||||
?.map((track: any) => {
|
||||
const lang = labelToLanguageCode(track.label);
|
||||
const type = getCaptionTypeFromUrl(track.file);
|
||||
if (!lang || !type) return null;
|
||||
return {
|
||||
id: track.file,
|
||||
url: track.file,
|
||||
language: lang,
|
||||
type,
|
||||
hasCorsRestrictions: true,
|
||||
};
|
||||
})
|
||||
.filter((x: any) => x) as Caption[]) ?? [],
|
||||
intro,
|
||||
outro,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
});
|
||||
77
src/providers/sources/myanime.ts
Normal file
77
src/providers/sources/myanime.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { flags } from '@/entrypoint/utils/targets';
|
||||
import { SourcererOutput, makeSourcerer } from '@/providers/base';
|
||||
import { compareTitle } from '@/utils/compare';
|
||||
import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
|
||||
import { NotFoundError } from '@/utils/errors';
|
||||
|
||||
// Levenshtein distance function for string similarity
|
||||
const levenshtein = (s: string, t: string): number => {
|
||||
if (!s.length) return t.length;
|
||||
if (!t.length) return s.length;
|
||||
const arr: number[][] = [];
|
||||
for (let i = 0; i <= t.length; i++) {
|
||||
arr[i] = [i];
|
||||
for (let j = 1; j <= s.length; j++) {
|
||||
arr[i][j] =
|
||||
i === 0
|
||||
? j
|
||||
: Math.min(arr[i - 1][j] + 1, arr[i][j - 1] + 1, arr[i - 1][j - 1] + (s[j - 1] === t[i - 1] ? 0 : 1));
|
||||
}
|
||||
}
|
||||
return arr[t.length][s.length];
|
||||
};
|
||||
|
||||
const universalScraper = async (ctx: MovieScrapeContext | ShowScrapeContext): Promise<SourcererOutput> => {
|
||||
const searchResults = await ctx.proxiedFetcher<any>(
|
||||
`https://anime-api-cyan-zeta.vercel.app/api/search?keyword=${encodeURIComponent(ctx.media.title)}`,
|
||||
);
|
||||
|
||||
const bestMatch = searchResults.results.data
|
||||
.map((item: any) => {
|
||||
const similarity =
|
||||
1 - levenshtein(item.title, ctx.media.title) / Math.max(item.title.length, ctx.media.title.length);
|
||||
const isExactMatch = compareTitle(item.title, ctx.media.title);
|
||||
return { ...item, similarity, isExactMatch };
|
||||
})
|
||||
.sort((a: any, b: any) => {
|
||||
if (a.isExactMatch && !b.isExactMatch) return -1;
|
||||
if (!a.isExactMatch && b.isExactMatch) return 1;
|
||||
return b.similarity - a.similarity;
|
||||
})[0];
|
||||
|
||||
if (!bestMatch) {
|
||||
throw new NotFoundError('No watchable sources found');
|
||||
}
|
||||
|
||||
const episodeData = await ctx.proxiedFetcher<any>(`https://anime.aether.mom/api/episodes/${bestMatch.id}`);
|
||||
|
||||
const episode = episodeData.results.episodes.find(
|
||||
(e: any) => e.episode_no === (ctx.media.type === 'show' ? ctx.media.episode.number : 1),
|
||||
);
|
||||
|
||||
if (!episode) {
|
||||
throw new NotFoundError('No watchable sources found');
|
||||
}
|
||||
|
||||
return {
|
||||
embeds: [
|
||||
{
|
||||
embedId: 'myanimesub',
|
||||
url: episode.id,
|
||||
},
|
||||
{
|
||||
embedId: 'myanimedub',
|
||||
url: episode.id,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
export const myanimeScraper = makeSourcerer({
|
||||
id: 'myanime',
|
||||
name: 'MyAnime',
|
||||
rank: 101,
|
||||
flags: [flags.CORS_ALLOWED],
|
||||
scrapeMovie: universalScraper,
|
||||
scrapeShow: universalScraper,
|
||||
});
|
||||
Loading…
Reference in a new issue