mirror of
https://github.com/anidl/multi-downloader-nx.git
synced 2026-01-11 20:10:20 +00:00
removed AnimeOnegai
This commit is contained in:
parent
7e680f1117
commit
3fb4392b39
26 changed files with 81 additions and 2013 deletions
1
.github/ISSUE_TEMPLATE/bug.yml
vendored
1
.github/ISSUE_TEMPLATE/bug.yml
vendored
|
|
@ -50,7 +50,6 @@ body:
|
|||
- Crunchyroll
|
||||
- Hidive
|
||||
- AnimationDigitalNetwork
|
||||
- AnimeOnegai
|
||||
- All
|
||||
- Irrelevant
|
||||
validations:
|
||||
|
|
|
|||
88
@types/animeOnegaiSearch.d.ts
vendored
88
@types/animeOnegaiSearch.d.ts
vendored
|
|
@ -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;
|
||||
}
|
||||
36
@types/animeOnegaiSeasons.d.ts
vendored
36
@types/animeOnegaiSeasons.d.ts
vendored
|
|
@ -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;
|
||||
}
|
||||
111
@types/animeOnegaiSeries.d.ts
vendored
111
@types/animeOnegaiSeries.d.ts
vendored
|
|
@ -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;
|
||||
}
|
||||
41
@types/animeOnegaiStream.d.ts
vendored
41
@types/animeOnegaiStream.d.ts
vendored
|
|
@ -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
4
@types/ws.d.ts
vendored
|
|
@ -30,8 +30,8 @@ export type MessageTypes = {
|
|||
isDownloading: [undefined, boolean];
|
||||
openFolder: [FolderTypes, undefined];
|
||||
changeProvider: [undefined, boolean];
|
||||
type: [undefined, 'crunchy' | 'hidive' | 'ao' | 'adn' | undefined];
|
||||
setup: ['crunchy' | 'hidive' | 'ao' | 'adn' | undefined, undefined];
|
||||
type: [undefined, 'crunchy' | 'hidive' | 'adn' | undefined];
|
||||
setup: ['crunchy' | 'hidive' | 'adn' | undefined, undefined];
|
||||
openFile: [[FolderTypes, string], undefined];
|
||||
openURL: [string, undefined];
|
||||
isSetup: [undefined, boolean];
|
||||
|
|
|
|||
908
ao.ts
908
ao.ts
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ If you find any bugs in this documentation or in the program itself please repor
|
|||
|
||||
## 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.
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
[](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
|
||||
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -27,8 +27,6 @@ const MenuBar: React.FC = () => {
|
|||
return 'Crunchyroll';
|
||||
case 'hidive':
|
||||
return 'Hidive';
|
||||
case 'ao':
|
||||
return 'AnimeOnegai';
|
||||
case 'adn':
|
||||
return 'AnimationDigitalNetwork';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Divider, Box, Button, Typography, Avatar } from '@mui/material';
|
|||
import useStore from '../hooks/useStore';
|
||||
import { StoreState } from './Store';
|
||||
|
||||
type Services = 'crunchy' | 'hidive' | 'ao' | 'adn';
|
||||
type Services = 'crunchy' | 'hidive' | 'adn';
|
||||
|
||||
export const serviceContext = React.createContext<Services | undefined>(undefined);
|
||||
|
||||
|
|
@ -39,14 +39,6 @@ const ServiceProvider: FCWithChildren = ({ children }) => {
|
|||
>
|
||||
Hidive
|
||||
</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'} />}>
|
||||
AnimationDigitalNetwork
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export type DownloadOptions = {
|
|||
export type StoreState = {
|
||||
episodeListing: Episode[];
|
||||
downloadOptions: DownloadOptions;
|
||||
service: 'crunchy' | 'hidive' | 'ao' | 'adn' | undefined;
|
||||
service: 'crunchy' | 'hidive' | 'adn' | undefined;
|
||||
version: string;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { MessageHandler, GuiState } from '../../@types/messageHandler';
|
|||
import { setState, getState, writeYamlCfgFile } from '../../modules/module.cfg-loader';
|
||||
import CrunchyHandler from './services/crunchyroll';
|
||||
import HidiveHandler from './services/hidive';
|
||||
import AnimeOnegaiHandler from './services/animeonegai';
|
||||
import ADNHandler from './services/adn';
|
||||
import WebSocketHandler from './websocket';
|
||||
import packageJson from '../../package.json';
|
||||
|
|
@ -35,8 +34,6 @@ export default class ServiceHandler {
|
|||
this.service = new CrunchyHandler(this.ws);
|
||||
} else if (data === 'hidive') {
|
||||
this.service = new HidiveHandler(this.ws);
|
||||
} else if (data === 'ao') {
|
||||
this.service = new AnimeOnegaiHandler(this.ws);
|
||||
} else if (data === 'adn') {
|
||||
this.service = new ADNHandler(this.ws);
|
||||
}
|
||||
|
|
@ -55,7 +52,7 @@ export default class ServiceHandler {
|
|||
this.ws.events.on('version', async (_, respond) => {
|
||||
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) => {
|
||||
if (this.service === undefined) return respond({ isOk: false, reason: new Error('No service selected') });
|
||||
respond(await this.service.checkToken());
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
19
index.ts
19
index.ts
|
|
@ -39,17 +39,6 @@ import update from './modules/module.updater';
|
|||
(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.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) {
|
||||
const ids = makeCommand(argv.service);
|
||||
|
|
@ -57,7 +46,7 @@ import update from './modules/module.updater';
|
|||
overrideArguments(cfg.cli, id);
|
||||
/* Reimport module to override appArgv */
|
||||
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;
|
||||
switch (argv.service) {
|
||||
|
|
@ -67,9 +56,6 @@ import update from './modules/module.updater';
|
|||
case 'hidive':
|
||||
service = new (await import('./hidive')).default();
|
||||
break;
|
||||
case 'ao':
|
||||
service = new (await import('./ao')).default();
|
||||
break;
|
||||
case 'adn':
|
||||
service = new (await import('./adn')).default();
|
||||
break;
|
||||
|
|
@ -88,9 +74,6 @@ import update from './modules/module.updater';
|
|||
case 'hidive':
|
||||
service = new (await import('./hidive')).default();
|
||||
break;
|
||||
case 'ao':
|
||||
service = new (await import('./ao')).default();
|
||||
break;
|
||||
case 'adn':
|
||||
service = new (await import('./adn')).default();
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import fs from 'fs';
|
|||
import path from 'path';
|
||||
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[] = [];
|
||||
str.forEach(function (part) {
|
||||
switch (part) {
|
||||
|
|
@ -13,9 +13,6 @@ const transformService = (str: Array<'crunchy' | 'hidive' | 'ao' | 'adn' | 'all'
|
|||
case 'hidive':
|
||||
services.push('Hidive');
|
||||
break;
|
||||
case 'ao':
|
||||
services.push('AnimeOnegai');
|
||||
break;
|
||||
case 'adn':
|
||||
services.push('AnimationDigitalNetwork');
|
||||
break;
|
||||
|
|
@ -33,7 +30,7 @@ If you find any bugs in this documentation or in the program itself please repor
|
|||
|
||||
## 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.
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -75,7 +75,7 @@ let argvC: {
|
|||
rawoutput: string;
|
||||
nocleanup: boolean;
|
||||
help: boolean | undefined;
|
||||
service: 'crunchy' | 'hidive' | 'ao' | 'adn';
|
||||
service: 'crunchy' | 'hidive' | 'adn';
|
||||
update: boolean;
|
||||
fontName: string | undefined;
|
||||
_: (string | number)[];
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
const groups = {
|
||||
|
|
@ -35,7 +35,7 @@ export type TAppArg<T extends boolean | string | number | unknown[], K = any> =
|
|||
default: T | undefined;
|
||||
name?: string;
|
||||
};
|
||||
service: Array<'crunchy' | 'hidive' | 'ao' | 'adn' | 'all'>;
|
||||
service: Array<'crunchy' | 'hidive' | 'adn' | 'all'>;
|
||||
usage: string; // -(-)${name} will be added for each command,
|
||||
demandOption?: true;
|
||||
transformer?: (value: T) => K;
|
||||
|
|
@ -110,12 +110,12 @@ const args: TAppArg<boolean | number | string | unknown[]>[] = [
|
|||
describe: 'Set the service locale',
|
||||
docDescribe: 'Set the local that will be used for the API.',
|
||||
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: 'en-US'
|
||||
},
|
||||
type: 'string',
|
||||
service: ['crunchy', 'ao', 'adn'],
|
||||
service: ['crunchy', 'adn'],
|
||||
usage: '${locale}'
|
||||
},
|
||||
{
|
||||
|
|
@ -226,7 +226,7 @@ const args: TAppArg<boolean | number | string | unknown[]>[] = [
|
|||
describe: 'Download only once the video with the best selected quality',
|
||||
type: 'boolean',
|
||||
group: 'dl',
|
||||
service: ['crunchy', 'ao'],
|
||||
service: ['crunchy'],
|
||||
docDescribe:
|
||||
'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.' +
|
||||
|
|
@ -689,7 +689,7 @@ const args: TAppArg<boolean | number | string | unknown[]>[] = [
|
|||
group: 'util',
|
||||
service: ['all'],
|
||||
type: 'string',
|
||||
choices: ['crunchy', 'hidive', 'ao', 'adn'],
|
||||
choices: ['crunchy', 'hidive', 'adn'],
|
||||
usage: '${service}',
|
||||
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)',
|
||||
docDescribe: true,
|
||||
group: 'auth',
|
||||
service: ['crunchy', 'ao'],
|
||||
service: ['crunchy'],
|
||||
type: 'string',
|
||||
usage: '${token}',
|
||||
default: {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ const hdPflCfgFile = path.join(workingDir, 'config', 'hd_profile');
|
|||
const sessCfgFile = {
|
||||
cr: path.join(workingDir, 'config', 'cr_sess'),
|
||||
hd: path.join(workingDir, 'config', 'hd_sess'),
|
||||
ao: path.join(workingDir, 'config', 'ao_sess'),
|
||||
adn: path.join(workingDir, 'config', 'adn_sess')
|
||||
};
|
||||
const stateFile = path.join(workingDir, 'config', 'guistate');
|
||||
|
|
@ -34,7 +33,6 @@ const tokenFile = {
|
|||
cr: path.join(workingDir, 'config', 'cr_token'),
|
||||
hd: path.join(workingDir, 'config', 'hd_token'),
|
||||
hdNew: path.join(workingDir, 'config', 'hd_new_token'),
|
||||
ao: path.join(workingDir, 'config', 'ao_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 = () => {
|
||||
let session = loadYamlCfgFile(sessCfgFile.hd, true);
|
||||
if (typeof session !== 'object' || session === null || Array.isArray(session)) {
|
||||
|
|
@ -392,8 +372,6 @@ export {
|
|||
loadNewHDToken,
|
||||
saveHDProfile,
|
||||
loadHDProfile,
|
||||
saveAOToken,
|
||||
loadAOToken,
|
||||
getState,
|
||||
setState,
|
||||
writeYamlCfgFile,
|
||||
|
|
|
|||
|
|
@ -14,9 +14,6 @@ export type DataType = {
|
|||
hidive: {
|
||||
s: ItemType;
|
||||
};
|
||||
ao: {
|
||||
s: ItemType;
|
||||
};
|
||||
adn: {
|
||||
s: ItemType;
|
||||
};
|
||||
|
|
@ -36,10 +33,6 @@ const addToArchive = (
|
|||
service: 'hidive';
|
||||
type: 's';
|
||||
}
|
||||
| {
|
||||
service: 'ao';
|
||||
type: 's';
|
||||
}
|
||||
| {
|
||||
service: 'adn';
|
||||
type: 's';
|
||||
|
|
@ -59,16 +52,7 @@ const addToArchive = (
|
|||
});
|
||||
(data as any)[kind.service][kind.type] = items;
|
||||
} else {
|
||||
if (kind.service === 'ao') {
|
||||
data['ao'] = {
|
||||
s: [
|
||||
{
|
||||
id: ID,
|
||||
already: []
|
||||
}
|
||||
]
|
||||
};
|
||||
} else if (kind.service === 'crunchy') {
|
||||
if (kind.service === 'crunchy') {
|
||||
data['crunchy'] = {
|
||||
s: ([] as ItemType).concat(
|
||||
kind.type === 's'
|
||||
|
|
@ -120,10 +104,6 @@ const downloaded = (
|
|||
service: 'hidive';
|
||||
type: 's';
|
||||
}
|
||||
| {
|
||||
service: 'ao';
|
||||
type: 's';
|
||||
}
|
||||
| {
|
||||
service: 'adn';
|
||||
type: 's';
|
||||
|
|
@ -150,7 +130,7 @@ const downloaded = (
|
|||
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 ret: Partial<ArgvType>[] = [];
|
||||
const kind = data[service];
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ function hasDisplay(): boolean {
|
|||
// req
|
||||
export class Req {
|
||||
private sessCfg: string;
|
||||
private service: 'cr' | 'hd' | 'ao' | 'adn';
|
||||
private service: 'cr' | 'hd' | 'adn';
|
||||
private session: Record<
|
||||
string,
|
||||
{
|
||||
|
|
@ -51,7 +51,7 @@ export class Req {
|
|||
private domain: Record<string, unknown>,
|
||||
private debug: boolean,
|
||||
private nosess = false,
|
||||
private type: 'cr' | 'hd' | 'ao' | 'adn'
|
||||
private type: 'cr' | 'hd' | 'adn'
|
||||
) {
|
||||
this.sessCfg = yamlCfg.sessCfgFile[type];
|
||||
this.service = type;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ export type LanguageItem = {
|
|||
hd_locale?: string;
|
||||
adn_locale?: string;
|
||||
new_hd_locale?: string;
|
||||
ao_locale?: string;
|
||||
locale: string;
|
||||
code: string;
|
||||
name: string;
|
||||
|
|
@ -13,13 +12,13 @@ export type 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-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-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: '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: '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' },
|
||||
|
|
@ -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: '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: '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
|
||||
|
|
@ -96,19 +95,6 @@ const searchLocales = (() => {
|
|||
];
|
||||
})();
|
||||
|
||||
export const aoSearchLocales = (() => {
|
||||
return [
|
||||
'',
|
||||
...new Set(
|
||||
languages
|
||||
.map((l) => {
|
||||
return l.ao_locale;
|
||||
})
|
||||
.slice(0, -1)
|
||||
)
|
||||
];
|
||||
})();
|
||||
|
||||
// convert
|
||||
const fixLanguageTag = (tag: string) => {
|
||||
tag = typeof tag == 'string' ? tag : 'und';
|
||||
|
|
|
|||
|
|
@ -80,11 +80,6 @@ export async function parse(manifest: string, language?: LanguageItem, url?: str
|
|||
const options: RequestInit = {
|
||||
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);
|
||||
if (!item.ok)
|
||||
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 = {
|
||||
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);
|
||||
if (!item.ok)
|
||||
console.warn(
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"name": "multi-downloader-nx",
|
||||
"short_name": "aniDL",
|
||||
"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": [
|
||||
"download",
|
||||
"downloader",
|
||||
|
|
@ -90,7 +90,7 @@
|
|||
"protoc": "^1.1.3",
|
||||
"removeNPMAbsolutePaths": "^3.0.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.9.2",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.45.0"
|
||||
},
|
||||
"scripts": {
|
||||
|
|
|
|||
125
pnpm-lock.yaml
125
pnpm-lock.yaml
|
|
@ -128,10 +128,10 @@ importers:
|
|||
version: 17.0.33
|
||||
'@typescript-eslint/eslint-plugin':
|
||||
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':
|
||||
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':
|
||||
specifier: ^6.7.0
|
||||
version: 6.7.0
|
||||
|
|
@ -152,13 +152,13 @@ importers:
|
|||
version: 3.0.1
|
||||
ts-node:
|
||||
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:
|
||||
specifier: ^5.9.2
|
||||
version: 5.9.2
|
||||
specifier: ^5.9.3
|
||||
version: 5.9.3
|
||||
typescript-eslint:
|
||||
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:
|
||||
|
||||
|
|
@ -763,14 +763,6 @@ packages:
|
|||
resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==}
|
||||
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:
|
||||
resolution: {integrity: sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==}
|
||||
peerDependencies:
|
||||
|
|
@ -1286,16 +1278,12 @@ packages:
|
|||
function-bind@1.1.2:
|
||||
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:
|
||||
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
||||
engines: {node: 6.* || 8.* || >= 10.*}
|
||||
|
||||
get-intrinsic@1.3.1:
|
||||
resolution: {integrity: sha512-fk1ZVEeOX9hVZ6QzoBNEC55+Ucqg4sTVwrVuigZhuRPESVFpMyXnd3sbXvPOwp7Y9riVyANiqhEuRF0G1aVSeQ==}
|
||||
get-intrinsic@1.3.0:
|
||||
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
get-proto@1.0.1:
|
||||
|
|
@ -2137,8 +2125,8 @@ packages:
|
|||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
typescript@5.9.2:
|
||||
resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==}
|
||||
typescript@5.9.3:
|
||||
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
|
|
@ -2673,41 +2661,41 @@ snapshots:
|
|||
'@types/node': 24.6.0
|
||||
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:
|
||||
'@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/type-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.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.3)
|
||||
'@typescript-eslint/visitor-keys': 8.45.0
|
||||
eslint: 9.36.0
|
||||
graphemer: 1.4.0
|
||||
ignore: 7.0.5
|
||||
natural-compare: 1.4.0
|
||||
ts-api-utils: 2.1.0(typescript@5.9.2)
|
||||
typescript: 5.9.2
|
||||
ts-api-utils: 2.1.0(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- 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:
|
||||
'@typescript-eslint/scope-manager': 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
|
||||
debug: 4.4.3
|
||||
eslint: 9.36.0
|
||||
typescript: 5.9.2
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- 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:
|
||||
'@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
|
||||
debug: 4.4.3
|
||||
typescript: 5.9.2
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
|
|
@ -2716,28 +2704,28 @@ snapshots:
|
|||
'@typescript-eslint/types': 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:
|
||||
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:
|
||||
'@typescript-eslint/types': 8.45.0
|
||||
'@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.2)
|
||||
'@typescript-eslint/utils': 8.45.0(eslint@9.36.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.3)
|
||||
debug: 4.4.3
|
||||
eslint: 9.36.0
|
||||
ts-api-utils: 2.1.0(typescript@5.9.2)
|
||||
typescript: 5.9.2
|
||||
ts-api-utils: 2.1.0(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@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:
|
||||
'@typescript-eslint/project-service': 8.45.0(typescript@5.9.2)
|
||||
'@typescript-eslint/tsconfig-utils': 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.3)
|
||||
'@typescript-eslint/types': 8.45.0
|
||||
'@typescript-eslint/visitor-keys': 8.45.0
|
||||
debug: 4.4.3
|
||||
|
|
@ -2745,19 +2733,19 @@ snapshots:
|
|||
is-glob: 4.0.3
|
||||
minimatch: 9.0.5
|
||||
semver: 7.7.2
|
||||
ts-api-utils: 2.1.0(typescript@5.9.2)
|
||||
typescript: 5.9.2
|
||||
ts-api-utils: 2.1.0(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- 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:
|
||||
'@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0)
|
||||
'@typescript-eslint/scope-manager': 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
|
||||
typescript: 5.9.2
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
|
|
@ -2869,10 +2857,6 @@ snapshots:
|
|||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
async-function@1.0.0: {}
|
||||
|
||||
async-generator-function@1.0.0: {}
|
||||
|
||||
b4a@1.7.3: {}
|
||||
|
||||
balanced-match@1.0.2: {}
|
||||
|
|
@ -2995,7 +2979,7 @@ snapshots:
|
|||
call-bound@1.0.4:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
get-intrinsic: 1.3.1
|
||||
get-intrinsic: 1.3.0
|
||||
|
||||
callsites@3.1.0: {}
|
||||
|
||||
|
|
@ -3445,20 +3429,15 @@ snapshots:
|
|||
|
||||
function-bind@1.1.2: {}
|
||||
|
||||
generator-function@2.0.0: {}
|
||||
|
||||
get-caller-file@2.0.5: {}
|
||||
|
||||
get-intrinsic@1.3.1:
|
||||
get-intrinsic@1.3.0:
|
||||
dependencies:
|
||||
async-function: 1.0.0
|
||||
async-generator-function: 1.0.0
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
es-define-property: 1.0.1
|
||||
es-errors: 1.3.0
|
||||
es-object-atoms: 1.1.1
|
||||
function-bind: 1.1.2
|
||||
generator-function: 2.0.0
|
||||
get-proto: 1.0.1
|
||||
gopd: 1.2.0
|
||||
has-symbols: 1.1.0
|
||||
|
|
@ -4154,14 +4133,14 @@ snapshots:
|
|||
dependencies:
|
||||
call-bound: 1.0.4
|
||||
es-errors: 1.3.0
|
||||
get-intrinsic: 1.3.1
|
||||
get-intrinsic: 1.3.0
|
||||
object-inspect: 1.13.4
|
||||
|
||||
side-channel-weakmap@1.0.2:
|
||||
dependencies:
|
||||
call-bound: 1.0.4
|
||||
es-errors: 1.3.0
|
||||
get-intrinsic: 1.3.1
|
||||
get-intrinsic: 1.3.0
|
||||
object-inspect: 1.13.4
|
||||
side-channel-map: 1.0.1
|
||||
|
||||
|
|
@ -4325,11 +4304,11 @@ snapshots:
|
|||
|
||||
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:
|
||||
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:
|
||||
'@cspotcode/source-map-support': 0.8.1
|
||||
'@tsconfig/node10': 1.0.11
|
||||
|
|
@ -4343,7 +4322,7 @@ snapshots:
|
|||
create-require: 1.1.1
|
||||
diff: 4.0.2
|
||||
make-error: 1.3.6
|
||||
typescript: 5.9.2
|
||||
typescript: 5.9.3
|
||||
v8-compile-cache-lib: 3.0.1
|
||||
yn: 3.1.1
|
||||
|
||||
|
|
@ -4365,20 +4344,20 @@ snapshots:
|
|||
|
||||
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:
|
||||
'@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/parser': 8.45.0(eslint@9.36.0)(typescript@5.9.2)
|
||||
'@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.2)
|
||||
'@typescript-eslint/utils': 8.45.0(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.3)
|
||||
'@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.45.0(eslint@9.36.0)(typescript@5.9.3)
|
||||
eslint: 9.36.0
|
||||
typescript: 5.9.2
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
typescript@5.4.5: {}
|
||||
|
||||
typescript@5.9.2: {}
|
||||
typescript@5.9.3: {}
|
||||
|
||||
ufo@1.6.1: {}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue