add myanime

This commit is contained in:
Aykhan 2025-09-03 18:07:22 +04:00
parent 8b667bd776
commit 9f22349271
4 changed files with 205 additions and 0 deletions

View file

@ -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,
];
}

View 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,
},
],
};
},
});

View 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,
},
],
};
},
});

View 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,
});