mirror of
https://github.com/anidl/multi-downloader-nx.git
synced 2026-03-11 17:45:30 +00:00
50% crunchy-beta.ts
This commit is contained in:
parent
4a83046116
commit
58697e8b0f
16 changed files with 737 additions and 381 deletions
122
@types/crunchyEpisodeList.d.ts
vendored
Normal file
122
@types/crunchyEpisodeList.d.ts
vendored
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
export interface CrunchyEpisodeList {
|
||||
__class__: string;
|
||||
__href__: string;
|
||||
__resource_key__: string;
|
||||
__links__: Actions;
|
||||
__actions__: Actions;
|
||||
total: number;
|
||||
items: Item[];
|
||||
}
|
||||
|
||||
export interface Actions {
|
||||
}
|
||||
|
||||
export interface Item {
|
||||
__class__: Class;
|
||||
__href__: string;
|
||||
__resource_key__: string;
|
||||
__links__: Links;
|
||||
__actions__: Actions;
|
||||
id: string;
|
||||
channel_id: ChannelID;
|
||||
series_id: string;
|
||||
series_title: string;
|
||||
series_slug_title: string;
|
||||
season_id: string;
|
||||
season_title: string;
|
||||
season_slug_title: string;
|
||||
season_number: number;
|
||||
episode: string;
|
||||
episode_number: number | null;
|
||||
sequence_number: number;
|
||||
production_episode_id: string;
|
||||
title: string;
|
||||
slug_title: string;
|
||||
description: string;
|
||||
next_episode_id?: string;
|
||||
next_episode_title?: string;
|
||||
hd_flag: boolean;
|
||||
is_mature: boolean;
|
||||
mature_blocked: boolean;
|
||||
episode_air_date: string;
|
||||
is_subbed: boolean;
|
||||
is_dubbed: boolean;
|
||||
is_clip: boolean;
|
||||
seo_title: string;
|
||||
seo_description: string;
|
||||
season_tags: string[];
|
||||
available_offline: boolean;
|
||||
media_type: Class;
|
||||
slug: string;
|
||||
images: Images;
|
||||
duration_ms: number;
|
||||
ad_breaks: AdBreak[];
|
||||
is_premium_only: boolean;
|
||||
listing_id: string;
|
||||
subtitle_locales: SubtitleLocale[];
|
||||
playback?: string;
|
||||
availability_notes: string;
|
||||
available_date?: string;
|
||||
hide_season_title?: boolean;
|
||||
hide_season_number?: boolean;
|
||||
isSelected?: boolean;
|
||||
seq_id: string;
|
||||
}
|
||||
|
||||
export enum Class {
|
||||
Episode = "episode",
|
||||
}
|
||||
|
||||
export interface Links {
|
||||
ads: Ads;
|
||||
"episode/channel": Ads;
|
||||
"episode/next_episode"?: Ads;
|
||||
"episode/season": Ads;
|
||||
"episode/series": Ads;
|
||||
streams?: Ads;
|
||||
}
|
||||
|
||||
export interface Ads {
|
||||
href: string;
|
||||
}
|
||||
|
||||
export interface AdBreak {
|
||||
type: AdBreakType;
|
||||
offset_ms: number;
|
||||
}
|
||||
|
||||
export enum AdBreakType {
|
||||
Midroll = "midroll",
|
||||
Preroll = "preroll",
|
||||
}
|
||||
|
||||
export enum ChannelID {
|
||||
Crunchyroll = "crunchyroll",
|
||||
}
|
||||
|
||||
export interface Images {
|
||||
thumbnail: Array<Thumbnail[]>;
|
||||
}
|
||||
|
||||
export interface Thumbnail {
|
||||
width: number;
|
||||
height: number;
|
||||
type: ThumbnailType;
|
||||
source: string;
|
||||
}
|
||||
|
||||
export enum ThumbnailType {
|
||||
Thumbnail = "thumbnail",
|
||||
}
|
||||
|
||||
export enum SubtitleLocale {
|
||||
ArSA = "ar-SA",
|
||||
DeDE = "de-DE",
|
||||
EnUS = "en-US",
|
||||
Es419 = "es-419",
|
||||
EsES = "es-ES",
|
||||
FrFR = "fr-FR",
|
||||
ItIT = "it-IT",
|
||||
PtBR = "pt-BR",
|
||||
RuRU = "ru-RU",
|
||||
}
|
||||
175
@types/crunchySearch.d.ts
vendored
Normal file
175
@types/crunchySearch.d.ts
vendored
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
// Generated by https://quicktype.io
|
||||
|
||||
export interface CrunchySearch {
|
||||
__class__: string;
|
||||
__href__: string;
|
||||
__resource_key__: string;
|
||||
__links__: CrunchySearchLinks;
|
||||
__actions__: Actions;
|
||||
total: number;
|
||||
items: CrunchySearchItem[];
|
||||
}
|
||||
|
||||
export interface Actions {
|
||||
}
|
||||
|
||||
export interface CrunchySearchLinks {
|
||||
continuation?: Continuation;
|
||||
}
|
||||
|
||||
export interface Continuation {
|
||||
href: string;
|
||||
}
|
||||
|
||||
export interface CrunchySearchItem {
|
||||
__class__: string;
|
||||
__href__: string;
|
||||
__resource_key__: string;
|
||||
__links__: CrunchySearchLinks;
|
||||
__actions__: Actions;
|
||||
type: string;
|
||||
total: number;
|
||||
items: ItemItem[];
|
||||
}
|
||||
|
||||
export interface ItemItem {
|
||||
__actions__: Actions;
|
||||
__class__: Class;
|
||||
__href__: string;
|
||||
__links__: PurpleLinks;
|
||||
channel_id: ChannelID;
|
||||
description: string;
|
||||
external_id: string;
|
||||
id: string;
|
||||
images: Images;
|
||||
linked_resource_key: string;
|
||||
new: boolean;
|
||||
new_content: boolean;
|
||||
promo_description: string;
|
||||
promo_title: string;
|
||||
search_metadata: SearchMetadata;
|
||||
series_metadata?: SeriesMetadata;
|
||||
slug: string;
|
||||
slug_title: string;
|
||||
title: string;
|
||||
type: ItemType;
|
||||
episode_metadata?: EpisodeMetadata;
|
||||
playback?: string;
|
||||
isSelected?: boolean;
|
||||
season_number?: string;
|
||||
is_premium_only?: boolean;
|
||||
hide_metadata?: boolean;
|
||||
seq_id?: string;
|
||||
f_num?: string;
|
||||
s_num?: string;
|
||||
ep_num?: string;
|
||||
last_public?: string;
|
||||
subtitle_locales?: string[];
|
||||
availability_notes?: string
|
||||
}
|
||||
|
||||
export enum Class {
|
||||
Panel = "panel",
|
||||
}
|
||||
|
||||
export interface PurpleLinks {
|
||||
resource: Continuation;
|
||||
"resource/channel": Continuation;
|
||||
"episode/season"?: Continuation;
|
||||
"episode/series"?: Continuation;
|
||||
streams?: Continuation;
|
||||
}
|
||||
|
||||
export enum ChannelID {
|
||||
Crunchyroll = "crunchyroll",
|
||||
}
|
||||
|
||||
export interface EpisodeMetadata {
|
||||
ad_breaks: AdBreak[];
|
||||
availability_notes: string;
|
||||
available_offline: boolean;
|
||||
duration_ms: number;
|
||||
episode: string;
|
||||
episode_air_date: string;
|
||||
episode_number: number;
|
||||
is_clip: boolean;
|
||||
is_dubbed: boolean;
|
||||
is_mature: boolean;
|
||||
is_premium_only: boolean;
|
||||
is_subbed: boolean;
|
||||
mature_blocked: boolean;
|
||||
maturity_ratings: string[];
|
||||
season_id: string;
|
||||
season_number: number;
|
||||
season_slug_title: string;
|
||||
season_title: string;
|
||||
sequence_number: number;
|
||||
series_id: string;
|
||||
series_slug_title: string;
|
||||
series_title: string;
|
||||
subtitle_locales: string[];
|
||||
tenant_categories?: TenantCategory[];
|
||||
available_date?: string;
|
||||
free_available_date?: string;
|
||||
}
|
||||
|
||||
export interface AdBreak {
|
||||
offset_ms: number;
|
||||
type: AdBreakType;
|
||||
}
|
||||
|
||||
export enum AdBreakType {
|
||||
Midroll = "midroll",
|
||||
Preroll = "preroll",
|
||||
}
|
||||
|
||||
export enum TenantCategory {
|
||||
Action = "Action",
|
||||
Drama = "Drama",
|
||||
SciFi = "Sci-Fi",
|
||||
}
|
||||
|
||||
export interface Images {
|
||||
poster_tall?: Array<PosterTall[]>;
|
||||
poster_wide?: Array<PosterTall[]>;
|
||||
thumbnail?: Array<PosterTall[]>;
|
||||
}
|
||||
|
||||
export interface PosterTall {
|
||||
height: number;
|
||||
source: string;
|
||||
type: PosterTallType;
|
||||
width: number;
|
||||
}
|
||||
|
||||
export enum PosterTallType {
|
||||
PosterTall = "poster_tall",
|
||||
PosterWide = "poster_wide",
|
||||
Thumbnail = "thumbnail",
|
||||
}
|
||||
|
||||
export interface SearchMetadata {
|
||||
score: number;
|
||||
}
|
||||
|
||||
export interface SeriesMetadata {
|
||||
availability_notes: string;
|
||||
episode_count: number;
|
||||
extended_description: string;
|
||||
is_dubbed: boolean;
|
||||
is_mature: boolean;
|
||||
is_simulcast: boolean;
|
||||
is_subbed: boolean;
|
||||
mature_blocked: boolean;
|
||||
maturity_ratings: string[];
|
||||
season_count: number;
|
||||
tenant_categories: TenantCategory[];
|
||||
}
|
||||
|
||||
export enum ItemType {
|
||||
Episode = "episode",
|
||||
Series = "series",
|
||||
Season = 'season',
|
||||
MovieListing = 'movie_listing',
|
||||
Movie = 'Movie'
|
||||
}
|
||||
26
@types/crunchyTypes.ts
Normal file
26
@types/crunchyTypes.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
export type CrunchyEpMeta = {
|
||||
mediaId: string,
|
||||
seasonTitle: string,
|
||||
episodeNumber: string,
|
||||
episodeTitle: string,
|
||||
playback?: string
|
||||
}
|
||||
|
||||
export type ParseItem = {
|
||||
isSelected?: boolean,
|
||||
type?: string,
|
||||
id: string,
|
||||
title: string,
|
||||
playback?: string,
|
||||
season_number?: number|string,
|
||||
is_premium_only?: boolean,
|
||||
hide_metadata?: boolean,
|
||||
seq_id?: string,
|
||||
f_num?: string,
|
||||
s_num?: string
|
||||
external_id?: string,
|
||||
ep_num?: string
|
||||
last_public?: string,
|
||||
subtitle_locales?: string[],
|
||||
availability_notes?: string
|
||||
}
|
||||
12
@types/sei-helper.d.ts
vendored
12
@types/sei-helper.d.ts
vendored
|
|
@ -1,5 +1,15 @@
|
|||
declare module 'sei-helper' {
|
||||
export async function question(qStr: string): string;
|
||||
export async function question(qStr: string): Promise<string>;
|
||||
export function cleanupFilename(str: string): string;
|
||||
export function exec(str: string, str1: string, str2: string);
|
||||
export const cookie: {
|
||||
parse: (data: Record<string, string>) => Record<string, {
|
||||
value: string;
|
||||
expires: Date;
|
||||
path: string;
|
||||
domain: string;
|
||||
secure: boolean;
|
||||
}>
|
||||
}
|
||||
export function formatTime(time: number): string
|
||||
}
|
||||
|
|
@ -1,40 +1,44 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
// build-in
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
import path from 'path';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
// package program
|
||||
const packageJson = require('./package.json');
|
||||
import packageJson from './package.json';
|
||||
console.log(`\n=== Crunchyroll Beta Downloader NX ${packageJson.version} ===\n`);
|
||||
|
||||
// plugins
|
||||
const shlp = require('sei-helper');
|
||||
const m3u8 = require('m3u8-parsed');
|
||||
const streamdl = require('hls-download');
|
||||
import shlp from 'sei-helper';
|
||||
import m3u8 from 'm3u8-parsed';
|
||||
import streamdl from 'hls-download';
|
||||
|
||||
// custom modules
|
||||
const fontsData = require('./crunchy/modules/module.fontsData');
|
||||
const langsData = require('./crunchy/modules/module.langsData');
|
||||
const yamlCfg = require('./crunchy/modules/module.cfg-loader');
|
||||
const yargs = require('./crunchy/modules/module.app-args');
|
||||
const epsFilter = require('./crunchy/modules/module.eps-filter');
|
||||
const appMux = require('./crunchy/modules/module.muxing');
|
||||
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 epsFilter from './modules/module.eps-filter';
|
||||
import Merger from './modules/module.merger';
|
||||
|
||||
// new-cfg paths
|
||||
const cfg = yamlCfg.loadCfg();
|
||||
let token = yamlCfg.loadCRToken();
|
||||
let cmsToken = {};
|
||||
let cmsToken: {
|
||||
cms?: Record<string, string>
|
||||
} = {};
|
||||
|
||||
// args
|
||||
const appYargs = new yargs(cfg.cli, langsData, true);
|
||||
const argv = appYargs.appArgv();
|
||||
const argv = yargs.appArgv(cfg.cli)
|
||||
argv.appstore = {};
|
||||
|
||||
// load req
|
||||
const { domain, api } = require('./crunchy/modules/module.api-urls');
|
||||
const reqModule = require('./crunchy/modules/module.req');
|
||||
const req = new reqModule.Req(domain, argv, true);
|
||||
import { domain, api } from './modules/module.api-urls';
|
||||
import * as reqModule from './modules/module.req';
|
||||
import { CrunchySearch, ItemItem, ItemType } from './@types/crunchySearch';
|
||||
import { CrunchyEpisodeList } from './@types/crunchyEpisodeList';
|
||||
import { CrunchyEpMeta, ParseItem } from './@types/crunchyTypes';
|
||||
const req = new reqModule.Req(domain, argv);
|
||||
|
||||
// select
|
||||
(async () => {
|
||||
|
|
@ -84,7 +88,7 @@ const req = new reqModule.Req(domain, argv, true);
|
|||
async function getFonts(){
|
||||
console.log('[INFO] Downloading fonts...');
|
||||
for(const f of Object.keys(fontsData.fonts)){
|
||||
const fontFile = fontsData.fonts[f];
|
||||
const fontFile = fontsData.fonts[f as fontsData.AvailableFonts];
|
||||
const fontLoc = path.join(cfg.dir.fonts, fontFile);
|
||||
if(fs.existsSync(fontLoc) && fs.statSync(fontLoc).size != 0){
|
||||
console.log(`[INFO] ${f} (${fontFile}) already downloaded!`);
|
||||
|
|
@ -99,8 +103,8 @@ async function getFonts(){
|
|||
}
|
||||
catch(e){}
|
||||
const fontUrl = fontsData.root + fontFile;
|
||||
const getFont = await req.getData(fontUrl, { useProxy: true, binary: true });
|
||||
if(getFont.ok){
|
||||
const getFont = await req.getData<Buffer>(fontUrl, { binary: true });
|
||||
if(getFont.ok && getFont.res){
|
||||
fs.writeFileSync(fontLoc, getFont.res.body);
|
||||
console.log(`[INFO] Downloaded: ${f} (${fontFile})`);
|
||||
}
|
||||
|
|
@ -114,22 +118,21 @@ async function getFonts(){
|
|||
|
||||
// auth method
|
||||
async function doAuth(){
|
||||
const iLogin = argv.user ? argv.user : await shlp.question('[Q] LOGIN/EMAIL');
|
||||
const iPsswd = argv.pass ? argv.pass : await shlp.question('[Q] PASSWORD ');
|
||||
const iLogin = await shlp.question('[Q] LOGIN/EMAIL');
|
||||
const iPsswd = await shlp.question('[Q] PASSWORD ');
|
||||
const authData = new URLSearchParams({
|
||||
'username': iLogin,
|
||||
'password': iPsswd,
|
||||
'grant_type': 'password',
|
||||
'scope': 'offline_access'
|
||||
}).toString();
|
||||
const authReqOpts = {
|
||||
const authReqOpts: reqModule.Params = {
|
||||
method: 'POST',
|
||||
headers: api.beta_authHeaderMob,
|
||||
body: authData,
|
||||
useProxy: true
|
||||
body: authData
|
||||
};
|
||||
const authReq = await req.getData(api.beta_auth, authReqOpts);
|
||||
if(!authReq.ok){
|
||||
if(!authReq.ok || !authReq.res){
|
||||
console.log('[ERROR] Authentication failed!');
|
||||
return;
|
||||
}
|
||||
|
|
@ -152,7 +155,7 @@ async function getProfile(){
|
|||
useProxy: true
|
||||
};
|
||||
const profileReq = await req.getData(api.beta_profile, profileReqOptions);
|
||||
if(!profileReq.ok){
|
||||
if(!profileReq.ok || !profileReq.res){
|
||||
console.log('[ERROR] Get profile failed!');
|
||||
return;
|
||||
}
|
||||
|
|
@ -166,14 +169,13 @@ async function doAnonymousAuth(){
|
|||
'grant_type': 'client_id',
|
||||
'scope': 'offline_access',
|
||||
}).toString();
|
||||
const authReqOpts = {
|
||||
const authReqOpts: reqModule.Params = {
|
||||
method: 'POST',
|
||||
headers: api.beta_authHeaderMob,
|
||||
body: authData,
|
||||
useProxy: true
|
||||
body: authData
|
||||
};
|
||||
const authReq = await req.getData(api.beta_auth, authReqOpts);
|
||||
if(!authReq.ok){
|
||||
if(!authReq.ok || !authReq.res){
|
||||
console.log('[ERROR] Authentication failed!');
|
||||
return;
|
||||
}
|
||||
|
|
@ -196,14 +198,13 @@ async function refreshToken(){
|
|||
'grant_type': 'refresh_token',
|
||||
'scope': 'offline_access'
|
||||
}).toString();
|
||||
const authReqOpts = {
|
||||
const authReqOpts: reqModule.Params = {
|
||||
method: 'POST',
|
||||
headers: api.beta_authHeaderMob,
|
||||
body: authData,
|
||||
useProxy: true
|
||||
body: authData
|
||||
};
|
||||
const authReq = await req.getData(api.beta_auth, authReqOpts);
|
||||
if(!authReq.ok){
|
||||
if(!authReq.ok || !authReq.res){
|
||||
console.log('[ERROR] Authentication failed!');
|
||||
return;
|
||||
}
|
||||
|
|
@ -232,12 +233,12 @@ async function getCMStoken(){
|
|||
useProxy: true
|
||||
};
|
||||
const cmsTokenReq = await req.getData(api.beta_cmsToken, cmsTokenReqOpts);
|
||||
if(!cmsTokenReq.ok){
|
||||
if(!cmsTokenReq.ok || !cmsTokenReq.res){
|
||||
console.log('[ERROR] Authentication CMS token failed!');
|
||||
return;
|
||||
}
|
||||
cmsToken = JSON.parse(cmsTokenReq.res.body);
|
||||
console.log('[INFO] Your Country: %s\n', cmsToken.cms.bucket.split('/')[1]);
|
||||
console.log('[INFO] Your Country: %s\n', cmsToken.cms?.bucket.split('/')[1]);
|
||||
}
|
||||
|
||||
async function getCmsData(){
|
||||
|
|
@ -257,8 +258,8 @@ async function getCmsData(){
|
|||
'Key-Pair-Id': cmsToken.cms.key_pair_id,
|
||||
}),
|
||||
].join('');
|
||||
const indexReq = await req.getData(indexReqOpts, { useProxy: true });
|
||||
if(!indexReq.ok){
|
||||
const indexReq = await req.getData(indexReqOpts);
|
||||
if(!indexReq.ok || ! indexReq.res){
|
||||
console.log('[ERROR] Get CMS index FAILED!');
|
||||
return;
|
||||
}
|
||||
|
|
@ -277,18 +278,18 @@ async function doSearch(){
|
|||
useProxy: true
|
||||
};
|
||||
const searchParams = new URLSearchParams({
|
||||
q: argv.search,
|
||||
n: 5,
|
||||
start: argv.page ? (parseInt(argv.page)-1)*5 : 0,
|
||||
q: argv.search as string,
|
||||
n: "5",
|
||||
start: argv.page ? `${(argv.page-1)*5}` : "0",
|
||||
type: argv['search-type'],
|
||||
locale: argv['search-locale'],
|
||||
}).toString();
|
||||
let searchReq = await req.getData(`${api.beta_search}?${searchParams}`, searchReqOpts);
|
||||
if(!searchReq.ok){
|
||||
if(!searchReq.ok || ! searchReq.res){
|
||||
console.log('[ERROR] Search FAILED!');
|
||||
return;
|
||||
}
|
||||
let searchResults = JSON.parse(searchReq.res.body);
|
||||
let searchResults = JSON.parse(searchReq.res.body) as CrunchySearch;
|
||||
if(searchResults.total < 1){
|
||||
console.log('[INFO] Nothing Found!');
|
||||
return;
|
||||
|
|
@ -300,9 +301,9 @@ async function doSearch(){
|
|||
'episode': 'Found episodes'
|
||||
};
|
||||
for(let search_item of searchResults.items){
|
||||
console.log('[INFO] %s:', searchTypesInfo[search_item.type]);
|
||||
console.log('[INFO] %s:', searchTypesInfo[search_item.type as keyof typeof searchTypesInfo]);
|
||||
// calculate pages
|
||||
let itemPad = parseInt(new URL(search_item.__href__, domain.api_beta).searchParams.get('start'));
|
||||
let itemPad = parseInt(new URL(search_item.__href__, domain.api_beta).searchParams.get('start') || '');
|
||||
let pageCur = itemPad > 0 ? Math.ceil(itemPad/5) + 1 : 1;
|
||||
let pageMax = Math.ceil(search_item.total/5);
|
||||
// pages per category
|
||||
|
|
@ -322,16 +323,17 @@ async function doSearch(){
|
|||
}
|
||||
}
|
||||
|
||||
async function parseObject(item, pad, getSeries, getMovieListing){
|
||||
async function parseObject(item: ParseItem, pad?: number, getSeries?: boolean, getMovieListing?: boolean){
|
||||
if(argv.debug){
|
||||
console.log(item);
|
||||
}
|
||||
pad = typeof pad == 'number' ? pad : 2;
|
||||
getSeries = typeof getSeries == 'boolean' ? getSeries : true;
|
||||
getMovieListing = typeof getMovieListing == 'boolean' ? getMovieListing : true;
|
||||
item.isSelected = typeof item.isSelected == 'boolean' ? item.isSelected : false;
|
||||
pad = pad || 2;
|
||||
getSeries = getSeries === undefined ? true : getSeries;
|
||||
getMovieListing = getMovieListing === undefined ? true : getMovieListing;
|
||||
item.isSelected = item.isSelected === undefined ? false : item.isSelected;
|
||||
if(!item.type){
|
||||
item.type = item.__class__;
|
||||
console.log('[INFO] Unable to parse type for %s. Defaulted to %s', item.id, ItemType.Episode)
|
||||
item.type = ItemType.Episode;
|
||||
}
|
||||
const oTypes = {
|
||||
'series': 'Z', // SRZ
|
||||
|
|
@ -396,7 +398,7 @@ async function parseObject(item, pad, getSeries, getMovieListing){
|
|||
const showObjectBooleans = oBooleans.length > 0 && !iMetadata.hide_metadata ? true : false;
|
||||
// make obj ids
|
||||
let objects_ids = [];
|
||||
objects_ids.push(oTypes[item.type] + ':' + item.id);
|
||||
objects_ids.push(oTypes[item.type as keyof typeof oTypes] + ':' + item.id);
|
||||
if(item.seq_id){
|
||||
objects_ids.unshift(item.seq_id);
|
||||
}
|
||||
|
|
@ -453,10 +455,10 @@ async function parseObject(item, pad, getSeries, getMovieListing){
|
|||
}
|
||||
}
|
||||
|
||||
async function getSeriesById(pad, hideSeriesTitle){
|
||||
async function getSeriesById(pad?: number, hideSeriesTitle?: boolean){
|
||||
// parse
|
||||
pad = typeof pad == 'number' ? pad : 0;
|
||||
hideSeriesTitle = typeof hideSeriesTitle == 'boolean' ? hideSeriesTitle : false;
|
||||
pad = pad || 0;
|
||||
hideSeriesTitle = hideSeriesTitle !== undefined ? hideSeriesTitle : false;
|
||||
// check token
|
||||
if(!cmsToken.cms){
|
||||
console.log('[ERROR] Authentication required!');
|
||||
|
|
@ -480,7 +482,7 @@ async function getSeriesById(pad, hideSeriesTitle){
|
|||
cmsToken.cms.bucket,
|
||||
'/seasons?',
|
||||
new URLSearchParams({
|
||||
'series_id': argv.series,
|
||||
'series_id': argv.series as string,
|
||||
'Policy': cmsToken.cms.policy,
|
||||
'Signature': cmsToken.cms.signature,
|
||||
'Key-Pair-Id': cmsToken.cms.key_pair_id,
|
||||
|
|
@ -488,8 +490,8 @@ async function getSeriesById(pad, hideSeriesTitle){
|
|||
].join('');
|
||||
// reqs
|
||||
if(!hideSeriesTitle){
|
||||
const seriesReq = await req.getData(seriesReqOpts, {useProxy: true});
|
||||
if(!seriesReq.ok){
|
||||
const seriesReq = await req.getData(seriesReqOpts);
|
||||
if(!seriesReq.ok || !seriesReq.res){
|
||||
console.log('[ERROR] Series Request FAILED!');
|
||||
return;
|
||||
}
|
||||
|
|
@ -497,8 +499,8 @@ async function getSeriesById(pad, hideSeriesTitle){
|
|||
await parseObject(seriesData, pad, false);
|
||||
}
|
||||
// seasons list
|
||||
const seriesSeasonListReq = await req.getData(seriesSeasonListReqOpts, {useProxy: true});
|
||||
if(!seriesSeasonListReq.ok){
|
||||
const seriesSeasonListReq = await req.getData(seriesSeasonListReqOpts);
|
||||
if(!seriesSeasonListReq.ok || !seriesSeasonListReq.res){
|
||||
console.log('[ERROR] Series Request FAILED!');
|
||||
return;
|
||||
}
|
||||
|
|
@ -513,8 +515,8 @@ async function getSeriesById(pad, hideSeriesTitle){
|
|||
}
|
||||
}
|
||||
|
||||
async function getMovieListingById(pad){
|
||||
pad = typeof pad == 'number' ? pad : 2;
|
||||
async function getMovieListingById(pad?: number){
|
||||
pad = pad || 2;
|
||||
if(!cmsToken.cms){
|
||||
console.log('[ERROR] Authentication required!');
|
||||
return;
|
||||
|
|
@ -524,14 +526,14 @@ async function getMovieListingById(pad){
|
|||
cmsToken.cms.bucket,
|
||||
'/movies?',
|
||||
new URLSearchParams({
|
||||
'movie_listing_id': argv['movie-listing'],
|
||||
'movie_listing_id': argv['movie-listing'] as string,
|
||||
'Policy': cmsToken.cms.policy,
|
||||
'Signature': cmsToken.cms.signature,
|
||||
'Key-Pair-Id': cmsToken.cms.key_pair_id,
|
||||
}),
|
||||
].join('');
|
||||
const movieListingReq = await req.getData(movieListingReqOpts, {useProxy: true});
|
||||
if(!movieListingReq.ok){
|
||||
const movieListingReq = await req.getData(movieListingReqOpts);
|
||||
if(!movieListingReq.ok || !movieListingReq.res){
|
||||
console.log('[ERROR] Movie Listing Request FAILED!');
|
||||
return;
|
||||
}
|
||||
|
|
@ -558,11 +560,11 @@ async function getNewlyAdded(){
|
|||
};
|
||||
const newlyAddedParams = new URLSearchParams({
|
||||
sort_by: 'newly_added',
|
||||
n: 25,
|
||||
start: argv.page ? (parseInt(argv.page)-1)*25 : 0,
|
||||
n: "25",
|
||||
start: (argv.page ? (argv.page-1)*25 : 0).toString(),
|
||||
}).toString();
|
||||
let newlyAddedReq = await req.getData(`${api.beta_browse}?${newlyAddedParams}`, newlyAddedReqOpts);
|
||||
if(!newlyAddedReq.ok){
|
||||
if(!newlyAddedReq.ok || !newlyAddedReq.res){
|
||||
console.log('[ERROR] Get newly added FAILED!');
|
||||
return;
|
||||
}
|
||||
|
|
@ -572,7 +574,7 @@ async function getNewlyAdded(){
|
|||
await parseObject(i, 2);
|
||||
}
|
||||
// calculate pages
|
||||
let itemPad = parseInt(new URL(newlyAddedResults.__href__, domain.api_beta).searchParams.get('start'));
|
||||
let itemPad = parseInt(new URL(newlyAddedResults.__href__, domain.api_beta).searchParams.get('start') as string);
|
||||
let pageCur = itemPad > 0 ? Math.ceil(itemPad/5) + 1 : 1;
|
||||
let pageMax = Math.ceil(newlyAddedResults.total/5);
|
||||
console.log(` [INFO] Total results: ${newlyAddedResults.total} (Page: ${pageCur}/${pageMax})`);
|
||||
|
|
@ -596,8 +598,8 @@ async function getSeasonById(){
|
|||
'Key-Pair-Id': cmsToken.cms.key_pair_id,
|
||||
}),
|
||||
].join('');
|
||||
const showInfoReq = await req.getData(showInfoReqOpts, {useProxy: true});
|
||||
if(!showInfoReq.ok){
|
||||
const showInfoReq = await req.getData(showInfoReqOpts);
|
||||
if(!showInfoReq.ok || !showInfoReq.res){
|
||||
console.log('[ERROR] Show Request FAILED!');
|
||||
return;
|
||||
}
|
||||
|
|
@ -608,20 +610,23 @@ async function getSeasonById(){
|
|||
cmsToken.cms.bucket,
|
||||
'/episodes?',
|
||||
new URLSearchParams({
|
||||
'season_id': argv.season,
|
||||
'season_id': argv.season as string,
|
||||
'Policy': cmsToken.cms.policy,
|
||||
'Signature': cmsToken.cms.signature,
|
||||
'Key-Pair-Id': cmsToken.cms.key_pair_id,
|
||||
}),
|
||||
].join('');
|
||||
const reqEpsList = await req.getData(reqEpsListOpts, {useProxy: true});
|
||||
if(!reqEpsList.ok){
|
||||
const reqEpsList = await req.getData(reqEpsListOpts);
|
||||
if(!reqEpsList.ok || !reqEpsList.res){
|
||||
console.log('[ERROR] Episode List Request FAILED!');
|
||||
return;
|
||||
}
|
||||
let episodeList = JSON.parse(reqEpsList.res.body);
|
||||
let episodeList = JSON.parse(reqEpsList.res.body) as CrunchyEpisodeList;
|
||||
|
||||
const epNumList = { ep: [], sp: 0 };
|
||||
const epNumList: {
|
||||
ep: number[],
|
||||
sp: number
|
||||
} = { ep: [], sp: 0 };
|
||||
const epNumLen = epsFilter.epNumLen;
|
||||
|
||||
if(episodeList.total < 1){
|
||||
|
|
@ -631,7 +636,7 @@ async function getSeasonById(){
|
|||
|
||||
const doEpsFilter = new epsFilter.doFilter();
|
||||
const selEps = doEpsFilter.checkFilter(argv.episode);
|
||||
const selectedMedia = [];
|
||||
const selectedMedia: CrunchyEpMeta[] = [];
|
||||
|
||||
episodeList.items.forEach((item) => {
|
||||
item.hide_season_title = true;
|
||||
|
|
@ -644,7 +649,7 @@ async function getSeasonById(){
|
|||
item.season_title = 'NO_TITLE';
|
||||
}
|
||||
// set data
|
||||
const epMeta = {
|
||||
const epMeta: CrunchyEpMeta = {
|
||||
mediaId: item.id,
|
||||
seasonTitle: item.season_title,
|
||||
episodeNumber: item.episode,
|
||||
|
|
@ -685,7 +690,7 @@ async function getSeasonById(){
|
|||
}
|
||||
|
||||
if(selectedMedia.length > 1){
|
||||
argv.appstore.isBatch = true;
|
||||
(argv.appstore as Record<string, unknown>).isBatch = true;
|
||||
}
|
||||
|
||||
console.log();
|
||||
|
|
@ -1,285 +0,0 @@
|
|||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
|
||||
const shlp = require('sei-helper');
|
||||
const got = require('got');
|
||||
const cookieFile = require('./module.cookieFile');
|
||||
const yamlCfg = require('./module.cfg-loader');
|
||||
const curlReq = require('./module.curl-req');
|
||||
|
||||
// set usable cookies
|
||||
const usefulCookies = {
|
||||
auth: [
|
||||
'etp_rt',
|
||||
'c_visitor',
|
||||
],
|
||||
sess: [
|
||||
'session_id',
|
||||
],
|
||||
};
|
||||
|
||||
// req
|
||||
const Req = class {
|
||||
constructor(domain, argv, is_beta){
|
||||
// settings and cookies
|
||||
this.is_beta = Boolean(is_beta);
|
||||
this.loadSessTxt = this.is_beta ? false : true;
|
||||
// main cfg
|
||||
this.domain = domain;
|
||||
this.argv = argv;
|
||||
// session cfg
|
||||
this.sessCfg = yamlCfg.sessCfgFile,
|
||||
this.session = this.is_beta ? {} : yamlCfg.loadCRSession();
|
||||
this.cfgDir = yamlCfg.cfgFolder;
|
||||
this.curl = false;
|
||||
}
|
||||
async getData (durl, params) {
|
||||
params = params || {};
|
||||
// options
|
||||
let options = {
|
||||
method: params.method ? params.method : 'GET',
|
||||
headers: {
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:90.0) Gecko/20100101 Firefox/90.0',
|
||||
},
|
||||
};
|
||||
// additional params
|
||||
if(params.headers){
|
||||
options.headers = {...options.headers, ...params.headers};
|
||||
}
|
||||
if(options.method == 'POST'){
|
||||
options.headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
}
|
||||
if(params.body){
|
||||
options.body = params.body;
|
||||
}
|
||||
if(params.binary == true){
|
||||
options.responseType = 'buffer';
|
||||
}
|
||||
if(typeof params.followRedirect == 'boolean'){
|
||||
options.followRedirect = params.followRedirect;
|
||||
}
|
||||
// check if cookies.txt exists
|
||||
const sessTxtFile = path.join(this.cfgDir, 'cookies.txt');
|
||||
if(!this.is_beta && this.loadSessTxt && fs.existsSync(sessTxtFile)){
|
||||
const cookiesTxtName = path.basename(sessTxtFile);
|
||||
try{
|
||||
// console.log(`[INFO] Loading custom ${cookiesTxtName} file...`);
|
||||
const netcookie = fs.readFileSync(sessTxtFile, 'utf8');
|
||||
fs.unlinkSync(sessTxtFile);
|
||||
this.setNewCookie('', true, netcookie);
|
||||
}
|
||||
catch(e){
|
||||
console.log(`[ERROR] Cannot load ${cookiesTxtName} file!`);
|
||||
}
|
||||
}
|
||||
this.loadSessTxt = false;
|
||||
// proxy
|
||||
if(params.useProxy && this.argv.proxy && this.argv.curl){
|
||||
try{
|
||||
options.curlProxy = buildProxy(this.argv.proxy);
|
||||
options.curlProxyAuth = this.argv['proxy-auth'];
|
||||
}
|
||||
catch(e){
|
||||
console.log(`[WARN] Not valid proxy URL${e.input?' ('+e.input+')':''}!`);
|
||||
console.log('[WARN] Skipping...\n');
|
||||
this.argv.proxy = false;
|
||||
}
|
||||
}
|
||||
// if auth
|
||||
let cookie = [];
|
||||
const loc = new URL(durl);
|
||||
if(!this.is_beta && Object.values(this.domain).includes(loc.origin)){
|
||||
for(let uCookie of usefulCookies.auth){
|
||||
const checkedCookie = this.checkCookieVal(this.session[uCookie]);
|
||||
if(checkedCookie){
|
||||
cookie.push(uCookie);
|
||||
}
|
||||
}
|
||||
for(let uCookie of usefulCookies.sess){
|
||||
if(this.checkSessId(this.session[uCookie]) && !this.argv.nosess){
|
||||
cookie.push(uCookie);
|
||||
}
|
||||
}
|
||||
if(!params.skipCookies){
|
||||
cookie.push('c_locale');
|
||||
options.headers.Cookie = shlp.cookie.make({
|
||||
...{ c_locale : { value: 'enUS' } },
|
||||
...this.session,
|
||||
}, cookie);
|
||||
}
|
||||
}
|
||||
// avoid cloudflare protection
|
||||
if(loc.origin == this.domain.www){
|
||||
options.minVersion = 'TLSv1.3';
|
||||
options.maxVersion = 'TLSv1.3';
|
||||
options.http2 = true;
|
||||
}
|
||||
// debug
|
||||
options.hooks = {
|
||||
beforeRequest: [
|
||||
(options) => {
|
||||
if(this.argv.debug){
|
||||
console.log('[DEBUG] GOT OPTIONS:');
|
||||
console.log(options);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
if(this.argv.debug){
|
||||
options.curlDebug = true;
|
||||
}
|
||||
// try do request
|
||||
try {
|
||||
let res;
|
||||
if(this.curl && this.argv.curl && Object.values(this.domain).includes(loc.origin)){
|
||||
res = await curlReq(this.curl, durl.toString(), options, this.cfgDir);
|
||||
}
|
||||
else{
|
||||
res = await got(durl.toString(), options);
|
||||
}
|
||||
if(!this.is_beta && !params.skipCookies && res && res.headers && res.headers['set-cookie']){
|
||||
this.setNewCookie(res.headers['set-cookie'], false);
|
||||
for(let uCookie of usefulCookies.sess){
|
||||
if(this.session[uCookie] && this.argv.nosess){
|
||||
this.argv.nosess = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
res,
|
||||
};
|
||||
}
|
||||
catch(error){
|
||||
if(error.response && error.response.statusCode && error.response.statusMessage){
|
||||
console.log(`[ERROR] ${error.name} ${error.response.statusCode}: ${error.response.statusMessage}`);
|
||||
}
|
||||
else{
|
||||
console.log(`[ERROR] ${error.name}: ${error.code || error.message}`);
|
||||
}
|
||||
if(error.response && !error.res){
|
||||
error.res = error.response;
|
||||
const docTitle = error.res.body.match(/<title>(.*)<\/title>/);
|
||||
if(error.res.body && docTitle){
|
||||
console.log('[ERROR]', docTitle[1]);
|
||||
}
|
||||
}
|
||||
if(error.res && error.res.body && error.response.statusCode
|
||||
&& error.response.statusCode != 404 && error.response.statusCode != 403){
|
||||
console.log('[ERROR] Body:', error.res.body);
|
||||
}
|
||||
return {
|
||||
ok: false,
|
||||
error,
|
||||
};
|
||||
}
|
||||
}
|
||||
setNewCookie(setCookie, isAuth, fileData){
|
||||
let cookieUpdated = [], lastExp = 0;
|
||||
setCookie = fileData ? cookieFile(fileData) : shlp.cookie.parse(setCookie);
|
||||
for(let cookieName of Object.keys(setCookie)){
|
||||
if(setCookie[cookieName] && setCookie[cookieName].value && setCookie[cookieName].value == 'deleted'){
|
||||
delete setCookie[cookieName];
|
||||
}
|
||||
}
|
||||
for(let uCookie of usefulCookies.auth){
|
||||
const cookieForceExp = 60*60*24*7;
|
||||
const cookieExpCur = this.session[uCookie] ? this.session[uCookie] : { expires: 0 };
|
||||
const cookieExp = new Date(cookieExpCur.expires).getTime() - cookieForceExp;
|
||||
if(cookieExp > lastExp){
|
||||
lastExp = cookieExp;
|
||||
}
|
||||
}
|
||||
for(let uCookie of usefulCookies.auth){
|
||||
if(!setCookie[uCookie]){
|
||||
continue;
|
||||
}
|
||||
if(isAuth || setCookie[uCookie] && Date.now() > lastExp){
|
||||
this.session[uCookie] = setCookie[uCookie];
|
||||
cookieUpdated.push(uCookie);
|
||||
}
|
||||
}
|
||||
for(let uCookie of usefulCookies.sess){
|
||||
if(!setCookie[uCookie]){
|
||||
continue;
|
||||
}
|
||||
if(
|
||||
isAuth
|
||||
|| this.argv.nosess && setCookie[uCookie]
|
||||
|| setCookie[uCookie] && !this.checkSessId(this.session[uCookie])
|
||||
){
|
||||
const sessionExp = 60*60;
|
||||
this.session[uCookie] = setCookie[uCookie];
|
||||
this.session[uCookie].expires = new Date(Date.now() + sessionExp*1000);
|
||||
this.session[uCookie]['Max-Age'] = sessionExp.toString();
|
||||
cookieUpdated.push(uCookie);
|
||||
}
|
||||
}
|
||||
if(cookieUpdated.length > 0){
|
||||
if(this.argv.debug){
|
||||
console.log('[SAVING FILE]',`${this.sessCfg}.yml`);
|
||||
}
|
||||
yamlCfg.saveCRSession(this.session);
|
||||
console.log(`[INFO] Cookies were updated! (${cookieUpdated.join(', ')})\n`);
|
||||
}
|
||||
}
|
||||
checkCookieVal(chcookie){
|
||||
return chcookie
|
||||
&& chcookie.toString() == '[object Object]'
|
||||
&& typeof chcookie.value == 'string'
|
||||
? true : false;
|
||||
}
|
||||
checkSessId(session_id){
|
||||
if(session_id && typeof session_id.expires == 'string'){
|
||||
session_id.expires = new Date(session_id.expires);
|
||||
}
|
||||
return session_id
|
||||
&& session_id.toString() == '[object Object]'
|
||||
&& typeof session_id.expires == 'object'
|
||||
&& Date.now() < new Date(session_id.expires).getTime()
|
||||
&& typeof session_id.value == 'string'
|
||||
? true : false;
|
||||
}
|
||||
uuidv4(){
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
let r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function buildProxy(proxyBaseUrl, proxyAuth){
|
||||
if(!proxyBaseUrl.match(/^(https?|socks4|socks5):/)){
|
||||
proxyBaseUrl = 'http://' + proxyBaseUrl;
|
||||
}
|
||||
|
||||
let proxyCfg = new URL(proxyBaseUrl);
|
||||
let proxyStr = `${proxyCfg.protocol}//`;
|
||||
|
||||
if(typeof proxyCfg.hostname != 'string' || proxyCfg.hostname == ''){
|
||||
throw new Error('[ERROR] Hostname and port required for proxy!');
|
||||
}
|
||||
|
||||
if(proxyAuth && typeof proxyAuth == 'string' && proxyAuth.match(':')){
|
||||
proxyCfg.username = proxyAuth.split(':')[0];
|
||||
proxyCfg.password = proxyAuth.split(':')[1];
|
||||
proxyStr += `${proxyCfg.username}:${proxyCfg.password}@`;
|
||||
}
|
||||
|
||||
proxyStr += proxyCfg.hostname;
|
||||
|
||||
if(!proxyCfg.port && proxyCfg.protocol == 'http:'){
|
||||
proxyStr += ':80';
|
||||
}
|
||||
else if(!proxyCfg.port && proxyCfg.protocol == 'https:'){
|
||||
proxyStr += ':443';
|
||||
}
|
||||
|
||||
return proxyStr;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
buildProxy,
|
||||
usefulCookies,
|
||||
Req,
|
||||
};
|
||||
8
funi.ts
8
funi.ts
|
|
@ -55,7 +55,7 @@ let title = '',
|
|||
stDlPath: Subtitle[] = [];
|
||||
|
||||
// main
|
||||
(async () => {
|
||||
export default (async () => {
|
||||
// load binaries
|
||||
cfg.bin = await yamlCfg.loadBinCfg();
|
||||
// select mode
|
||||
|
|
@ -65,13 +65,13 @@ let title = '',
|
|||
else if(argv.search){
|
||||
searchShow();
|
||||
}
|
||||
else if(argv.s && argv.s > 0){
|
||||
else if(argv.s && !isNaN(parseInt(argv.s)) && parseInt(argv.s) > 0){
|
||||
getShow();
|
||||
}
|
||||
else{
|
||||
appYargs.showHelp();
|
||||
}
|
||||
})();
|
||||
});
|
||||
|
||||
// auth
|
||||
async function auth(){
|
||||
|
|
@ -157,7 +157,7 @@ async function getShow(){
|
|||
sort_direction: string,
|
||||
title_id: number,
|
||||
language?: string
|
||||
} = { limit: -1, sort: 'order', sort_direction: 'ASC', title_id: argv.s as number };
|
||||
} = { limit: -1, sort: 'order', sort_direction: 'ASC', title_id: parseInt(argv.s as string) };
|
||||
if(argv.alt){ qs.language = 'English'; }
|
||||
const episodesData = await getData({
|
||||
baseUrl: api_host,
|
||||
|
|
|
|||
18
index.ts
18
index.ts
|
|
@ -1 +1,17 @@
|
|||
import { appArgv } from "./modules/module.app-args";
|
||||
import { appArgv } from "./modules/module.app-args";
|
||||
import * as yamlCfg from './modules/module.cfg-loader';
|
||||
|
||||
import funimation from './funi'
|
||||
|
||||
(async () => {
|
||||
const cfg = yamlCfg.loadCfg();
|
||||
|
||||
const argv = appArgv(cfg.cli);
|
||||
|
||||
if (argv.service === 'funi') {
|
||||
await funimation()
|
||||
} else if (argv.service === 'crunchy') {
|
||||
|
||||
}
|
||||
|
||||
})()
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
import { Headers } from "got/dist/source";
|
||||
|
||||
// api domains
|
||||
const domain = {
|
||||
www: 'https://www.crunchyroll.com',
|
||||
|
|
@ -28,8 +30,8 @@ export type APIType = {
|
|||
beta_search: string
|
||||
beta_browse: string
|
||||
beta_cms: string,
|
||||
beta_authHeader: HeadersInit,
|
||||
beta_authHeaderMob: HeadersInit
|
||||
beta_authHeader: Headers,
|
||||
beta_authHeaderMob: Headers
|
||||
}
|
||||
|
||||
// api urls
|
||||
|
|
|
|||
|
|
@ -119,9 +119,9 @@ const appArgv = (cfg: {
|
|||
default: parseDefault<number>('videoLayer', 7),
|
||||
type: 'number'
|
||||
})
|
||||
.option('server', {
|
||||
.option('x', {
|
||||
group: groups.dl,
|
||||
alias: 'x',
|
||||
alias: 'server',
|
||||
describe: 'Select server',
|
||||
choices: [1, 2, 3, 4],
|
||||
default: parseDefault<number>('nServer', 1),
|
||||
|
|
@ -260,7 +260,14 @@ const appArgv = (cfg: {
|
|||
describe: 'Show this help',
|
||||
type: 'boolean'
|
||||
})
|
||||
.option('service', {
|
||||
group: groups.util,
|
||||
describe: 'Set the service to use',
|
||||
choices: ['funi', 'chrunchy'],
|
||||
demandOption: true
|
||||
})
|
||||
.parseSync();
|
||||
return argv;
|
||||
}
|
||||
|
||||
const showHelp = yargs.showHelp;
|
||||
|
|
|
|||
|
|
@ -192,4 +192,17 @@ const saveFuniToken = (data: {
|
|||
}
|
||||
};
|
||||
|
||||
export { loadBinCfg, loadCfg, loadFuniToken, saveFuniToken, saveCRSession, saveCRToken, loadCRToken, loadCRSession };
|
||||
const cfgDir = path.join(workingDir, 'config');
|
||||
|
||||
export {
|
||||
loadBinCfg,
|
||||
loadCfg,
|
||||
loadFuniToken,
|
||||
saveFuniToken,
|
||||
saveCRSession,
|
||||
saveCRToken,
|
||||
loadCRToken,
|
||||
loadCRSession,
|
||||
sessCfgFile,
|
||||
cfgDir
|
||||
};
|
||||
|
|
@ -23,4 +23,4 @@ const parse = (data: string) => {
|
|||
return res;
|
||||
};
|
||||
|
||||
module.exports = parse;
|
||||
export default parse;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
// build-in
|
||||
import child_process from 'child_process';
|
||||
import fs from 'fs-extra';
|
||||
import { Headers } from 'got';
|
||||
import path from 'path';
|
||||
|
||||
export type CurlOptions = {
|
||||
headers?: Record<string, string>,
|
||||
headers?: Headers,
|
||||
curlProxy?: boolean,
|
||||
curlProxyAuth?: string,
|
||||
minVersion?: string,
|
||||
http2?: string,
|
||||
http2?: boolean,
|
||||
body?: unknown,
|
||||
curlDebug?: boolean
|
||||
} | undefined;
|
||||
|
|
@ -158,4 +159,4 @@ function uuidv4() {
|
|||
});
|
||||
}
|
||||
|
||||
module.exports = curlReq;
|
||||
export default curlReq;
|
||||
|
|
|
|||
|
|
@ -89,5 +89,7 @@ function fontMime(fontFile: string){
|
|||
return 'application/octet-stream';
|
||||
}
|
||||
|
||||
export type AvailableFonts = keyof typeof fonts;
|
||||
|
||||
// output
|
||||
export { root, fonts, assFonts, fontMime };
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ export type Options = {
|
|||
dinstid?: boolean|string,
|
||||
debug?: boolean
|
||||
}
|
||||
// TODO convert to class
|
||||
const getData = async <T = string>(options: Options) => {
|
||||
const regionHeaders = {};
|
||||
|
||||
|
|
|
|||
261
modules/module.req.ts
Normal file
261
modules/module.req.ts
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
import path from 'path';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import shlp from 'sei-helper';
|
||||
import got, { Headers, Method, Options, ReadError, Response } from 'got';
|
||||
import cookieFile from './module.cookieFile';
|
||||
import * as yamlCfg from './module.cfg-loader';
|
||||
import curlReq from './module.curl-req';
|
||||
|
||||
export type Params = {
|
||||
method?: Method,
|
||||
headers?: Headers,
|
||||
body?: string | Buffer,
|
||||
binary?: boolean,
|
||||
followRedirect?: boolean
|
||||
}
|
||||
|
||||
// set usable cookies
|
||||
const usefulCookies = {
|
||||
auth: [
|
||||
'etp_rt',
|
||||
'c_visitor',
|
||||
],
|
||||
sess: [
|
||||
'session_id',
|
||||
],
|
||||
};
|
||||
|
||||
// req
|
||||
class Req {
|
||||
private sessCfg = yamlCfg.sessCfgFile;
|
||||
private session: Record<string, {
|
||||
value: string;
|
||||
expires: Date;
|
||||
path: string;
|
||||
domain: string;
|
||||
secure: boolean;
|
||||
'Max-Age'?: string
|
||||
}> = {};
|
||||
private cfgDir = yamlCfg.cfgDir
|
||||
private curl: boolean|string = false;
|
||||
|
||||
constructor(private domain: Record<string, unknown>, private argv: Record<string, unknown>) {}
|
||||
async getData<T = string> (durl: string, params?: Params) {
|
||||
params = params || {};
|
||||
// options
|
||||
let options: Options & {
|
||||
minVersion?: string,
|
||||
maxVersion?: string
|
||||
curlDebug?: boolean
|
||||
} = {
|
||||
method: params.method ? params.method : 'GET',
|
||||
headers: {
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:90.0) Gecko/20100101 Firefox/90.0',
|
||||
},
|
||||
};
|
||||
// additional params
|
||||
if(params.headers){
|
||||
options.headers = {...options.headers, ...params.headers};
|
||||
}
|
||||
if(options.method == 'POST'){
|
||||
(options.headers as Headers)['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
}
|
||||
if(params.body){
|
||||
options.body = params.body;
|
||||
}
|
||||
if(params.binary == true){
|
||||
options.responseType = 'buffer';
|
||||
}
|
||||
if(typeof params.followRedirect == 'boolean'){
|
||||
options.followRedirect = params.followRedirect;
|
||||
}
|
||||
// Removed Proxy support since it was only partialy supported
|
||||
/*if(params.useProxy && this.argv.proxy && this.argv.curl){
|
||||
try{
|
||||
options.curlProxy = buildProxy(this.argv.proxy);
|
||||
options.curlProxyAuth = this.argv['proxy-auth'];
|
||||
}
|
||||
catch(e){
|
||||
console.log(`[WARN] Not valid proxy URL${e.input?' ('+e.input+')':''}!`);
|
||||
console.log('[WARN] Skipping...\n');
|
||||
this.argv.proxy = false;
|
||||
}
|
||||
}*/
|
||||
// if auth
|
||||
let cookie = [];
|
||||
const loc = new URL(durl);
|
||||
// avoid cloudflare protection
|
||||
if(loc.origin == this.domain.www){
|
||||
options.minVersion = 'TLSv1.3';
|
||||
options.maxVersion = 'TLSv1.3';
|
||||
options.http2 = true;
|
||||
}
|
||||
// debug
|
||||
options.hooks = {
|
||||
beforeRequest: [
|
||||
(options) => {
|
||||
if(this.argv.debug){
|
||||
console.log('[DEBUG] GOT OPTIONS:');
|
||||
console.log(options);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
if(this.argv.debug){
|
||||
options.curlDebug = true;
|
||||
}
|
||||
// try do request
|
||||
try {
|
||||
let res: Response<T>;
|
||||
if(this.curl && this.argv.curl && Object.values(this.domain).includes(loc.origin)){
|
||||
res = await curlReq(typeof this.curl === 'boolean' ? '' : this.curl, durl.toString(), options, this.cfgDir) as unknown as Response<T>;
|
||||
}
|
||||
else{
|
||||
res = await got(durl.toString(), options) as unknown as Response<T>;
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
res
|
||||
};
|
||||
}
|
||||
catch(_error){
|
||||
const error = _error as {
|
||||
name: string
|
||||
} & ReadError & {
|
||||
res: Response<unknown>
|
||||
}
|
||||
if(error.response && error.response.statusCode && error.response.statusMessage){
|
||||
console.log(`[ERROR] ${error.name} ${error.response.statusCode}: ${error.response.statusMessage}`);
|
||||
}
|
||||
else{
|
||||
console.log(`[ERROR] ${error.name}: ${error.code || error.message}`);
|
||||
}
|
||||
if(error.response && !error.res){
|
||||
error.res = error.response;
|
||||
const docTitle = (error.res.body as string).match(/<title>(.*)<\/title>/);
|
||||
if(error.res.body && docTitle){
|
||||
console.log('[ERROR]', docTitle[1]);
|
||||
}
|
||||
}
|
||||
if(error.res && error.res.body && error.response.statusCode
|
||||
&& error.response.statusCode != 404 && error.response.statusCode != 403){
|
||||
console.log('[ERROR] Body:', error.res.body);
|
||||
}
|
||||
return {
|
||||
ok: false,
|
||||
error,
|
||||
};
|
||||
}
|
||||
}
|
||||
setNewCookie(setCookie: Record<string, string>, isAuth: boolean, fileData?: string){
|
||||
let cookieUpdated = [], lastExp = 0;
|
||||
console.trace('Type of setCookie:', typeof setCookie, setCookie)
|
||||
const parsedCookie = fileData ? cookieFile(fileData) : shlp.cookie.parse(setCookie);
|
||||
for(let cookieName of Object.keys(parsedCookie)){
|
||||
if(parsedCookie[cookieName] && parsedCookie[cookieName].value && parsedCookie[cookieName].value == 'deleted'){
|
||||
delete parsedCookie[cookieName];
|
||||
}
|
||||
}
|
||||
for(let uCookie of usefulCookies.auth){
|
||||
const cookieForceExp = 60*60*24*7;
|
||||
const cookieExpCur = this.session[uCookie] ? this.session[uCookie] : { expires: 0 };
|
||||
const cookieExp = new Date(cookieExpCur.expires).getTime() - cookieForceExp;
|
||||
if(cookieExp > lastExp){
|
||||
lastExp = cookieExp;
|
||||
}
|
||||
}
|
||||
for(let uCookie of usefulCookies.auth){
|
||||
if(!parsedCookie[uCookie]){
|
||||
continue;
|
||||
}
|
||||
if(isAuth || parsedCookie[uCookie] && Date.now() > lastExp){
|
||||
this.session[uCookie] = parsedCookie[uCookie];
|
||||
cookieUpdated.push(uCookie);
|
||||
}
|
||||
}
|
||||
for(let uCookie of usefulCookies.sess){
|
||||
if(!parsedCookie[uCookie]){
|
||||
continue;
|
||||
}
|
||||
if(
|
||||
isAuth
|
||||
|| this.argv.nosess && parsedCookie[uCookie]
|
||||
|| parsedCookie[uCookie] && !this.checkSessId(this.session[uCookie])
|
||||
){
|
||||
const sessionExp = 60*60;
|
||||
this.session[uCookie] = parsedCookie[uCookie];
|
||||
this.session[uCookie].expires = new Date(Date.now() + sessionExp*1000);
|
||||
this.session[uCookie]['Max-Age'] = sessionExp.toString();
|
||||
cookieUpdated.push(uCookie);
|
||||
}
|
||||
}
|
||||
if(cookieUpdated.length > 0){
|
||||
if(this.argv.debug){
|
||||
console.log('[SAVING FILE]',`${this.sessCfg}.yml`);
|
||||
}
|
||||
yamlCfg.saveCRSession(this.session);
|
||||
console.log(`[INFO] Cookies were updated! (${cookieUpdated.join(', ')})\n`);
|
||||
}
|
||||
}
|
||||
checkCookieVal(chcookie: Record<string, string>){
|
||||
return chcookie
|
||||
&& chcookie.toString() == '[object Object]'
|
||||
&& typeof chcookie.value == 'string'
|
||||
? true : false;
|
||||
}
|
||||
checkSessId(session_id: Record<string, unknown>){
|
||||
if(session_id && typeof session_id.expires == 'string'){
|
||||
session_id.expires = new Date(session_id.expires);
|
||||
}
|
||||
return session_id
|
||||
&& session_id.toString() == '[object Object]'
|
||||
&& typeof session_id.expires == 'object'
|
||||
&& Date.now() < new Date(session_id.expires as any).getTime()
|
||||
&& typeof session_id.value == 'string'
|
||||
? true : false;
|
||||
}
|
||||
uuidv4(){
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
let r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function buildProxy(proxyBaseUrl: string, proxyAuth: string){
|
||||
if(!proxyBaseUrl.match(/^(https?|socks4|socks5):/)){
|
||||
proxyBaseUrl = 'http://' + proxyBaseUrl;
|
||||
}
|
||||
|
||||
let proxyCfg = new URL(proxyBaseUrl);
|
||||
let proxyStr = `${proxyCfg.protocol}//`;
|
||||
|
||||
if(typeof proxyCfg.hostname != 'string' || proxyCfg.hostname == ''){
|
||||
throw new Error('[ERROR] Hostname and port required for proxy!');
|
||||
}
|
||||
|
||||
if(proxyAuth && typeof proxyAuth == 'string' && proxyAuth.match(':')){
|
||||
proxyCfg.username = proxyAuth.split(':')[0];
|
||||
proxyCfg.password = proxyAuth.split(':')[1];
|
||||
proxyStr += `${proxyCfg.username}:${proxyCfg.password}@`;
|
||||
}
|
||||
|
||||
proxyStr += proxyCfg.hostname;
|
||||
|
||||
if(!proxyCfg.port && proxyCfg.protocol == 'http:'){
|
||||
proxyStr += ':80';
|
||||
}
|
||||
else if(!proxyCfg.port && proxyCfg.protocol == 'https:'){
|
||||
proxyStr += ':443';
|
||||
}
|
||||
|
||||
return proxyStr;
|
||||
}
|
||||
|
||||
export {
|
||||
buildProxy,
|
||||
usefulCookies,
|
||||
Req,
|
||||
};
|
||||
|
||||
Loading…
Reference in a new issue