removed AnimeOnegai

This commit is contained in:
stratumadev 2025-09-30 23:39:47 +02:00
parent 7e680f1117
commit 3fb4392b39
26 changed files with 81 additions and 2013 deletions

View file

@ -50,7 +50,6 @@ body:
- Crunchyroll - Crunchyroll
- Hidive - Hidive
- AnimationDigitalNetwork - AnimationDigitalNetwork
- AnimeOnegai
- All - All
- Irrelevant - Irrelevant
validations: validations:

View file

@ -1,88 +0,0 @@
export interface AnimeOnegaiSearch {
text: string;
list: AOSearchResult[];
}
export interface AOSearchResult {
/**
* Asset ID
*/
ID: number;
CreatedAt: Date;
UpdatedAt: Date;
DeletedAt: null;
title: string;
active: boolean;
excerpt: string;
description: string;
bg: string;
poster: string;
entry: string;
code_name: string;
/**
* The Video ID required to get the streams
*/
video_entry: string;
trailer: string;
year: number;
/**
* Asset Type, Known Possibilities
* * 1 - Video
* * 2 - Series
*/
asset_type: 1 | 2;
status: number;
permalink: string;
duration: string;
subtitles: boolean;
price: number;
rent_price: number;
rating: number;
color: number | null;
classification: number;
brazil_classification: null | string;
likes: number;
views: number;
button: string;
stream_url: string;
stream_url_backup: string;
copyright: null | string;
skip_intro: null | string;
ending: null | string;
bumper_intro: string;
ads: string;
age_restriction: boolean | null;
epg: null;
allow_languages: string[] | null;
allow_countries: string[] | null;
classification_text: string;
locked: boolean;
resign: boolean;
favorite: boolean;
actors_list: null;
voiceactors_list: null;
artdirectors_list: null;
audios_list: null;
awards_list: null;
companies_list: null;
countries_list: null;
directors_list: null;
edition_list: null;
genres_list: null;
music_list: null;
photograpy_list: null;
producer_list: null;
screenwriter_list: null;
season_list: null;
tags_list: null;
chapter_id: number;
chapter_entry: string;
chapter_poster: string;
progress_time: number;
progress_percent: number;
included_subscription: number;
paid_content: number;
rent_content: number;
objectID: string;
lang: string;
}

View file

@ -1,36 +0,0 @@
export interface AnimeOnegaiSeasons {
ID: number;
CreatedAt: Date;
UpdatedAt: Date;
DeletedAt: null;
name: string;
number: number;
asset_id: number;
entry: string;
description: string;
active: boolean;
allow_languages: string[];
allow_countries: string[];
list: Episode[];
}
export interface Episode {
ID: number;
CreatedAt: Date;
UpdatedAt: Date;
DeletedAt: null;
name: string;
number: number;
description: string;
thumbnail: string;
entry: string;
video_entry: string;
active: boolean;
season_id: number;
stream_url: string;
skip_intro: null;
ending: null;
open_free: boolean;
asset_id: number;
age_restriction: boolean;
}

View file

@ -1,111 +0,0 @@
export interface AnimeOnegaiSeries {
ID: number;
CreatedAt: Date;
UpdatedAt: Date;
DeletedAt: null;
title: string;
active: boolean;
excerpt: string;
description: string;
bg: string;
poster: string;
entry: string;
code_name: string;
/**
* The Video ID required to get the streams
*/
video_entry: string;
trailer: string;
year: number;
asset_type: number;
status: number;
permalink: string;
duration: string;
subtitles: boolean;
price: number;
rent_price: number;
rating: number;
color: number;
classification: number;
brazil_classification: string;
likes: number;
views: number;
button: string;
stream_url: string;
stream_url_backup: string;
copyright: string;
skip_intro: null;
ending: null;
bumper_intro: string;
ads: string;
age_restriction: boolean;
epg: null;
allow_languages: string[];
allow_countries: string[];
classification_text: string;
locked: boolean;
resign: boolean;
favorite: boolean;
actors_list: CtorsList[];
voiceactors_list: CtorsList[];
artdirectors_list: any[];
audios_list: SList[];
awards_list: any[];
companies_list: any[];
countries_list: any[];
directors_list: CtorsList[];
edition_list: any[];
genres_list: SList[];
music_list: any[];
photograpy_list: any[];
producer_list: any[];
screenwriter_list: any[];
season_list: any[];
tags_list: TagsList[];
chapter_id: number;
chapter_entry: string;
chapter_poster: string;
progress_time: number;
progress_percent: number;
included_subscription: number;
paid_content: number;
rent_content: number;
objectID: string;
lang: string;
}
export interface CtorsList {
ID: number;
CreatedAt: Date;
UpdatedAt: Date;
DeletedAt: null;
name: string;
Permalink?: string;
country: number | null;
year: number | null;
death: number | null;
image: string;
genre: null;
description: string;
permalink?: string;
background?: string;
}
export interface SList {
ID: number;
CreatedAt: Date;
UpdatedAt: Date;
DeletedAt: null;
name: string;
age_restriction?: number;
}
export interface TagsList {
ID: number;
CreatedAt: Date;
UpdatedAt: Date;
DeletedAt: null;
name: string;
position: number;
status: boolean;
}

View file

@ -1,41 +0,0 @@
export interface AnimeOnegaiStream {
ID: number;
CreatedAt: Date;
UpdatedAt: Date;
DeletedAt: null;
name: string;
source_url: string;
backup_url: string;
live: boolean;
token_handler: number;
entry: string;
job: string;
drm: boolean;
transcoding_content_id: string;
transcoding_asset_id: string;
status: number;
thumbnail: string;
hls: string;
dash: string;
widevine_proxy: string;
playready_proxy: string;
apple_licence: string;
apple_certificate: string;
dpath: string;
dbin: string;
subtitles: Subtitle[];
origin: number;
offline_entry: string;
offline_status: boolean;
}
export interface Subtitle {
ID: number;
CreatedAt: Date;
UpdatedAt: Date;
DeletedAt: null;
name: string;
lang: string;
entry_id: string;
url: string;
}

4
@types/ws.d.ts vendored
View file

@ -30,8 +30,8 @@ export type MessageTypes = {
isDownloading: [undefined, boolean]; isDownloading: [undefined, boolean];
openFolder: [FolderTypes, undefined]; openFolder: [FolderTypes, undefined];
changeProvider: [undefined, boolean]; changeProvider: [undefined, boolean];
type: [undefined, 'crunchy' | 'hidive' | 'ao' | 'adn' | undefined]; type: [undefined, 'crunchy' | 'hidive' | 'adn' | undefined];
setup: ['crunchy' | 'hidive' | 'ao' | 'adn' | undefined, undefined]; setup: ['crunchy' | 'hidive' | 'adn' | undefined, undefined];
openFile: [[FolderTypes, string], undefined]; openFile: [[FolderTypes, string], undefined];
openURL: [string, undefined]; openURL: [string, undefined];
isSetup: [undefined, boolean]; isSetup: [undefined, boolean];

908
ao.ts
View file

@ -1,908 +0,0 @@
// Package Info
import packageJson from './package.json';
// Node
import path from 'path';
import fs from 'fs-extra';
// Modules
import * as fontsData from './modules/module.fontsData';
import * as langsData from './modules/module.langsData';
import * as yamlCfg from './modules/module.cfg-loader';
import * as yargs from './modules/module.app-args';
import * as reqModule from './modules/module.fetch';
import Merger, { Font, MergerInput, SubtitleInput } from './modules/module.merger';
import { canDecrypt, getKeysWVD, cdm, getKeysPRD } from './modules/cdm';
import streamdl, { M3U8Json } from './modules/hls-download';
import { console } from './modules/log';
import { domain } from './modules/module.api-urls';
import { downloaded } from './modules/module.downloadArchive';
import parseSelect from './modules/module.parseSelect';
import parseFileName, { Variable } from './modules/module.filename';
import { AvailableFilenameVars } from './modules/module.args';
import { parse } from './modules/module.transform-mpd';
import Helper from './modules/module.helper';
// Types
import { ServiceClass } from './@types/serviceClassInterface';
import { AuthData, AuthResponse, SearchData, SearchResponse, SearchResponseItem } from './@types/messageHandler';
import { AOSearchResult, AnimeOnegaiSearch } from './@types/animeOnegaiSearch';
import { AnimeOnegaiSeries } from './@types/animeOnegaiSeries';
import { AnimeOnegaiSeasons, Episode } from './@types/animeOnegaiSeasons';
import { DownloadedMedia } from './@types/hidiveTypes';
import { AnimeOnegaiStream } from './@types/animeOnegaiStream';
import { sxItem } from './crunchy';
type parsedMultiDubDownload = {
data: {
lang: string;
videoId: string;
episode: Episode;
}[];
seriesTitle: string;
seasonTitle: string;
episodeTitle: string;
episodeNumber: number;
seasonNumber: number;
seriesID: number;
seasonID: number;
image: string;
};
export default class AnimeOnegai implements ServiceClass {
public cfg: yamlCfg.ConfigObject;
private token: Record<string, any>;
private req: reqModule.Req;
public locale: string;
public jpnStrings: string[] = [
'japonés con subtítulos en español',
'japonés con subtítulos en portugués',
'japonês com legendas em espanhol',
'japonês com legendas em português',
'japonés'
];
public spaStrings: string[] = ['doblaje en español', 'dublagem em espanhol', 'español'];
public porStrings: string[] = ['doblaje en portugués', 'dublagem em português'];
private defaultOptions: RequestInit = {
headers: {
origin: 'https://www.animeonegai.com',
referer: 'https://www.animeonegai.com/'
}
};
constructor(private debug = false) {
this.cfg = yamlCfg.loadCfg();
this.token = yamlCfg.loadAOToken();
this.req = new reqModule.Req(domain, debug, false, 'ao');
this.locale = 'es';
}
public async cli() {
console.info(`\n=== Multi Downloader NX ${packageJson.version} ===\n`);
const argv = yargs.appArgv(this.cfg.cli);
if (['pt', 'es'].includes(argv.locale)) this.locale = argv.locale;
if (argv.debug) this.debug = true;
// load binaries
this.cfg.bin = await yamlCfg.loadBinCfg();
if (argv.allDubs) {
argv.dubLang = langsData.dubLanguageCodes;
}
if (argv.auth) {
//Authenticate
await this.doAuth({
username: argv.username ?? (await Helper.question('[Q] LOGIN/EMAIL: ')),
password: argv.password ?? (await Helper.question('[Q] PASSWORD: '))
});
} else if (argv.search && argv.search.length > 2) {
//Search
await this.doSearch({ ...argv, search: argv.search as string });
} else if (argv.s && !isNaN(parseInt(argv.s, 10)) && parseInt(argv.s, 10) > 0) {
const selected = await this.selectShow(parseInt(argv.s), argv.e, argv.but, argv.all, argv);
if (selected.isOk) {
for (const select of selected.value) {
if (!(await this.downloadEpisode(select, { ...argv, skipsubs: false }))) {
console.error(`Unable to download selected episode ${select.episodeNumber}`);
return false;
}
}
}
return true;
} else if (argv.token) {
this.token = { token: argv.token };
yamlCfg.saveAOToken(this.token);
console.info('Saved token');
} else {
console.info('No option selected or invalid value entered. Try --help.');
}
}
public async doSearch(data: SearchData): Promise<SearchResponse> {
const searchReq = await this.req.getData(`https://api.animeonegai.com/v1/search/algolia/${encodeURIComponent(data.search)}?lang=${this.locale}`, this.defaultOptions);
if (!searchReq.ok || !searchReq.res) {
console.error('Search FAILED!');
return { isOk: false, reason: new Error('Search failed. No more information provided') };
}
const searchData = (await searchReq.res.json()) as AnimeOnegaiSearch;
const searchItems: AOSearchResult[] = [];
console.info('Search Results:');
for (const hit of searchData.list) {
searchItems.push(hit);
let fullType: string;
if (hit.asset_type == 2) {
fullType = `S.${hit.ID}`;
} else if (hit.asset_type == 1) {
fullType = `E.${hit.ID}`;
} else {
fullType = 'Unknown';
console.warn(`Unknown asset type ${hit.asset_type}, please report this.`);
}
console.log(`[${fullType}] ${hit.title}`);
}
return {
isOk: true,
value: searchItems
.filter((a) => a.asset_type == 2)
.flatMap((a): SearchResponseItem => {
return {
id: a.ID + '',
image: a.poster ?? '/notFound.png',
name: a.title,
rating: a.likes,
desc: a.description
};
})
};
}
public async doAuth(data: AuthData): Promise<AuthResponse> {
data;
console.error(
'Authentication not possible, manual authentication required due to recaptcha. In order to login use the --token flag. You can get the token by logging into the website, and opening the dev console and running the command "localStorage.ott_token"'
);
return { isOk: false, reason: new Error('Authentication not possible, manual authentication required do to recaptcha.') };
}
public async getShow(id: number) {
const getSeriesData = await this.req.getData(`https://api.animeonegai.com/v1/asset/${id}?lang=${this.locale}`, this.defaultOptions);
if (!getSeriesData.ok || !getSeriesData.res) {
console.error('Failed to get Show Data');
return { isOk: false };
}
const seriesData = (await getSeriesData.res.json()) as AnimeOnegaiSeries;
const getSeasonData = await this.req.getData(`https://api.animeonegai.com/v1/asset/content/${id}?lang=${this.locale}`, this.defaultOptions);
if (!getSeasonData.ok || !getSeasonData.res) {
console.error('Failed to get Show Data');
return { isOk: false };
}
const seasonData = (await getSeasonData.res.json()) as AnimeOnegaiSeasons[];
return { isOk: true, data: seriesData, seasons: seasonData };
}
public async listShow(id: number, outputEpisode: boolean = true) {
const series = await this.getShow(id);
if (!series.isOk || !series.data) {
console.error('Failed to list series data: Failed to get series');
return { isOk: false };
}
console.info(`[S.${series.data.ID}] ${series.data.title} (${series.seasons.length} Seasons)`);
if (series.seasons.length === 0 && series.data.asset_type !== 1) {
console.info(' No Seasons found!');
return { isOk: false };
}
const episodes: { [key: string]: (Episode & { lang?: string })[] } = {};
for (const season of series.seasons) {
let lang: string | undefined = undefined;
if (this.jpnStrings.includes(season.name.trim().toLowerCase())) lang = 'ja';
else if (this.porStrings.includes(season.name.trim().toLowerCase())) lang = 'pt';
else if (this.spaStrings.includes(season.name.trim().toLowerCase())) lang = 'es';
else {
lang = 'unknown';
console.error(`Language (${season.name.trim()}) not known, please report this!`);
}
for (const episode of season.list) {
if (!episodes[episode.number]) {
episodes[episode.number] = [];
}
/*if (!episodes[episode.number].find(a=>a.lang == lang))*/ episodes[episode.number].push({ ...episode, lang });
}
}
//Item is movie, lets define it manually
if (series.data.asset_type === 1 && series.seasons.length === 0) {
let lang: string | undefined;
if (this.jpnStrings.some((str) => series.data.title.includes(str.toLowerCase()))) lang = 'ja';
else if (this.porStrings.some((str) => series.data.title.includes(str.toLowerCase()))) lang = 'pt';
else if (this.spaStrings.some((str) => series.data.title.includes(str.toLowerCase()))) lang = 'es';
else {
lang = 'unknown';
console.error('Language could not be parsed from movie title, please report this!');
}
episodes[1] = [
{
video_entry: series.data.video_entry,
number: 1,
season_id: 1,
name: series.data.title,
ID: series.data.ID,
CreatedAt: series.data.CreatedAt,
DeletedAt: series.data.DeletedAt,
UpdatedAt: series.data.UpdatedAt,
active: series.data.active,
description: series.data.description,
age_restriction: series.data.age_restriction,
asset_id: series.data.ID,
ending: null,
entry: series.data.entry,
stream_url: series.data.stream_url,
skip_intro: null,
thumbnail: series.data.bg,
open_free: false,
lang
}
]; // as unknown as (Episode & { lang?: string })[];
// The above needs to be uncommented if the episode number should be M1 instead of 1
}
//Enable to output episodes seperate from selection
if (outputEpisode) {
for (const episodeKey in episodes) {
const episode = episodes[episodeKey][0];
const langs = Array.from(new Set(episodes[episodeKey].map((a) => a.lang)));
console.info(
` [E.${episode.ID}] E${episode.number} - ${episode.name} (${langs
.map((a) => {
if (a) return langsData.languages.find((b) => b.ao_locale === a)?.name;
return 'Unknown';
})
.join(', ')})`
);
}
}
return { isOk: true, value: episodes, series: series };
}
public async selectShow(id: number, e: string | undefined, but: boolean, all: boolean, options: yargs.ArgvType) {
const getShowData = await this.listShow(id, false);
if (!getShowData.isOk || !getShowData.value) {
return { isOk: false, value: [] };
}
//const showData = getShowData.value;
const doEpsFilter = parseSelect(e as string);
// build selected episodes
const selEpsArr: parsedMultiDubDownload[] = [];
const episodes = getShowData.value;
const seasonNumberTitleParse = getShowData.series.data.title.match(/\d+/);
const seasonNumber = seasonNumberTitleParse ? parseInt(seasonNumberTitleParse[0]) : 1;
for (const episodeKey in getShowData.value) {
const episode = episodes[episodeKey][0];
const selectedLangs: string[] = [];
const selected: {
lang: string;
videoId: string;
episode: Episode;
}[] = [];
for (const episode of episodes[episodeKey]) {
const lang = langsData.languages.find((a) => a.ao_locale === episode.lang);
let isSelected = false;
if (typeof selected.find((a) => a.lang == episode.lang) == 'undefined') {
if (options.dubLang.includes(lang?.code ?? 'Unknown')) {
if (
(but && !doEpsFilter.isSelected([episode.number + '', episode.ID + ''])) ||
all ||
(!but && doEpsFilter.isSelected([episode.number + '', episode.ID + '']))
) {
isSelected = true;
selected.push({ lang: episode.lang as string, videoId: episode.video_entry, episode: episode });
}
}
const selectedLang = isSelected ? `${lang?.name ?? 'Unknown'}` : `${lang?.name ?? 'Unknown'}`;
if (!selectedLangs.includes(selectedLang)) {
selectedLangs.push(selectedLang);
}
}
}
if (selected.length > 0) {
selEpsArr.push({
data: selected,
seasonNumber: seasonNumber,
episodeNumber: episode.number,
episodeTitle: episode.name,
image: episode.thumbnail,
seasonID: episode.season_id,
seasonTitle: getShowData.series.data.title,
seriesTitle: getShowData.series.data.title,
seriesID: getShowData.series.data.ID
});
}
console.info(` [S${seasonNumber}E${episode.number}] - ${episode.name} (${selectedLangs.join(', ')})`);
}
return { isOk: true, value: selEpsArr, showData: getShowData.series };
}
public async downloadEpisode(data: parsedMultiDubDownload, options: yargs.ArgvType): Promise<boolean> {
const res = await this.downloadMediaList(data, options);
if (res === undefined || res.error) {
return false;
} else {
if (!options.skipmux) {
await this.muxStreams(res.data, { ...options, output: res.fileName });
} else {
console.info('Skipping mux');
}
downloaded(
{
service: 'ao',
type: 's'
},
data.seasonID + '',
[data.episodeNumber + '']
);
}
return true;
}
public async muxStreams(data: DownloadedMedia[], options: yargs.ArgvType) {
this.cfg.bin = await yamlCfg.loadBinCfg();
let hasAudioStreams = false;
if (options.novids || data.filter((a) => a.type === 'Video').length === 0) return console.info('Skip muxing since no vids are downloaded');
if (data.some((a) => a.type === 'Audio')) {
hasAudioStreams = true;
}
const merger = new Merger({
onlyVid: hasAudioStreams
? data
.filter((a) => a.type === 'Video')
.map((a): MergerInput => {
if (a.type === 'Subtitle') throw new Error('Never');
return {
lang: a.lang,
path: a.path
};
})
: [],
skipSubMux: options.skipSubMux,
inverseTrackOrder: false,
keepAllVideos: options.keepAllVideos,
onlyAudio: hasAudioStreams
? data
.filter((a) => a.type === 'Audio')
.map((a): MergerInput => {
if (a.type === 'Subtitle') throw new Error('Never');
return {
lang: a.lang,
path: a.path
};
})
: [],
output: `${options.output}.${options.mp4 ? 'mp4' : 'mkv'}`,
subtitles: data
.filter((a) => a.type === 'Subtitle')
.map((a): SubtitleInput => {
if (a.type === 'Video') throw new Error('Never');
if (a.type === 'Audio') throw new Error('Never');
return {
file: a.path,
language: a.language,
closedCaption: a.cc
};
}),
simul: data
.filter((a) => a.type === 'Video')
.map((a): boolean => {
if (a.type === 'Subtitle') throw new Error('Never');
return !a.uncut as boolean;
})[0],
fonts: Merger.makeFontsList(this.cfg.dir.fonts, data.filter((a) => a.type === 'Subtitle') as sxItem[]),
videoAndAudio: hasAudioStreams
? []
: data
.filter((a) => a.type === 'Video')
.map((a): MergerInput => {
if (a.type === 'Subtitle') throw new Error('Never');
return {
lang: a.lang,
path: a.path
};
}),
videoTitle: options.videoTitle,
options: {
ffmpeg: options.ffmpegOptions,
mkvmerge: options.mkvmergeOptions
},
defaults: {
audio: options.defaultAudio,
sub: options.defaultSub
},
ccTag: options.ccTag
});
const bin = Merger.checkMerger(this.cfg.bin, options.mp4, options.forceMuxer);
// collect fonts info
// mergers
let isMuxed = false;
if (options.syncTiming) {
await merger.createDelays();
}
if (bin.MKVmerge) {
await merger.merge('mkvmerge', bin.MKVmerge);
isMuxed = true;
} else if (bin.FFmpeg) {
await merger.merge('ffmpeg', bin.FFmpeg);
isMuxed = true;
} else {
console.info('\nDone!\n');
return;
}
if (isMuxed && !options.nocleanup) merger.cleanUp();
}
public async downloadMediaList(
medias: parsedMultiDubDownload,
options: yargs.ArgvType
): Promise<
| {
data: DownloadedMedia[];
fileName: string;
error: boolean;
}
| undefined
> {
if (!this.token.token) {
console.error('Authentication required!');
return;
}
if (!this.cfg.bin.ffmpeg) this.cfg.bin = await yamlCfg.loadBinCfg();
let mediaName = '...';
let fileName;
const variables: Variable[] = [];
if (medias.seasonTitle && medias.episodeNumber && medias.episodeTitle) {
mediaName = `${medias.seasonTitle} - ${medias.episodeNumber} - ${medias.episodeTitle}`;
}
const files: DownloadedMedia[] = [];
let subIndex = 0;
let dlFailed = false;
let dlVideoOnce = false; // Variable to save if best selected video quality was downloaded
for (const media of medias.data) {
console.info(`Requesting: [E.${media.episode.ID}] ${mediaName}`);
const AuthHeaders = {
headers: {
Authorization: `Bearer ${this.token.token}`,
Referer: 'https://www.animeonegai.com/',
Origin: 'https://www.animeonegai.com',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Content-Type': 'application/json'
}
};
const playbackReq = await this.req.getData(`https://api.animeonegai.com/v1/media/${media.videoId}?lang=${this.locale}`, AuthHeaders);
if (!playbackReq.ok || !playbackReq.res) {
console.error('Request Stream URLs FAILED!');
return undefined;
}
const streamData = (await playbackReq.res.json()) as AnimeOnegaiStream;
variables.push(
...(
[
['title', medias.episodeTitle, true],
['episode', medias.episodeNumber, false],
['service', 'AO', false],
['seriesTitle', medias.seriesTitle, true],
['showTitle', medias.seasonTitle, true],
['season', medias.seasonNumber, false]
] as [AvailableFilenameVars, string | number, boolean][]
).map((a): Variable => {
return {
name: a[0],
replaceWith: a[1],
type: typeof a[1],
sanitize: a[2]
} as Variable;
})
);
if (!canDecrypt) {
console.error('No valid Widevine or PlayReady CDM detected. Please ensure a supported and functional CDM is installed.');
return undefined;
}
if (!this.cfg.bin.mp4decrypt && !this.cfg.bin.shaka) {
console.error('Neither Shaka nor MP4Decrypt found. Please ensure at least one of them is installed.');
return undefined;
}
const lang = langsData.languages.find((a) => a.ao_locale == media.lang) as langsData.LanguageItem;
if (!lang) {
console.error(`Unable to find language for code ${media.lang}`);
return;
}
let tsFile = undefined;
if (!streamData.dash) {
console.error("You don't have access to download this content");
continue;
}
console.info('Playlists URL: %s', streamData.dash);
if (!dlFailed && !(options.novids && options.noaudio)) {
const streamPlaylistsReq = await this.req.getData(streamData.dash, AuthHeaders);
if (!streamPlaylistsReq.ok || !streamPlaylistsReq.res) {
console.error("CAN'T FETCH VIDEO PLAYLISTS!");
dlFailed = true;
} else {
const streamPlaylistBody = (await streamPlaylistsReq.res.text()).replace(
/<BaseURL>(.*?)<\/BaseURL>/g,
`<BaseURL>${streamData.dash.split('/dash/')[0]}/dash/$1</BaseURL>`
);
//Parse MPD Playlists
const streamPlaylists = await parse(streamPlaylistBody, lang as langsData.LanguageItem, streamData.dash.split('/dash/')[0] + '/dash/');
//Get name of CDNs/Servers
const streamServers = Object.keys(streamPlaylists);
options.x = options.x > streamServers.length ? 1 : options.x;
const selectedServer = streamServers[options.x - 1];
const selectedList = streamPlaylists[selectedServer];
//set Video Qualities
const videos = selectedList.video.map((item) => {
return {
...item,
resolutionText: `${item.quality.width}x${item.quality.height} (${Math.round(item.bandwidth / 1024)}KiB/s)`
};
});
const audios = selectedList.audio.map((item) => {
return {
...item,
resolutionText: `${Math.round(item.bandwidth / 1024)}kB/s`
};
});
videos.sort((a, b) => {
return a.quality.width - b.quality.width;
});
audios.sort((a, b) => {
return a.bandwidth - b.bandwidth;
});
let chosenVideoQuality = options.q === 0 ? videos.length : options.q;
if (chosenVideoQuality > videos.length) {
console.warn(
`The requested quality of ${options.q} is greater than the maximum ${videos.length}.\n[WARN] Therefor the maximum will be capped at ${videos.length}.`
);
chosenVideoQuality = videos.length;
}
chosenVideoQuality--;
let chosenAudioQuality = options.q === 0 ? audios.length : options.q;
if (chosenAudioQuality > audios.length) {
chosenAudioQuality = audios.length;
}
chosenAudioQuality--;
const chosenVideoSegments = videos[chosenVideoQuality];
const chosenAudioSegments = audios[chosenAudioQuality];
console.info(`Servers available:\n\t${streamServers.join('\n\t')}`);
console.info(`Available Video Qualities:\n\t${videos.map((a, ind) => `[${ind + 1}] ${a.resolutionText}`).join('\n\t')}`);
console.info(`Available Audio Qualities:\n\t${audios.map((a, ind) => `[${ind + 1}] ${a.resolutionText}`).join('\n\t')}`);
variables.push(
{
name: 'height',
type: 'number',
replaceWith: chosenVideoSegments.quality.height
},
{
name: 'width',
type: 'number',
replaceWith: chosenVideoSegments.quality.width
}
);
console.info(`Selected quality: \n\tVideo: ${chosenVideoSegments.resolutionText}\n\tAudio: ${chosenAudioSegments.resolutionText}\n\tServer: ${selectedServer}`);
//console.info('Stream URL:', chosenVideoSegments.segments[0].uri);
// TODO check filename
fileName = parseFileName(options.fileName, variables, options.numbers, options.override).join(path.sep);
const outFile = parseFileName(options.fileName + '.' + lang.name, variables, options.numbers, options.override).join(path.sep);
const tempFile = parseFileName(`temp-${media.videoId}`, variables, options.numbers, options.override).join(path.sep);
const tempTsFile = path.isAbsolute(tempFile as string) ? tempFile : path.join(this.cfg.dir.content, tempFile);
let [audioDownloaded, videoDownloaded] = [false, false];
// When best selected video quality is already downloaded
if (dlVideoOnce && options.dlVideoOnce) {
console.info('Already downloaded video, skipping video download...');
} else if (options.novids) {
console.info('Skipping video download...');
} else {
//Download Video
const totalParts = chosenVideoSegments.segments.length;
const mathParts = Math.ceil(totalParts / options.partsize);
const mathMsg = `(${mathParts}*${options.partsize})`;
console.info('Total parts in video stream:', totalParts, mathMsg);
tsFile = path.isAbsolute(outFile as string) ? outFile : path.join(this.cfg.dir.content, outFile);
const dirName = path.dirname(tsFile);
if (!fs.existsSync(dirName)) {
fs.mkdirSync(dirName, { recursive: true });
}
const videoJson: M3U8Json = {
segments: chosenVideoSegments.segments
};
try {
const videoDownload = await new streamdl({
output: chosenVideoSegments.pssh_wvd ? `${tempTsFile}.video.enc.m4s` : `${tsFile}.video.m4s`,
timeout: options.timeout,
m3u8json: videoJson,
// baseurl: chunkPlaylist.baseUrl,
threads: options.partsize,
fsRetryTime: options.fsRetryTime * 1000,
override: options.force,
callback: options.callbackMaker
? options.callbackMaker({
fileName: `${path.isAbsolute(outFile) ? outFile.slice(this.cfg.dir.content.length) : outFile}`,
image: medias.image,
parent: {
title: medias.seasonTitle
},
title: medias.episodeTitle,
language: lang
})
: undefined
}).download();
if (!videoDownload.ok) {
console.error(`DL Stats: ${JSON.stringify(videoDownload.parts)}\n`);
dlFailed = true;
} else {
dlVideoOnce = true;
videoDownloaded = true;
}
} catch (e) {
console.error(e);
dlFailed = true;
}
}
if (chosenAudioSegments && !options.noaudio) {
//Download Audio (if available)
const totalParts = chosenAudioSegments.segments.length;
const mathParts = Math.ceil(totalParts / options.partsize);
const mathMsg = `(${mathParts}*${options.partsize})`;
console.info('Total parts in audio stream:', totalParts, mathMsg);
tsFile = path.isAbsolute(outFile as string) ? outFile : path.join(this.cfg.dir.content, outFile);
const dirName = path.dirname(tsFile);
if (!fs.existsSync(dirName)) {
fs.mkdirSync(dirName, { recursive: true });
}
const audioJson: M3U8Json = {
segments: chosenAudioSegments.segments
};
try {
const audioDownload = await new streamdl({
output: chosenAudioSegments.pssh_wvd ? `${tempTsFile}.audio.enc.m4s` : `${tsFile}.audio.m4s`,
timeout: options.timeout,
m3u8json: audioJson,
// baseurl: chunkPlaylist.baseUrl,
threads: options.partsize,
fsRetryTime: options.fsRetryTime * 1000,
override: options.force,
callback: options.callbackMaker
? options.callbackMaker({
fileName: `${path.isAbsolute(outFile) ? outFile.slice(this.cfg.dir.content.length) : outFile}`,
image: medias.image,
parent: {
title: medias.seasonTitle
},
title: medias.episodeTitle,
language: lang
})
: undefined
}).download();
if (!audioDownload.ok) {
console.error(`DL Stats: ${JSON.stringify(audioDownload.parts)}\n`);
dlFailed = true;
} else {
audioDownloaded = true;
}
} catch (e) {
console.error(e);
dlFailed = true;
}
} else if (options.noaudio) {
console.info('Skipping audio download...');
}
//Handle Decryption if needed
if ((chosenVideoSegments.pssh_wvd || chosenAudioSegments.pssh_wvd) && (videoDownloaded || audioDownloaded)) {
console.info('Decryption Needed, attempting to decrypt');
let encryptionKeys;
if (cdm === 'widevine') {
encryptionKeys = await getKeysWVD(chosenVideoSegments.pssh_wvd, streamData.widevine_proxy, {});
}
if (cdm === 'playready') {
encryptionKeys = await getKeysPRD(chosenVideoSegments.pssh_prd, streamData.playready_proxy, {});
}
if (!encryptionKeys || encryptionKeys.length == 0) {
console.error('Failed to get encryption keys');
return undefined;
}
/*const keys = {} as Record<string, string>;
encryptionKeys.forEach(function(key) {
keys[key.kid] = key.key;
});*/
if (this.cfg.bin.mp4decrypt || this.cfg.bin.shaka) {
let commandBase = `--show-progress --key ${encryptionKeys[cdm === 'playready' ? 0 : 1].kid}:${encryptionKeys[cdm === 'playready' ? 0 : 1].key} `;
let commandVideo = commandBase + `"${tempTsFile}.video.enc.m4s" "${tempTsFile}.video.m4s"`;
let commandAudio = commandBase + `"${tempTsFile}.audio.enc.m4s" "${tempTsFile}.audio.m4s"`;
if (this.cfg.bin.shaka) {
commandBase = ` --enable_raw_key_decryption ${encryptionKeys.map((kb) => '--keys key_id=' + kb.kid + ':key=' + kb.key).join(' ')}`;
commandVideo = `input="${tempTsFile}.video.enc.m4s",stream=video,output="${tempTsFile}.video.m4s"` + commandBase;
commandAudio = `input="${tempTsFile}.audio.enc.m4s",stream=audio,output="${tempTsFile}.audio.m4s"` + commandBase;
}
if (videoDownloaded) {
console.info('Started decrypting video,', this.cfg.bin.shaka ? 'using shaka' : 'using mp4decrypt');
const decryptVideo = Helper.exec(
this.cfg.bin.shaka ? 'shaka-packager' : 'mp4decrypt',
this.cfg.bin.shaka ? `"${this.cfg.bin.shaka}"` : `"${this.cfg.bin.mp4decrypt}"`,
commandVideo
);
if (!decryptVideo.isOk) {
console.error(decryptVideo.err);
console.error(`Decryption failed with exit code ${decryptVideo.err.code}`);
if (this.cfg.bin.shaka) {
console.error(`Downgrade to Shaka-Packager v2.6.1 (https://github.com/shaka-project/shaka-packager/releases/tag/v2.6.1) and try again`);
}
fs.renameSync(`${tempTsFile}.video.enc.m4s`, `${tsFile}.video.enc.m4s`);
return undefined;
} else {
console.info('Decryption done for video');
if (!options.nocleanup) {
fs.removeSync(`${tempTsFile}.video.enc.m4s`);
}
fs.copyFileSync(`${tempTsFile}.video.m4s`, `${tsFile}.video.m4s`);
fs.unlinkSync(`${tempTsFile}.video.m4s`);
files.push({
type: 'Video',
path: `${tsFile}.video.m4s`,
lang: lang
});
}
}
if (audioDownloaded) {
console.info('Started decrypting audio,', this.cfg.bin.shaka ? 'using shaka' : 'using mp4decrypt');
const decryptAudio = Helper.exec(
this.cfg.bin.shaka ? 'shaka-packager' : 'mp4decrypt',
this.cfg.bin.shaka ? `"${this.cfg.bin.shaka}"` : `"${this.cfg.bin.mp4decrypt}"`,
commandAudio
);
if (!decryptAudio.isOk) {
console.error(decryptAudio.err);
console.error(`Decryption failed with exit code ${decryptAudio.err.code}`);
if (this.cfg.bin.shaka) {
console.error(`Downgrade to Shaka-Packager v2.6.1 (https://github.com/shaka-project/shaka-packager/releases/tag/v2.6.1) and try again`);
}
fs.renameSync(`${tempTsFile}.audio.enc.m4s`, `${tsFile}.audio.enc.m4s`);
return undefined;
} else {
if (!options.nocleanup) {
fs.removeSync(`${tempTsFile}.audio.enc.m4s`);
}
fs.copyFileSync(`${tempTsFile}.audio.m4s`, `${tsFile}.audio.m4s`);
fs.unlinkSync(`${tempTsFile}.audio.m4s`);
files.push({
type: 'Audio',
path: `${tsFile}.audio.m4s`,
lang: lang
});
console.info('Decryption done for audio');
}
}
} else {
console.warn('mp4decrypt/shaka not found, files need decryption. Decryption Keys:', encryptionKeys);
}
} else {
if (videoDownloaded) {
files.push({
type: 'Video',
path: `${tsFile}.video.m4s`,
lang: lang
});
}
if (audioDownloaded) {
files.push({
type: 'Audio',
path: `${tsFile}.audio.m4s`,
lang: lang
});
}
}
}
} else if (options.novids && options.noaudio) {
fileName = parseFileName(options.fileName, variables, options.numbers, options.override).join(path.sep);
}
if (options.dlsubs.indexOf('all') > -1) {
options.dlsubs = ['all'];
}
if (options.nosubs) {
console.info('Subtitles downloading disabled from nosubs flag.');
options.skipsubs = true;
}
if (!options.skipsubs && options.dlsubs.indexOf('none') == -1) {
if (streamData.subtitles.length > 0) {
for (const sub of streamData.subtitles) {
const subLang = langsData.languages.find((a) => a.ao_locale === sub.lang);
if (!subLang) {
console.warn(`Language not found for subtitle language: ${sub.lang}, Skipping`);
continue;
}
const sxData: Partial<sxItem> = {};
sxData.file = langsData.subsFile(fileName as string, subIndex + '', subLang, false, options.ccTag);
if (path.isAbsolute(sxData.file)) {
sxData.path = sxData.file;
} else {
sxData.path = path.join(this.cfg.dir.content, sxData.file);
}
const dirName = path.dirname(sxData.path);
if (!fs.existsSync(dirName)) {
fs.mkdirSync(dirName, { recursive: true });
}
sxData.language = subLang;
if ((options.dlsubs.includes('all') || options.dlsubs.includes(subLang.locale)) && sub.url.includes('.ass')) {
const getSubtitle = await this.req.getData(sub.url, AuthHeaders);
if (getSubtitle.ok && getSubtitle.res) {
console.info(`Subtitle Downloaded: ${sub.url}`);
const sBody = await getSubtitle.res.text();
sxData.title = `${subLang.language}`;
sxData.fonts = fontsData.assFonts(sBody) as Font[];
fs.writeFileSync(sxData.path, sBody);
files.push({
type: 'Subtitle',
...(sxData as sxItem),
cc: false
});
} else {
console.warn(`Failed to download subtitle: ${sxData.file}`);
}
}
subIndex++;
}
} else {
console.warn("Can't find urls for subtitles!");
}
} else {
console.info('Subtitles downloading skipped!');
}
await this.sleep(options.waittime);
}
return {
error: dlFailed,
data: files,
fileName: fileName ? (path.isAbsolute(fileName) ? fileName : path.join(this.cfg.dir.content, fileName)) || './unknown' : './unknown'
};
}
public sleep(ms: number) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
}

View file

@ -4,7 +4,7 @@ If you find any bugs in this documentation or in the program itself please repor
## Legal Warning ## Legal Warning
This application is not endorsed by or affiliated with *Crunchyroll*, *Hidive*, *AnimeOnegai*, or *AnimationDigitalNetwork*. This application is not endorsed by or affiliated with *Crunchyroll*, *Hidive* or *AnimationDigitalNetwork*.
This application enables you to download videos for offline viewing which may be forbidden by law in your country. This application enables you to download videos for offline viewing which may be forbidden by law in your country.
The usage of this application may also cause a violation of the *Terms of Service* between you and the stream provider. The usage of this application may also cause a violation of the *Terms of Service* between you and the stream provider.
This tool is not responsible for your actions; please make an informed decision before using this application. This tool is not responsible for your actions; please make an informed decision before using this application.

View file

@ -2,11 +2,11 @@
[![Discord Shield](https://discord.com/api/guilds/884479461997805568/widget.png?style=banner2)](https://discord.gg/qEpbWen5vq) [![Discord Shield](https://discord.com/api/guilds/884479461997805568/widget.png?style=banner2)](https://discord.gg/qEpbWen5vq)
This downloader can download anime from different sites. Currently supported are *Crunchyroll*, *Hidive*, *AnimeOnegai*, and *AnimationDigitalNetwork*. This downloader can download anime from different sites. Currently supported are *Crunchyroll*, *Hidive* and *AnimationDigitalNetwork*.
## Legal Warning ## Legal Warning
This application is not endorsed by or affiliated with *Crunchyroll*, *Hidive*, *AnimeOnegai*, or *AnimationDigitalNetwork*. This application enables you to download videos for offline viewing which may be forbidden by law in your country. The usage of this application may also cause a violation of the *Terms of Service* between you and the stream provider. This tool is not responsible for your actions; please make an informed decision before using this application. This application is not endorsed by or affiliated with *Crunchyroll*, *Hidive* or *AnimationDigitalNetwork*. This application enables you to download videos for offline viewing which may be forbidden by law in your country. The usage of this application may also cause a violation of the *Terms of Service* between you and the stream provider. This tool is not responsible for your actions; please make an informed decision before using this application.
## Dependencies ## Dependencies

View file

@ -27,8 +27,6 @@ const MenuBar: React.FC = () => {
return 'Crunchyroll'; return 'Crunchyroll';
case 'hidive': case 'hidive':
return 'Hidive'; return 'Hidive';
case 'ao':
return 'AnimeOnegai';
case 'adn': case 'adn':
return 'AnimationDigitalNetwork'; return 'AnimationDigitalNetwork';
} }

View file

@ -3,7 +3,7 @@ import { Divider, Box, Button, Typography, Avatar } from '@mui/material';
import useStore from '../hooks/useStore'; import useStore from '../hooks/useStore';
import { StoreState } from './Store'; import { StoreState } from './Store';
type Services = 'crunchy' | 'hidive' | 'ao' | 'adn'; type Services = 'crunchy' | 'hidive' | 'adn';
export const serviceContext = React.createContext<Services | undefined>(undefined); export const serviceContext = React.createContext<Services | undefined>(undefined);
@ -39,14 +39,6 @@ const ServiceProvider: FCWithChildren = ({ children }) => {
> >
Hidive Hidive
</Button> </Button>
<Button
size="large"
variant="contained"
onClick={() => setService('ao')}
startIcon={<Avatar src={'https://www.animeonegai.com/assets/img/anime/general/ao3-favicon.png'} />}
>
AnimeOnegai
</Button>
<Button size="large" variant="contained" onClick={() => setService('adn')} startIcon={<Avatar src={'https://animationdigitalnetwork.com/favicon.ico'} />}> <Button size="large" variant="contained" onClick={() => setService('adn')} startIcon={<Avatar src={'https://animationdigitalnetwork.com/favicon.ico'} />}>
AnimationDigitalNetwork AnimationDigitalNetwork
</Button> </Button>

View file

@ -21,7 +21,7 @@ export type DownloadOptions = {
export type StoreState = { export type StoreState = {
episodeListing: Episode[]; episodeListing: Episode[];
downloadOptions: DownloadOptions; downloadOptions: DownloadOptions;
service: 'crunchy' | 'hidive' | 'ao' | 'adn' | undefined; service: 'crunchy' | 'hidive' | 'adn' | undefined;
version: string; version: string;
}; };

View file

@ -5,7 +5,6 @@ import { MessageHandler, GuiState } from '../../@types/messageHandler';
import { setState, getState, writeYamlCfgFile } from '../../modules/module.cfg-loader'; import { setState, getState, writeYamlCfgFile } from '../../modules/module.cfg-loader';
import CrunchyHandler from './services/crunchyroll'; import CrunchyHandler from './services/crunchyroll';
import HidiveHandler from './services/hidive'; import HidiveHandler from './services/hidive';
import AnimeOnegaiHandler from './services/animeonegai';
import ADNHandler from './services/adn'; import ADNHandler from './services/adn';
import WebSocketHandler from './websocket'; import WebSocketHandler from './websocket';
import packageJson from '../../package.json'; import packageJson from '../../package.json';
@ -35,8 +34,6 @@ export default class ServiceHandler {
this.service = new CrunchyHandler(this.ws); this.service = new CrunchyHandler(this.ws);
} else if (data === 'hidive') { } else if (data === 'hidive') {
this.service = new HidiveHandler(this.ws); this.service = new HidiveHandler(this.ws);
} else if (data === 'ao') {
this.service = new AnimeOnegaiHandler(this.ws);
} else if (data === 'adn') { } else if (data === 'adn') {
this.service = new ADNHandler(this.ws); this.service = new ADNHandler(this.ws);
} }
@ -55,7 +52,7 @@ export default class ServiceHandler {
this.ws.events.on('version', async (_, respond) => { this.ws.events.on('version', async (_, respond) => {
respond(packageJson.version); respond(packageJson.version);
}); });
this.ws.events.on('type', async (_, respond) => respond(this.service === undefined ? undefined : (this.service.name as 'hidive' | 'crunchy' | 'ao' | 'adn'))); this.ws.events.on('type', async (_, respond) => respond(this.service === undefined ? undefined : (this.service.name as 'hidive' | 'crunchy' | 'adn')));
this.ws.events.on('checkToken', async (_, respond) => { this.ws.events.on('checkToken', async (_, respond) => {
if (this.service === undefined) return respond({ isOk: false, reason: new Error('No service selected') }); if (this.service === undefined) return respond({ isOk: false, reason: new Error('No service selected') });
respond(await this.service.checkToken()); respond(await this.service.checkToken());

View file

@ -1,170 +0,0 @@
import {
AuthData,
CheckTokenResponse,
DownloadData,
Episode,
EpisodeListResponse,
MessageHandler,
ResolveItemsData,
SearchData,
SearchResponse
} from '../../../@types/messageHandler';
import AnimeOnegai from '../../../ao';
import { getDefault } from '../../../modules/module.args';
import { languages } from '../../../modules/module.langsData';
import WebSocketHandler from '../websocket';
import Base from './base';
import { console } from '../../../modules/log';
import * as yargs from '../../../modules/module.app-args';
class AnimeOnegaiHandler extends Base implements MessageHandler {
private ao: AnimeOnegai;
public name = 'ao';
constructor(ws: WebSocketHandler) {
super(ws);
this.ao = new AnimeOnegai();
this.initState();
this.getDefaults();
}
public getDefaults() {
const _default = yargs.appArgv(this.ao.cfg.cli, true);
if (['es', 'pt'].includes(_default.locale)) this.ao.locale = _default.locale;
}
public async auth(data: AuthData) {
return this.ao.doAuth(data);
}
public async checkToken(): Promise<CheckTokenResponse> {
//TODO: implement proper method to check token
return { isOk: true, value: undefined };
}
public async search(data: SearchData): Promise<SearchResponse> {
console.debug(`Got search options: ${JSON.stringify(data)}`);
const search = await this.ao.doSearch(data);
if (!search.isOk) {
return search;
}
return { isOk: true, value: search.value };
}
public async handleDefault(name: string) {
return getDefault(name, this.ao.cfg.cli);
}
public async availableDubCodes(): Promise<string[]> {
const dubLanguageCodesArray: string[] = [];
for (const language of languages) {
if (language.ao_locale) dubLanguageCodesArray.push(language.code);
}
return [...new Set(dubLanguageCodesArray)];
}
public async availableSubCodes(): Promise<string[]> {
const subLanguageCodesArray: string[] = [];
for (const language of languages) {
if (language.ao_locale) subLanguageCodesArray.push(language.locale);
}
return ['all', 'none', ...new Set(subLanguageCodesArray)];
}
public async resolveItems(data: ResolveItemsData): Promise<boolean> {
const parse = parseInt(data.id);
if (isNaN(parse) || parse <= 0) return false;
console.debug(`Got resolve options: ${JSON.stringify(data)}`);
const _default = yargs.appArgv(this.ao.cfg.cli, true);
const res = await this.ao.selectShow(parseInt(data.id), data.e, data.but, data.all, _default);
if (!res.isOk || !res.value) return res.isOk;
this.addToQueue(
res.value.map((a) => {
return {
...data,
ids: a.data.map((a) => a.videoId),
title: a.episodeTitle,
parent: {
title: a.seasonTitle,
season: a.seasonTitle
},
e: a.episodeNumber + '',
image: a.image,
episode: a.episodeNumber + ''
};
})
);
return true;
}
public async listEpisodes(id: string): Promise<EpisodeListResponse> {
const parse = parseInt(id);
if (isNaN(parse) || parse <= 0) return { isOk: false, reason: new Error('The ID is invalid') };
const request = await this.ao.listShow(parse);
if (!request.isOk || !request.value) return { isOk: false, reason: new Error('Unknown upstream error, check for additional logs') };
const episodes: Episode[] = [];
const seasonNumberTitleParse = request.series.data.title.match(/\d+$/);
const seasonNumber = seasonNumberTitleParse ? parseInt(seasonNumberTitleParse[0]) : 1;
//request.value
for (const episodeKey in request.value) {
const episode = request.value[episodeKey][0];
const langs = Array.from(new Set(request.value[episodeKey].map((a) => a.lang)));
episodes.push({
e: episode.number + '',
lang: langs as string[],
name: episode.name,
season: seasonNumber + '',
seasonTitle: '',
episode: episode.number + '',
id: episode.video_entry + '',
img: episode.thumbnail,
description: episode.description,
time: ''
});
}
return { isOk: true, value: episodes };
}
public async downloadItem(data: DownloadData) {
this.setDownloading(true);
console.debug(`Got download options: ${JSON.stringify(data)}`);
const _default = yargs.appArgv(this.ao.cfg.cli, true);
const res = await this.ao.selectShow(parseInt(data.id), data.e, false, false, {
..._default,
dubLang: data.dubLang,
e: data.e
});
if (res.isOk) {
for (const select of res.value) {
if (
!(await this.ao.downloadEpisode(select, {
..._default,
skipsubs: false,
callbackMaker: this.makeProgressHandler.bind(this),
q: data.q,
fileName: data.fileName,
dlsubs: data.dlsubs,
dlVideoOnce: data.dlVideoOnce,
force: 'y',
novids: data.novids,
noaudio: data.noaudio,
hslang: data.hslang || 'none',
dubLang: data.dubLang
}))
) {
const er = new Error(`Unable to download episode ${data.e} from ${data.id}`);
er.name = 'Download error';
this.alertError(er);
}
}
} else {
this.alertError(new Error('Failed to download episode, check for additional logs.'));
}
this.sendMessage({ name: 'finish', data: undefined });
this.setDownloading(false);
this.onFinish();
}
}
export default AnimeOnegaiHandler;

View file

@ -39,17 +39,6 @@ import update from './modules/module.updater';
(argv.s === undefined ? argv.series : argv.s) as string (argv.s === undefined ? argv.series : argv.s) as string
); );
console.info('Added %s to the downloadArchive list', argv.s === undefined ? argv.series : argv.s); console.info('Added %s to the downloadArchive list', argv.s === undefined ? argv.series : argv.s);
} else if (argv.service === 'ao') {
if (argv.s === undefined) return console.error('`-s` not found');
addToArchive(
{
service: 'hidive',
//type: argv.s === undefined ? 'srz' : 's'
type: 's'
},
(argv.s === undefined ? argv.series : argv.s) as string
);
console.info('Added %s to the downloadArchive list', argv.s === undefined ? argv.series : argv.s);
} }
} else if (argv.downloadArchive) { } else if (argv.downloadArchive) {
const ids = makeCommand(argv.service); const ids = makeCommand(argv.service);
@ -57,7 +46,7 @@ import update from './modules/module.updater';
overrideArguments(cfg.cli, id); overrideArguments(cfg.cli, id);
/* Reimport module to override appArgv */ /* Reimport module to override appArgv */
Object.keys(require.cache).forEach((key) => { Object.keys(require.cache).forEach((key) => {
if (key.endsWith('crunchy.js') || key.endsWith('hidive.js') || key.endsWith('ao.js')) delete require.cache[key]; if (key.endsWith('crunchy.js') || key.endsWith('hidive.js')) delete require.cache[key];
}); });
let service: ServiceClass; let service: ServiceClass;
switch (argv.service) { switch (argv.service) {
@ -67,9 +56,6 @@ import update from './modules/module.updater';
case 'hidive': case 'hidive':
service = new (await import('./hidive')).default(); service = new (await import('./hidive')).default();
break; break;
case 'ao':
service = new (await import('./ao')).default();
break;
case 'adn': case 'adn':
service = new (await import('./adn')).default(); service = new (await import('./adn')).default();
break; break;
@ -88,9 +74,6 @@ import update from './modules/module.updater';
case 'hidive': case 'hidive':
service = new (await import('./hidive')).default(); service = new (await import('./hidive')).default();
break; break;
case 'ao':
service = new (await import('./ao')).default();
break;
case 'adn': case 'adn':
service = new (await import('./adn')).default(); service = new (await import('./adn')).default();
break; break;

View file

@ -3,7 +3,7 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
import { args, groups } from './module.args'; import { args, groups } from './module.args';
const transformService = (str: Array<'crunchy' | 'hidive' | 'ao' | 'adn' | 'all'>) => { const transformService = (str: Array<'crunchy' | 'hidive' | 'adn' | 'all'>) => {
const services: string[] = []; const services: string[] = [];
str.forEach(function (part) { str.forEach(function (part) {
switch (part) { switch (part) {
@ -13,9 +13,6 @@ const transformService = (str: Array<'crunchy' | 'hidive' | 'ao' | 'adn' | 'all'
case 'hidive': case 'hidive':
services.push('Hidive'); services.push('Hidive');
break; break;
case 'ao':
services.push('AnimeOnegai');
break;
case 'adn': case 'adn':
services.push('AnimationDigitalNetwork'); services.push('AnimationDigitalNetwork');
break; break;
@ -33,7 +30,7 @@ If you find any bugs in this documentation or in the program itself please repor
## Legal Warning ## Legal Warning
This application is not endorsed by or affiliated with *Crunchyroll*, *Hidive*, *AnimeOnegai*, or *AnimationDigitalNetwork*. This application is not endorsed by or affiliated with *Crunchyroll*, *Hidive* or *AnimationDigitalNetwork*.
This application enables you to download videos for offline viewing which may be forbidden by law in your country. This application enables you to download videos for offline viewing which may be forbidden by law in your country.
The usage of this application may also cause a violation of the *Terms of Service* between you and the stream provider. The usage of this application may also cause a violation of the *Terms of Service* between you and the stream provider.
This tool is not responsible for your actions; please make an informed decision before using this application. This tool is not responsible for your actions; please make an informed decision before using this application.

View file

@ -1,457 +0,0 @@
// // build-in
// import crypto from 'crypto';
// import fs from 'fs';
// import url from 'url';
// import readline from 'readline/promises';
// import { stdin as input, stdout as output } from 'process';
// // extra
// import got, { Response } from 'got';
// import { console } from './log';
// import { ProgressData } from '../@types/messageHandler';
// import Helper from './module.helper';
// // The following function should fix an issue with downloading. For more information see https://github.com/sindresorhus/got/issues/1489
// const fixMiddleWare = (res: Response) => {
// const isResponseOk = (response: Response) => {
// const {statusCode} = response;
// const limitStatusCode = response.request.options.followRedirect ? 299 : 399;
// return (statusCode >= 200 && statusCode <= limitStatusCode) || statusCode === 304;
// };
// if (isResponseOk(res)) {
// res.request.destroy();
// }
// return res;
// };
// export type HLSCallback = (data: ProgressData) => unknown;
// export type M3U8Json = {
// segments: Record<string, unknown>[],
// mediaSequence?: number,
// }
// type Segment = {
// uri: string
// key: Key,
// byterange?: {
// offset: number,
// length: number
// }
// }
// type Key = {
// uri: string,
// iv: number[]
// }
// export type HLSOptions = {
// m3u8json: M3U8Json,
// output?: string,
// threads?: number,
// retries?: number,
// offset?: number,
// baseurl?: string,
// skipInit?: boolean,
// timeout?: number,
// fsRetryTime?: number,
// override?: 'Y'|'y'|'N'|'n'|'C'|'c'
// callback?: HLSCallback
// }
// type Data = {
// parts: {
// first: number,
// total: number,
// completed: number
// },
// m3u8json: M3U8Json,
// outputFile: string,
// threads: number,
// retries: number,
// offset: number,
// baseurl?: string
// skipInit?: boolean,
// keys: {
// [uri: string]: Buffer|string
// },
// timeout: number,
// checkPartLength: boolean,
// isResume: boolean,
// bytesDownloaded: number,
// waitTime: number,
// callback?: HLSCallback,
// override?: string,
// dateStart: number
// }
// // hls class
// class hlsDownload {
// private data: Data;
// constructor(options: HLSOptions){
// // check playlist
// if(
// !options
// || !options.m3u8json
// || !options.m3u8json.segments
// || options.m3u8json.segments.length === 0
// ){
// throw new Error('Playlist is empty!');
// }
// // init options
// this.data = {
// parts: {
// first: options.m3u8json.mediaSequence || 0,
// total: options.m3u8json.segments.length,
// completed: 0,
// },
// m3u8json: options.m3u8json,
// outputFile: options.output || 'stream.ts',
// threads: options.threads || 5,
// retries: options.retries || 4,
// offset: options.offset || 0,
// baseurl: options.baseurl,
// skipInit: options.skipInit,
// keys: {},
// timeout: options.timeout ? options.timeout : 60 * 1000,
// checkPartLength: true,
// isResume: options.offset ? options.offset > 0 : false,
// bytesDownloaded: 0,
// waitTime: options.fsRetryTime ?? 1000 * 5,
// callback: options.callback,
// override: options.override,
// dateStart: 0
// };
// }
// async download(){
// // set output
// const fn = this.data.outputFile;
// // try load resume file
// if(fs.existsSync(fn) && fs.existsSync(`${fn}.resume`) && this.data.offset < 1){
// try{
// console.info('Resume data found! Trying to resume...');
// const resumeData = JSON.parse(fs.readFileSync(`${fn}.resume`, 'utf-8'));
// if(
// resumeData.total == this.data.m3u8json.segments.length
// && resumeData.completed != resumeData.total
// && !isNaN(resumeData.completed)
// ){
// console.info('Resume data is ok!');
// this.data.offset = resumeData.completed;
// this.data.isResume = true;
// }
// else{
// console.warn(' Resume data is wrong!');
// console.warn({
// resume: { total: resumeData.total, dled: resumeData.completed },
// current: { total: this.data.m3u8json.segments.length },
// });
// }
// }
// catch(e){
// console.error('Resume failed, downloading will be not resumed!');
// console.error(e);
// }
// }
// // ask before rewrite file
// if (fs.existsSync(`${fn}`) && !this.data.isResume) {
// const rl = readline.createInterface({ input, output });
// let rwts = this.data.override ?? await rl.question(`[Q] File «${fn}» already exists! Rewrite? ([y]es/[N]o/[c]ontinue)`);
// rl.close();
// rwts = rwts || 'N';
// if (['Y', 'y'].includes(rwts[0])) {
// console.info(`Deleting «${fn}»...`);
// fs.unlinkSync(fn);
// }
// else if (['C', 'c'].includes(rwts[0])) {
// return { ok: true, parts: this.data.parts };
// }
// else {
// return { ok: false, parts: this.data.parts };
// }
// }
// // show output filename
// if (fs.existsSync(fn) && this.data.isResume) {
// console.info(`Adding content to «${fn}»...`);
// }
// else{
// console.info(`Saving stream to «${fn}»...`);
// }
// // start time
// this.data.dateStart = Date.now();
// let segments = this.data.m3u8json.segments;
// // download init part
// if (segments[0].map && this.data.offset === 0 && !this.data.skipInit) {
// console.info('Download and save init part...');
// const initSeg = segments[0].map as Segment;
// if(segments[0].key){
// initSeg.key = segments[0].key as Key;
// }
// try{
// const initDl = await this.downloadPart(initSeg, 0, 0);
// fs.writeFileSync(fn, initDl.dec, { flag: 'a' });
// fs.writeFileSync(`${fn}.resume`, JSON.stringify({
// completed: 0,
// total: this.data.m3u8json.segments.length
// }));
// console.info('Init part downloaded.');
// }
// catch(e: any){
// console.error(`Part init download error:\n\t${e.message}`);
// return { ok: false, parts: this.data.parts };
// }
// }
// else if(segments[0].map && this.data.offset === 0 && this.data.skipInit){
// console.warn('Skipping init part can lead to broken video!');
// }
// // resuming ...
// if(this.data.offset > 0){
// segments = segments.slice(this.data.offset);
// console.info(`Resuming download from part ${this.data.offset+1}...`);
// this.data.parts.completed = this.data.offset;
// }
// // dl process
// for (let p = 0; p < segments.length / this.data.threads; p++) {
// // set offsets
// const offset = p * this.data.threads;
// const dlOffset = offset + this.data.threads;
// // map download threads
// const krq = new Map(), prq = new Map();
// const res = [];
// let errcnt = 0;
// for (let px = offset; px < dlOffset && px < segments.length; px++){
// const curp = segments[px];
// const key = curp.key as Key;
// if(key && !krq.has(key.uri) && !this.data.keys[key.uri as string]){
// krq.set(key.uri, this.downloadKey(key, px, this.data.offset));
// }
// }
// try {
// await Promise.all(krq.values());
// } catch (er: any) {
// console.error(`Key ${er.p + 1} download error:\n\t${er.message}`);
// return { ok: false, parts: this.data.parts };
// }
// for (let px = offset; px < dlOffset && px < segments.length; px++){
// const curp = segments[px] as Segment;
// prq.set(px, this.downloadPart(curp, px, this.data.offset));
// }
// for (let i = prq.size; i--;) {
// try {
// const r = await Promise.race(prq.values());
// prq.delete(r.p);
// res[r.p - offset] = r.dec;
// }
// catch (error: any) {
// console.error('Part %s download error:\n\t%s',
// error.p + 1 + this.data.offset, error.message);
// prq.delete(error.p);
// errcnt++;
// }
// }
// // catch error
// if (errcnt > 0) {
// console.error(`${errcnt} parts not downloaded`);
// return { ok: false, parts: this.data.parts };
// }
// // write downloaded
// for (const r of res) {
// let error = 0;
// while (error < 3) {
// try {
// fs.writeFileSync(fn, r, { flag: 'a' });
// break;
// } catch (err) {
// console.error(err);
// console.error(`Unable to write to file '${fn}' (Attempt ${error+1}/3)`);
// console.info(`Waiting ${Math.round(this.data.waitTime / 1000)}s before retrying`);
// await new Promise<void>((resolve) => setTimeout(() => resolve(), this.data.waitTime));
// }
// error++;
// }
// if (error === 3) {
// console.error(`Unable to write content to '${fn}'.`);
// return { ok: false, parts: this.data.parts };
// }
// }
// // log downloaded
// const totalSeg = segments.length + this.data.offset; // Add the sliced lenght back so the resume data will be correct even if an resumed download fails
// const downloadedSeg = dlOffset < totalSeg ? dlOffset : totalSeg;
// this.data.parts.completed = downloadedSeg + this.data.offset;
// const data = extFn.getDownloadInfo(
// this.data.dateStart, downloadedSeg, totalSeg,
// this.data.bytesDownloaded
// );
// fs.writeFileSync(`${fn}.resume`, JSON.stringify({
// completed: this.data.parts.completed,
// total: totalSeg
// }));
// console.info(`${downloadedSeg} of ${totalSeg} parts downloaded [${data.percent}%] (${Helper.formatTime(parseInt((data.time / 1000).toFixed(0)))} | ${(data.downloadSpeed / 1000000).toPrecision(2)}Mb/s)`);
// if (this.data.callback)
// this.data.callback({ total: this.data.parts.total, cur: this.data.parts.completed, bytes: this.data.bytesDownloaded, percent: data.percent, time: data.time, downloadSpeed: data.downloadSpeed });
// }
// // return result
// fs.unlinkSync(`${fn}.resume`);
// return { ok: true, parts: this.data.parts };
// }
// async downloadPart(seg: Segment, segIndex: number, segOffset: number){
// const sURI = extFn.getURI(seg.uri, this.data.baseurl);
// let decipher, part, dec;
// const p = segIndex;
// try {
// if (seg.key != undefined) {
// decipher = await this.getKey(seg.key, p, segOffset);
// }
// part = await extFn.getData(p, sURI, {
// ...(seg.byterange ? {
// Range: `bytes=${seg.byterange.offset}-${seg.byterange.offset+seg.byterange.length-1}`
// } : {})
// }, segOffset, false, this.data.timeout, this.data.retries, [
// (res, retryWithMergedOptions) => {
// if(this.data.checkPartLength && res.headers['content-length']){
// if(!res.body || (res.body as any).length != res.headers['content-length']){
// // 'Part not fully downloaded'
// return retryWithMergedOptions();
// }
// }
// return res;
// }
// ]);
// if(this.data.checkPartLength && !(part as any).headers['content-length']){
// this.data.checkPartLength = false;
// console.warn(`Part ${segIndex+segOffset+1}: can't check parts size!`);
// }
// if (decipher == undefined) {
// this.data.bytesDownloaded += (part.body as Buffer).byteLength;
// return { dec: part.body, p };
// }
// dec = decipher.update(part.body as Buffer);
// dec = Buffer.concat([dec, decipher.final()]);
// this.data.bytesDownloaded += dec.byteLength;
// }
// catch (error: any) {
// error.p = p;
// throw error;
// }
// return { dec, p };
// }
// async downloadKey(key: Key, segIndex: number, segOffset: number){
// const kURI = extFn.getURI(key.uri, this.data.baseurl);
// if (!this.data.keys[kURI]) {
// try {
// const rkey = await extFn.getData(segIndex, kURI, {}, segOffset, true, this.data.timeout, this.data.retries, [
// (res, retryWithMergedOptions) => {
// if (!res || !res.body) {
// // 'Key get error'
// return retryWithMergedOptions();
// }
// if((res.body as any).length != 16){
// // 'Key not fully downloaded'
// return retryWithMergedOptions();
// }
// return res;
// }
// ]);
// return rkey;
// }
// catch (error: any) {
// error.p = segIndex;
// throw error;
// }
// }
// }
// async getKey(key: Key, segIndex: number, segOffset: number){
// const kURI = extFn.getURI(key.uri, this.data.baseurl);
// const p = segIndex;
// if (!this.data.keys[kURI]) {
// try{
// const rkey = await this.downloadKey(key, segIndex, segOffset);
// if (!rkey)
// throw new Error();
// this.data.keys[kURI] = rkey.body;
// }
// catch (error: any) {
// error.p = p;
// throw error;
// }
// }
// // get ivs
// const iv = Buffer.alloc(16);
// const ivs = key.iv ? key.iv : [0, 0, 0, p + 1];
// for (let i = 0; i < ivs.length; i++) {
// iv.writeUInt32BE(ivs[i], i * 4);
// }
// return crypto.createDecipheriv('aes-128-cbc', this.data.keys[kURI], iv);
// }
// }
// const extFn = {
// getURI: (uri: string, baseurl?: string) => {
// const httpURI = /^https{0,1}:/.test(uri);
// if (!baseurl && !httpURI) {
// throw new Error('No base and not http(s) uri');
// }
// else if (httpURI) {
// return uri;
// }
// return baseurl + uri;
// },
// getDownloadInfo: (dateStart: number, partsDL: number, partsTotal: number, downloadedBytes: number) => {
// const dateElapsed = Date.now() - dateStart;
// const percentFxd = parseInt((partsDL / partsTotal * 100).toFixed());
// const percent = percentFxd < 100 ? percentFxd : (partsTotal == partsDL ? 100 : 99);
// const revParts = dateElapsed * (partsTotal / partsDL - 1);
// const downloadSpeed = downloadedBytes / (dateElapsed / 1000); //Bytes per second
// return { percent, time: revParts, downloadSpeed };
// },
// getData: (partIndex: number, uri: string, headers: Record<string, string>, segOffset: number, isKey: boolean, timeout: number, retry: number, afterResponse: ((res: Response, retryWithMergedOptions: () => Response) => Response)[]) => {
// // get file if uri is local
// if (uri.startsWith('file://')) {
// return {
// body: fs.readFileSync(url.fileURLToPath(uri)),
// };
// }
// // base options
// headers = headers && typeof headers == 'object' ? headers : {};
// const options = { headers, retry, responseType: 'buffer', hooks: {
// beforeRequest: [
// (options: Record<string, Record<string, unknown>>) => {
// if(!options.headers['user-agent']){
// options.headers['user-agent'] = 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:70.0) Gecko/20100101 Firefox/70.0';
// }
// //TODO: implement fix for hidive properly
// if ((options.url.hostname as string).match('hidive')) {
// options.headers['referrer'] = 'https://www.hidive.com/';
// options.headers['origin'] = 'https://www.hidive.com';
// } else if ((options.url.hostname as string).includes('animecdn')) {
// options.headers = {
// origin: 'https://www.animeonegai.com',
// referer: 'https://www.animeonegai.com/',
// range: options.headers['range']
// };
// }
// // console.log(' - Req:', options.url.pathname);
// }
// ],
// afterResponse: [(fixMiddleWare as (r: Response, s: () => Response) => Response)].concat(afterResponse || []),
// beforeRetry: [
// (_: any, error: Error, retryCount: number) => {
// if(error){
// const partType = isKey ? 'Key': 'Part';
// const partIndx = partIndex + 1 + segOffset;
// console.warn('%s %s: %d attempt to retrieve data', partType, partIndx, retryCount + 1);
// console.error(`\t${error.message}`);
// }
// }
// ]
// }} as Record<string, unknown>;
// // proxy
// options.timeout = timeout;
// // do request
// return got(uri, options);
// }
// };
// export default hlsDownload;

View file

@ -75,7 +75,7 @@ let argvC: {
rawoutput: string; rawoutput: string;
nocleanup: boolean; nocleanup: boolean;
help: boolean | undefined; help: boolean | undefined;
service: 'crunchy' | 'hidive' | 'ao' | 'adn'; service: 'crunchy' | 'hidive' | 'adn';
update: boolean; update: boolean;
fontName: string | undefined; fontName: string | undefined;
_: (string | number)[]; _: (string | number)[];

View file

@ -1,4 +1,4 @@
import { aoSearchLocales, dubLanguageCodes, languages, searchLocales, subtitleLanguagesFilter } from './module.langsData'; import { dubLanguageCodes, languages, searchLocales, subtitleLanguagesFilter } from './module.langsData';
import { CrunchyVideoPlayStreams, CrunchyAudioPlayStreams } from '../@types/enums'; import { CrunchyVideoPlayStreams, CrunchyAudioPlayStreams } from '../@types/enums';
const groups = { const groups = {
@ -35,7 +35,7 @@ export type TAppArg<T extends boolean | string | number | unknown[], K = any> =
default: T | undefined; default: T | undefined;
name?: string; name?: string;
}; };
service: Array<'crunchy' | 'hidive' | 'ao' | 'adn' | 'all'>; service: Array<'crunchy' | 'hidive' | 'adn' | 'all'>;
usage: string; // -(-)${name} will be added for each command, usage: string; // -(-)${name} will be added for each command,
demandOption?: true; demandOption?: true;
transformer?: (value: T) => K; transformer?: (value: T) => K;
@ -110,12 +110,12 @@ const args: TAppArg<boolean | number | string | unknown[]>[] = [
describe: 'Set the service locale', describe: 'Set the service locale',
docDescribe: 'Set the local that will be used for the API.', docDescribe: 'Set the local that will be used for the API.',
group: 'search', group: 'search',
choices: [...searchLocales.filter((a) => a !== undefined), ...aoSearchLocales.filter((a) => a !== undefined)] as string[], choices: [...searchLocales.filter((a) => a !== undefined)] as string[],
default: { default: {
default: 'en-US' default: 'en-US'
}, },
type: 'string', type: 'string',
service: ['crunchy', 'ao', 'adn'], service: ['crunchy', 'adn'],
usage: '${locale}' usage: '${locale}'
}, },
{ {
@ -226,7 +226,7 @@ const args: TAppArg<boolean | number | string | unknown[]>[] = [
describe: 'Download only once the video with the best selected quality', describe: 'Download only once the video with the best selected quality',
type: 'boolean', type: 'boolean',
group: 'dl', group: 'dl',
service: ['crunchy', 'ao'], service: ['crunchy'],
docDescribe: docDescribe:
'If selected, the best selected quality will be downloaded only for the first language,' + 'If selected, the best selected quality will be downloaded only for the first language,' +
'\nthen the worst video quality with the same audio quality will be downloaded for every other language.' + '\nthen the worst video quality with the same audio quality will be downloaded for every other language.' +
@ -689,7 +689,7 @@ const args: TAppArg<boolean | number | string | unknown[]>[] = [
group: 'util', group: 'util',
service: ['all'], service: ['all'],
type: 'string', type: 'string',
choices: ['crunchy', 'hidive', 'ao', 'adn'], choices: ['crunchy', 'hidive', 'adn'],
usage: '${service}', usage: '${service}',
default: { default: {
default: '' default: ''
@ -806,7 +806,7 @@ const args: TAppArg<boolean | number | string | unknown[]>[] = [
describe: 'Allows you to login with your token (Example on crunchy is Refresh Token/etp-rt cookie)', describe: 'Allows you to login with your token (Example on crunchy is Refresh Token/etp-rt cookie)',
docDescribe: true, docDescribe: true,
group: 'auth', group: 'auth',
service: ['crunchy', 'ao'], service: ['crunchy'],
type: 'string', type: 'string',
usage: '${token}', usage: '${token}',
default: { default: {

View file

@ -26,7 +26,6 @@ const hdPflCfgFile = path.join(workingDir, 'config', 'hd_profile');
const sessCfgFile = { const sessCfgFile = {
cr: path.join(workingDir, 'config', 'cr_sess'), cr: path.join(workingDir, 'config', 'cr_sess'),
hd: path.join(workingDir, 'config', 'hd_sess'), hd: path.join(workingDir, 'config', 'hd_sess'),
ao: path.join(workingDir, 'config', 'ao_sess'),
adn: path.join(workingDir, 'config', 'adn_sess') adn: path.join(workingDir, 'config', 'adn_sess')
}; };
const stateFile = path.join(workingDir, 'config', 'guistate'); const stateFile = path.join(workingDir, 'config', 'guistate');
@ -34,7 +33,6 @@ const tokenFile = {
cr: path.join(workingDir, 'config', 'cr_token'), cr: path.join(workingDir, 'config', 'cr_token'),
hd: path.join(workingDir, 'config', 'hd_token'), hd: path.join(workingDir, 'config', 'hd_token'),
hdNew: path.join(workingDir, 'config', 'hd_new_token'), hdNew: path.join(workingDir, 'config', 'hd_new_token'),
ao: path.join(workingDir, 'config', 'ao_token'),
adn: path.join(workingDir, 'config', 'adn_token') adn: path.join(workingDir, 'config', 'adn_token')
}; };
@ -237,24 +235,6 @@ const saveADNToken = (data: Record<string, unknown>) => {
} }
}; };
const loadAOToken = () => {
let token = loadYamlCfgFile(tokenFile.ao, true);
if (typeof token !== 'object' || token === null || Array.isArray(token)) {
token = {};
}
return token;
};
const saveAOToken = (data: Record<string, unknown>) => {
const cfgFolder = path.dirname(tokenFile.ao);
try {
fs.ensureDirSync(cfgFolder);
fs.writeFileSync(`${tokenFile.ao}.yml`, yaml.stringify(data));
} catch (e) {
console.error("Can't save token file to disk!");
}
};
const loadHDSession = () => { const loadHDSession = () => {
let session = loadYamlCfgFile(sessCfgFile.hd, true); let session = loadYamlCfgFile(sessCfgFile.hd, true);
if (typeof session !== 'object' || session === null || Array.isArray(session)) { if (typeof session !== 'object' || session === null || Array.isArray(session)) {
@ -392,8 +372,6 @@ export {
loadNewHDToken, loadNewHDToken,
saveHDProfile, saveHDProfile,
loadHDProfile, loadHDProfile,
saveAOToken,
loadAOToken,
getState, getState,
setState, setState,
writeYamlCfgFile, writeYamlCfgFile,

View file

@ -14,9 +14,6 @@ export type DataType = {
hidive: { hidive: {
s: ItemType; s: ItemType;
}; };
ao: {
s: ItemType;
};
adn: { adn: {
s: ItemType; s: ItemType;
}; };
@ -36,10 +33,6 @@ const addToArchive = (
service: 'hidive'; service: 'hidive';
type: 's'; type: 's';
} }
| {
service: 'ao';
type: 's';
}
| { | {
service: 'adn'; service: 'adn';
type: 's'; type: 's';
@ -59,16 +52,7 @@ const addToArchive = (
}); });
(data as any)[kind.service][kind.type] = items; (data as any)[kind.service][kind.type] = items;
} else { } else {
if (kind.service === 'ao') { if (kind.service === 'crunchy') {
data['ao'] = {
s: [
{
id: ID,
already: []
}
]
};
} else if (kind.service === 'crunchy') {
data['crunchy'] = { data['crunchy'] = {
s: ([] as ItemType).concat( s: ([] as ItemType).concat(
kind.type === 's' kind.type === 's'
@ -120,10 +104,6 @@ const downloaded = (
service: 'hidive'; service: 'hidive';
type: 's'; type: 's';
} }
| {
service: 'ao';
type: 's';
}
| { | {
service: 'adn'; service: 'adn';
type: 's'; type: 's';
@ -150,7 +130,7 @@ const downloaded = (
fs.writeFileSync(archiveFile, JSON.stringify(data, null, 4)); fs.writeFileSync(archiveFile, JSON.stringify(data, null, 4));
}; };
const makeCommand = (service: 'crunchy' | 'hidive' | 'ao' | 'adn'): Partial<ArgvType>[] => { const makeCommand = (service: 'crunchy' | 'hidive' | 'adn'): Partial<ArgvType>[] => {
const data = loadData(); const data = loadData();
const ret: Partial<ArgvType>[] = []; const ret: Partial<ArgvType>[] = [];
const kind = data[service]; const kind = data[service];

View file

@ -32,7 +32,7 @@ function hasDisplay(): boolean {
// req // req
export class Req { export class Req {
private sessCfg: string; private sessCfg: string;
private service: 'cr' | 'hd' | 'ao' | 'adn'; private service: 'cr' | 'hd' | 'adn';
private session: Record< private session: Record<
string, string,
{ {
@ -51,7 +51,7 @@ export class Req {
private domain: Record<string, unknown>, private domain: Record<string, unknown>,
private debug: boolean, private debug: boolean,
private nosess = false, private nosess = false,
private type: 'cr' | 'hd' | 'ao' | 'adn' private type: 'cr' | 'hd' | 'adn'
) { ) {
this.sessCfg = yamlCfg.sessCfgFile[type]; this.sessCfg = yamlCfg.sessCfgFile[type];
this.service = type; this.service = type;

View file

@ -5,7 +5,6 @@ export type LanguageItem = {
hd_locale?: string; hd_locale?: string;
adn_locale?: string; adn_locale?: string;
new_hd_locale?: string; new_hd_locale?: string;
ao_locale?: string;
locale: string; locale: string;
code: string; code: string;
name: string; name: string;
@ -13,13 +12,13 @@ export type LanguageItem = {
}; };
const languages: LanguageItem[] = [ const languages: LanguageItem[] = [
{ locale: 'un', code: 'und', name: 'Undetermined', language: 'Undetermined', new_hd_locale: 'und', cr_locale: 'und', adn_locale: 'und', ao_locale: 'und' }, { locale: 'un', code: 'und', name: 'Undetermined', language: 'Undetermined', new_hd_locale: 'und', cr_locale: 'und', adn_locale: 'und' },
{ cr_locale: 'en-US', new_hd_locale: 'en-US', hd_locale: 'English', locale: 'en', code: 'eng', name: 'English' }, { cr_locale: 'en-US', new_hd_locale: 'en-US', hd_locale: 'English', locale: 'en', code: 'eng', name: 'English' },
{ cr_locale: 'en-IN', locale: 'en-IN', code: 'eng', name: 'English (India)' }, { cr_locale: 'en-IN', locale: 'en-IN', code: 'eng', name: 'English (India)' },
{ cr_locale: 'es-LA', new_hd_locale: 'es-MX', hd_locale: 'Spanish LatAm', locale: 'es-419', code: 'spa', name: 'Spanish', language: 'Latin American Spanish' }, { cr_locale: 'es-LA', new_hd_locale: 'es-MX', hd_locale: 'Spanish LatAm', locale: 'es-419', code: 'spa', name: 'Spanish', language: 'Latin American Spanish' },
{ cr_locale: 'es-419', ao_locale: 'es', hd_locale: 'Spanish', locale: 'es-419', code: 'spa-419', name: 'Spanish', language: 'Latin American Spanish' }, { cr_locale: 'es-419', hd_locale: 'Spanish', locale: 'es-419', code: 'spa-419', name: 'Spanish', language: 'Latin American Spanish' },
{ cr_locale: 'es-ES', new_hd_locale: 'es-ES', hd_locale: 'Spanish Europe', locale: 'es-ES', code: 'spa-ES', name: 'Castilian', language: 'European Spanish' }, { cr_locale: 'es-ES', new_hd_locale: 'es-ES', hd_locale: 'Spanish Europe', locale: 'es-ES', code: 'spa-ES', name: 'Castilian', language: 'European Spanish' },
{ cr_locale: 'pt-BR', ao_locale: 'pt', new_hd_locale: 'pt-BR', hd_locale: 'Portuguese', locale: 'pt-BR', code: 'por', name: 'Portuguese', language: 'Brazilian Portuguese' }, { cr_locale: 'pt-BR', new_hd_locale: 'pt-BR', hd_locale: 'Portuguese', locale: 'pt-BR', code: 'por', name: 'Portuguese', language: 'Brazilian Portuguese' },
{ cr_locale: 'pt-PT', locale: 'pt-PT', code: 'por', name: 'Portuguese (Portugal)', language: 'Portugues (Portugal)' }, { cr_locale: 'pt-PT', locale: 'pt-PT', code: 'por', name: 'Portuguese (Portugal)', language: 'Portugues (Portugal)' },
{ cr_locale: 'fr-FR', adn_locale: 'fr', hd_locale: 'French', locale: 'fr', code: 'fra', name: 'French' }, { cr_locale: 'fr-FR', adn_locale: 'fr', hd_locale: 'French', locale: 'fr', code: 'fra', name: 'French' },
{ cr_locale: 'de-DE', adn_locale: 'de', hd_locale: 'German', locale: 'de', code: 'deu', name: 'German' }, { cr_locale: 'de-DE', adn_locale: 'de', hd_locale: 'German', locale: 'de', code: 'deu', name: 'German' },
@ -42,7 +41,7 @@ const languages: LanguageItem[] = [
{ cr_locale: 'vi-VN', locale: 'vi-VN', code: 'vie', name: 'Vietnamese', language: 'Tiếng Việt' }, { cr_locale: 'vi-VN', locale: 'vi-VN', code: 'vie', name: 'Vietnamese', language: 'Tiếng Việt' },
{ cr_locale: 'id-ID', locale: 'id-ID', code: 'ind', name: 'Indonesian', language: 'Bahasa Indonesia' }, { cr_locale: 'id-ID', locale: 'id-ID', code: 'ind', name: 'Indonesian', language: 'Bahasa Indonesia' },
{ cr_locale: 'te-IN', locale: 'te-IN', code: 'tel', name: 'Telugu (India)', language: 'తెలుగు' }, { cr_locale: 'te-IN', locale: 'te-IN', code: 'tel', name: 'Telugu (India)', language: 'తెలుగు' },
{ cr_locale: 'ja-JP', adn_locale: 'ja', ao_locale: 'ja', hd_locale: 'Japanese', locale: 'ja', code: 'jpn', name: 'Japanese' } { cr_locale: 'ja-JP', adn_locale: 'ja', hd_locale: 'Japanese', locale: 'ja', code: 'jpn', name: 'Japanese' }
]; ];
// add en language names // add en language names
@ -96,19 +95,6 @@ const searchLocales = (() => {
]; ];
})(); })();
export const aoSearchLocales = (() => {
return [
'',
...new Set(
languages
.map((l) => {
return l.ao_locale;
})
.slice(0, -1)
)
];
})();
// convert // convert
const fixLanguageTag = (tag: string) => { const fixLanguageTag = (tag: string) => {
tag = typeof tag == 'string' ? tag : 'und'; tag = typeof tag == 'string' ? tag : 'und';

View file

@ -80,11 +80,6 @@ export async function parse(manifest: string, language?: LanguageItem, url?: str
const options: RequestInit = { const options: RequestInit = {
method: 'head' method: 'head'
}; };
if (playlist.sidx.uri.includes('animecdn'))
options.headers = {
origin: 'https://www.animeonegai.com',
referer: 'https://www.animeonegai.com/'
};
const item = await fetch(playlist.sidx.uri, options); const item = await fetch(playlist.sidx.uri, options);
if (!item.ok) if (!item.ok)
console.warn(`${item.status}: ${item.statusText}, Unable to fetch byteLength for audio stream ${Math.round(playlist.attributes.BANDWIDTH / 1024)}KiB/s`); console.warn(`${item.status}: ${item.statusText}, Unable to fetch byteLength for audio stream ${Math.round(playlist.attributes.BANDWIDTH / 1024)}KiB/s`);
@ -164,11 +159,6 @@ export async function parse(manifest: string, language?: LanguageItem, url?: str
const options: RequestInit = { const options: RequestInit = {
method: 'head' method: 'head'
}; };
if (playlist.sidx.uri.includes('animecdn'))
options.headers = {
origin: 'https://www.animeonegai.com',
referer: 'https://www.animeonegai.com/'
};
const item = await fetch(playlist.sidx.uri, options); const item = await fetch(playlist.sidx.uri, options);
if (!item.ok) if (!item.ok)
console.warn( console.warn(

View file

@ -2,7 +2,7 @@
"name": "multi-downloader-nx", "name": "multi-downloader-nx",
"short_name": "aniDL", "short_name": "aniDL",
"version": "5.5.7", "version": "5.5.7",
"description": "Downloader for Crunchyroll, Hidive, AnimeOnegai, and AnimationDigitalNetwork with CLI and GUI", "description": "Downloader for Crunchyroll, Hidive, and AnimationDigitalNetwork with CLI and GUI",
"keywords": [ "keywords": [
"download", "download",
"downloader", "downloader",
@ -90,7 +90,7 @@
"protoc": "^1.1.3", "protoc": "^1.1.3",
"removeNPMAbsolutePaths": "^3.0.1", "removeNPMAbsolutePaths": "^3.0.1",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.9.2", "typescript": "^5.9.3",
"typescript-eslint": "^8.45.0" "typescript-eslint": "^8.45.0"
}, },
"scripts": { "scripts": {

View file

@ -128,10 +128,10 @@ importers:
version: 17.0.33 version: 17.0.33
'@typescript-eslint/eslint-plugin': '@typescript-eslint/eslint-plugin':
specifier: ^8.45.0 specifier: ^8.45.0
version: 8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0)(typescript@5.9.2))(eslint@9.36.0)(typescript@5.9.2) version: 8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0)(typescript@5.9.3))(eslint@9.36.0)(typescript@5.9.3)
'@typescript-eslint/parser': '@typescript-eslint/parser':
specifier: ^8.45.0 specifier: ^8.45.0
version: 8.45.0(eslint@9.36.0)(typescript@5.9.2) version: 8.45.0(eslint@9.36.0)(typescript@5.9.3)
'@yao-pkg/pkg': '@yao-pkg/pkg':
specifier: ^6.7.0 specifier: ^6.7.0
version: 6.7.0 version: 6.7.0
@ -152,13 +152,13 @@ importers:
version: 3.0.1 version: 3.0.1
ts-node: ts-node:
specifier: ^10.9.2 specifier: ^10.9.2
version: 10.9.2(@types/node@24.6.0)(typescript@5.9.2) version: 10.9.2(@types/node@24.6.0)(typescript@5.9.3)
typescript: typescript:
specifier: ^5.9.2 specifier: ^5.9.3
version: 5.9.2 version: 5.9.3
typescript-eslint: typescript-eslint:
specifier: ^8.45.0 specifier: ^8.45.0
version: 8.45.0(eslint@9.36.0)(typescript@5.9.2) version: 8.45.0(eslint@9.36.0)(typescript@5.9.3)
packages: packages:
@ -763,14 +763,6 @@ packages:
resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==}
engines: {node: '>=4'} engines: {node: '>=4'}
async-function@1.0.0:
resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
engines: {node: '>= 0.4'}
async-generator-function@1.0.0:
resolution: {integrity: sha512-+NAXNqgCrB95ya4Sr66i1CL2hqLVckAk7xwRYWdcm39/ELQ6YNn1aw5r0bdQtqNZgQpEWzc5yc/igXc7aL5SLA==}
engines: {node: '>= 0.4'}
b4a@1.7.3: b4a@1.7.3:
resolution: {integrity: sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==} resolution: {integrity: sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==}
peerDependencies: peerDependencies:
@ -1286,16 +1278,12 @@ packages:
function-bind@1.1.2: function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
generator-function@2.0.0:
resolution: {integrity: sha512-xPypGGincdfyl/AiSGa7GjXLkvld9V7GjZlowup9SHIJnQnHLFiLODCd/DqKOp0PBagbHJ68r1KJI9Mut7m4sA==}
engines: {node: '>= 0.4'}
get-caller-file@2.0.5: get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*} engines: {node: 6.* || 8.* || >= 10.*}
get-intrinsic@1.3.1: get-intrinsic@1.3.0:
resolution: {integrity: sha512-fk1ZVEeOX9hVZ6QzoBNEC55+Ucqg4sTVwrVuigZhuRPESVFpMyXnd3sbXvPOwp7Y9riVyANiqhEuRF0G1aVSeQ==} resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
get-proto@1.0.1: get-proto@1.0.1:
@ -2137,8 +2125,8 @@ packages:
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
hasBin: true hasBin: true
typescript@5.9.2: typescript@5.9.3:
resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
hasBin: true hasBin: true
@ -2673,41 +2661,41 @@ snapshots:
'@types/node': 24.6.0 '@types/node': 24.6.0
optional: true optional: true
'@typescript-eslint/eslint-plugin@8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0)(typescript@5.9.2))(eslint@9.36.0)(typescript@5.9.2)': '@typescript-eslint/eslint-plugin@8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0)(typescript@5.9.3))(eslint@9.36.0)(typescript@5.9.3)':
dependencies: dependencies:
'@eslint-community/regexpp': 4.12.1 '@eslint-community/regexpp': 4.12.1
'@typescript-eslint/parser': 8.45.0(eslint@9.36.0)(typescript@5.9.2) '@typescript-eslint/parser': 8.45.0(eslint@9.36.0)(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.45.0 '@typescript-eslint/scope-manager': 8.45.0
'@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0)(typescript@5.9.2) '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0)(typescript@5.9.3)
'@typescript-eslint/utils': 8.45.0(eslint@9.36.0)(typescript@5.9.2) '@typescript-eslint/utils': 8.45.0(eslint@9.36.0)(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.45.0 '@typescript-eslint/visitor-keys': 8.45.0
eslint: 9.36.0 eslint: 9.36.0
graphemer: 1.4.0 graphemer: 1.4.0
ignore: 7.0.5 ignore: 7.0.5
natural-compare: 1.4.0 natural-compare: 1.4.0
ts-api-utils: 2.1.0(typescript@5.9.2) ts-api-utils: 2.1.0(typescript@5.9.3)
typescript: 5.9.2 typescript: 5.9.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@typescript-eslint/parser@8.45.0(eslint@9.36.0)(typescript@5.9.2)': '@typescript-eslint/parser@8.45.0(eslint@9.36.0)(typescript@5.9.3)':
dependencies: dependencies:
'@typescript-eslint/scope-manager': 8.45.0 '@typescript-eslint/scope-manager': 8.45.0
'@typescript-eslint/types': 8.45.0 '@typescript-eslint/types': 8.45.0
'@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.2) '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.45.0 '@typescript-eslint/visitor-keys': 8.45.0
debug: 4.4.3 debug: 4.4.3
eslint: 9.36.0 eslint: 9.36.0
typescript: 5.9.2 typescript: 5.9.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@typescript-eslint/project-service@8.45.0(typescript@5.9.2)': '@typescript-eslint/project-service@8.45.0(typescript@5.9.3)':
dependencies: dependencies:
'@typescript-eslint/tsconfig-utils': 8.45.0(typescript@5.9.2) '@typescript-eslint/tsconfig-utils': 8.45.0(typescript@5.9.3)
'@typescript-eslint/types': 8.45.0 '@typescript-eslint/types': 8.45.0
debug: 4.4.3 debug: 4.4.3
typescript: 5.9.2 typescript: 5.9.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -2716,28 +2704,28 @@ snapshots:
'@typescript-eslint/types': 8.45.0 '@typescript-eslint/types': 8.45.0
'@typescript-eslint/visitor-keys': 8.45.0 '@typescript-eslint/visitor-keys': 8.45.0
'@typescript-eslint/tsconfig-utils@8.45.0(typescript@5.9.2)': '@typescript-eslint/tsconfig-utils@8.45.0(typescript@5.9.3)':
dependencies: dependencies:
typescript: 5.9.2 typescript: 5.9.3
'@typescript-eslint/type-utils@8.45.0(eslint@9.36.0)(typescript@5.9.2)': '@typescript-eslint/type-utils@8.45.0(eslint@9.36.0)(typescript@5.9.3)':
dependencies: dependencies:
'@typescript-eslint/types': 8.45.0 '@typescript-eslint/types': 8.45.0
'@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.2) '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3)
'@typescript-eslint/utils': 8.45.0(eslint@9.36.0)(typescript@5.9.2) '@typescript-eslint/utils': 8.45.0(eslint@9.36.0)(typescript@5.9.3)
debug: 4.4.3 debug: 4.4.3
eslint: 9.36.0 eslint: 9.36.0
ts-api-utils: 2.1.0(typescript@5.9.2) ts-api-utils: 2.1.0(typescript@5.9.3)
typescript: 5.9.2 typescript: 5.9.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@typescript-eslint/types@8.45.0': {} '@typescript-eslint/types@8.45.0': {}
'@typescript-eslint/typescript-estree@8.45.0(typescript@5.9.2)': '@typescript-eslint/typescript-estree@8.45.0(typescript@5.9.3)':
dependencies: dependencies:
'@typescript-eslint/project-service': 8.45.0(typescript@5.9.2) '@typescript-eslint/project-service': 8.45.0(typescript@5.9.3)
'@typescript-eslint/tsconfig-utils': 8.45.0(typescript@5.9.2) '@typescript-eslint/tsconfig-utils': 8.45.0(typescript@5.9.3)
'@typescript-eslint/types': 8.45.0 '@typescript-eslint/types': 8.45.0
'@typescript-eslint/visitor-keys': 8.45.0 '@typescript-eslint/visitor-keys': 8.45.0
debug: 4.4.3 debug: 4.4.3
@ -2745,19 +2733,19 @@ snapshots:
is-glob: 4.0.3 is-glob: 4.0.3
minimatch: 9.0.5 minimatch: 9.0.5
semver: 7.7.2 semver: 7.7.2
ts-api-utils: 2.1.0(typescript@5.9.2) ts-api-utils: 2.1.0(typescript@5.9.3)
typescript: 5.9.2 typescript: 5.9.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@typescript-eslint/utils@8.45.0(eslint@9.36.0)(typescript@5.9.2)': '@typescript-eslint/utils@8.45.0(eslint@9.36.0)(typescript@5.9.3)':
dependencies: dependencies:
'@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0) '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0)
'@typescript-eslint/scope-manager': 8.45.0 '@typescript-eslint/scope-manager': 8.45.0
'@typescript-eslint/types': 8.45.0 '@typescript-eslint/types': 8.45.0
'@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.2) '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3)
eslint: 9.36.0 eslint: 9.36.0
typescript: 5.9.2 typescript: 5.9.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -2869,10 +2857,6 @@ snapshots:
dependencies: dependencies:
tslib: 2.8.1 tslib: 2.8.1
async-function@1.0.0: {}
async-generator-function@1.0.0: {}
b4a@1.7.3: {} b4a@1.7.3: {}
balanced-match@1.0.2: {} balanced-match@1.0.2: {}
@ -2995,7 +2979,7 @@ snapshots:
call-bound@1.0.4: call-bound@1.0.4:
dependencies: dependencies:
call-bind-apply-helpers: 1.0.2 call-bind-apply-helpers: 1.0.2
get-intrinsic: 1.3.1 get-intrinsic: 1.3.0
callsites@3.1.0: {} callsites@3.1.0: {}
@ -3445,20 +3429,15 @@ snapshots:
function-bind@1.1.2: {} function-bind@1.1.2: {}
generator-function@2.0.0: {}
get-caller-file@2.0.5: {} get-caller-file@2.0.5: {}
get-intrinsic@1.3.1: get-intrinsic@1.3.0:
dependencies: dependencies:
async-function: 1.0.0
async-generator-function: 1.0.0
call-bind-apply-helpers: 1.0.2 call-bind-apply-helpers: 1.0.2
es-define-property: 1.0.1 es-define-property: 1.0.1
es-errors: 1.3.0 es-errors: 1.3.0
es-object-atoms: 1.1.1 es-object-atoms: 1.1.1
function-bind: 1.1.2 function-bind: 1.1.2
generator-function: 2.0.0
get-proto: 1.0.1 get-proto: 1.0.1
gopd: 1.2.0 gopd: 1.2.0
has-symbols: 1.1.0 has-symbols: 1.1.0
@ -4154,14 +4133,14 @@ snapshots:
dependencies: dependencies:
call-bound: 1.0.4 call-bound: 1.0.4
es-errors: 1.3.0 es-errors: 1.3.0
get-intrinsic: 1.3.1 get-intrinsic: 1.3.0
object-inspect: 1.13.4 object-inspect: 1.13.4
side-channel-weakmap@1.0.2: side-channel-weakmap@1.0.2:
dependencies: dependencies:
call-bound: 1.0.4 call-bound: 1.0.4
es-errors: 1.3.0 es-errors: 1.3.0
get-intrinsic: 1.3.1 get-intrinsic: 1.3.0
object-inspect: 1.13.4 object-inspect: 1.13.4
side-channel-map: 1.0.1 side-channel-map: 1.0.1
@ -4325,11 +4304,11 @@ snapshots:
tree-kill@1.2.2: {} tree-kill@1.2.2: {}
ts-api-utils@2.1.0(typescript@5.9.2): ts-api-utils@2.1.0(typescript@5.9.3):
dependencies: dependencies:
typescript: 5.9.2 typescript: 5.9.3
ts-node@10.9.2(@types/node@24.6.0)(typescript@5.9.2): ts-node@10.9.2(@types/node@24.6.0)(typescript@5.9.3):
dependencies: dependencies:
'@cspotcode/source-map-support': 0.8.1 '@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.11 '@tsconfig/node10': 1.0.11
@ -4343,7 +4322,7 @@ snapshots:
create-require: 1.1.1 create-require: 1.1.1
diff: 4.0.2 diff: 4.0.2
make-error: 1.3.6 make-error: 1.3.6
typescript: 5.9.2 typescript: 5.9.3
v8-compile-cache-lib: 3.0.1 v8-compile-cache-lib: 3.0.1
yn: 3.1.1 yn: 3.1.1
@ -4365,20 +4344,20 @@ snapshots:
typed-query-selector@2.12.0: {} typed-query-selector@2.12.0: {}
typescript-eslint@8.45.0(eslint@9.36.0)(typescript@5.9.2): typescript-eslint@8.45.0(eslint@9.36.0)(typescript@5.9.3):
dependencies: dependencies:
'@typescript-eslint/eslint-plugin': 8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0)(typescript@5.9.2))(eslint@9.36.0)(typescript@5.9.2) '@typescript-eslint/eslint-plugin': 8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0)(typescript@5.9.3))(eslint@9.36.0)(typescript@5.9.3)
'@typescript-eslint/parser': 8.45.0(eslint@9.36.0)(typescript@5.9.2) '@typescript-eslint/parser': 8.45.0(eslint@9.36.0)(typescript@5.9.3)
'@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.2) '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3)
'@typescript-eslint/utils': 8.45.0(eslint@9.36.0)(typescript@5.9.2) '@typescript-eslint/utils': 8.45.0(eslint@9.36.0)(typescript@5.9.3)
eslint: 9.36.0 eslint: 9.36.0
typescript: 5.9.2 typescript: 5.9.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
typescript@5.4.5: {} typescript@5.4.5: {}
typescript@5.9.2: {} typescript@5.9.3: {}
ufo@1.6.1: {} ufo@1.6.1: {}