added initial adn downloader functions
This commit is contained in:
parent
f3e749ce79
commit
195b7aee15
@ -37,10 +37,12 @@
|
||||
"dependencies": {
|
||||
"@fastify/cors": "^9.0.1",
|
||||
"@pinia/nuxt": "^0.4.11",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/fluent-ffmpeg": "^2.1.24",
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"ass-compiler": "^0.1.11",
|
||||
"crypto-js": "^4.2.0",
|
||||
"electron-log": "^5.1.2",
|
||||
"electron-updater": "^5.3.0",
|
||||
"express": "^4.19.2",
|
||||
@ -48,6 +50,7 @@
|
||||
"ffmpeg-static": "^5.2.0",
|
||||
"ffprobe-static": "^3.1.0",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"mpd-parser": "^1.3.0",
|
||||
"node-cache": "^5.1.2",
|
||||
"node-cron": "^3.0.3",
|
||||
|
21
pnpm-lock.yaml
generated
21
pnpm-lock.yaml
generated
@ -11,6 +11,9 @@ dependencies:
|
||||
'@pinia/nuxt':
|
||||
specifier: ^0.4.11
|
||||
version: 0.4.11(typescript@5.4.4)(vue@3.4.21)
|
||||
'@types/crypto-js':
|
||||
specifier: ^4.2.2
|
||||
version: 4.2.2
|
||||
'@types/fluent-ffmpeg':
|
||||
specifier: ^2.1.24
|
||||
version: 2.1.24
|
||||
@ -23,6 +26,9 @@ dependencies:
|
||||
ass-compiler:
|
||||
specifier: ^0.1.11
|
||||
version: 0.1.11
|
||||
crypto-js:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0
|
||||
electron-log:
|
||||
specifier: ^5.1.2
|
||||
version: 5.1.2
|
||||
@ -44,6 +50,9 @@ dependencies:
|
||||
fluent-ffmpeg:
|
||||
specifier: ^2.1.2
|
||||
version: 2.1.2
|
||||
jsencrypt:
|
||||
specifier: ^3.3.2
|
||||
version: 3.3.2
|
||||
mpd-parser:
|
||||
specifier: ^1.3.0
|
||||
version: 1.3.0
|
||||
@ -2068,6 +2077,10 @@ packages:
|
||||
'@types/node': 20.12.5
|
||||
dev: true
|
||||
|
||||
/@types/crypto-js@4.2.2:
|
||||
resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==}
|
||||
dev: false
|
||||
|
||||
/@types/debug@4.1.12:
|
||||
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
|
||||
dependencies:
|
||||
@ -4338,6 +4351,10 @@ packages:
|
||||
optional: true
|
||||
dev: true
|
||||
|
||||
/crypto-js@4.2.0:
|
||||
resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
|
||||
dev: false
|
||||
|
||||
/css-declaration-sorter@7.2.0(postcss@8.4.38):
|
||||
resolution: {integrity: sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==}
|
||||
engines: {node: ^14 || ^16 || >=18}
|
||||
@ -6954,6 +6971,10 @@ packages:
|
||||
resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==}
|
||||
requiresBuild: true
|
||||
|
||||
/jsencrypt@3.3.2:
|
||||
resolution: {integrity: sha512-arQR1R1ESGdAxY7ZheWr12wCaF2yF47v5qpB76TtV64H1pyGudk9Hvw8Y9tb/FiTIaaTRUyaSnm5T/Y53Ghm/A==}
|
||||
dev: false
|
||||
|
||||
/jsesc@2.5.2:
|
||||
resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
|
||||
engines: {node: '>=4'}
|
||||
|
0
src/api/routes/adn/adn.controller.ts
Normal file
0
src/api/routes/adn/adn.controller.ts
Normal file
0
src/api/routes/adn/adn.route.ts
Normal file
0
src/api/routes/adn/adn.route.ts
Normal file
377
src/api/routes/adn/adn.service.ts
Normal file
377
src/api/routes/adn/adn.service.ts
Normal file
@ -0,0 +1,377 @@
|
||||
import JSEncrypt from 'jsencrypt'
|
||||
import CryptoJS from 'crypto-js'
|
||||
import { server } from '../../api'
|
||||
import { ADNPlayerConfig } from '../../types/adn'
|
||||
|
||||
export async function getShowADN(q: number) {
|
||||
const cachedData = server.CacheController.get(`getshowadn-${q}`)
|
||||
|
||||
if (cachedData) {
|
||||
return cachedData
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`https://gw.api.animationdigitalnetwork.fr/show/${q}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-target-distribution': 'de'
|
||||
}
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const data: {
|
||||
show: Array<any>
|
||||
} = JSON.parse(await response.text())
|
||||
|
||||
server.CacheController.set(`getshowadn-${q}`, data.show, 1000)
|
||||
|
||||
return data.show
|
||||
} else {
|
||||
throw new Error('Failed to fetch ADN')
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(e as string)
|
||||
}
|
||||
}
|
||||
|
||||
export async function getEpisodesWithShowIdADN(q: number) {
|
||||
const cachedData = server.CacheController.get(`getepisodesadn-${q}`)
|
||||
|
||||
if (cachedData) {
|
||||
return cachedData
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`https://gw.api.animationdigitalnetwork.fr/video/show/${q}?offset=0&limit=-1&order=asc`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-target-distribution': 'de'
|
||||
}
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const data: {
|
||||
videos: Array<any>
|
||||
} = JSON.parse(await response.text())
|
||||
|
||||
server.CacheController.set(`getepisodesadn-${q}`, data.videos, 1000)
|
||||
|
||||
return data.videos
|
||||
} else {
|
||||
throw new Error('Failed to fetch ADN')
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(e as string)
|
||||
}
|
||||
}
|
||||
|
||||
export async function getEpisodeADN(q: number) {
|
||||
const cachedData = server.CacheController.get(`getepisodeadn-${q}`)
|
||||
|
||||
if (cachedData) {
|
||||
return cachedData
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`https://gw.api.animationdigitalnetwork.fr/video/${q}/public`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-target-distribution': 'de'
|
||||
}
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const data: {
|
||||
video: Array<any>
|
||||
} = JSON.parse(await response.text())
|
||||
|
||||
server.CacheController.set(`getepisodeadn-${q}`, data.video, 1000)
|
||||
|
||||
return data.video
|
||||
} else {
|
||||
throw new Error('Failed to fetch ADN')
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(e as string)
|
||||
}
|
||||
}
|
||||
|
||||
export async function searchADN(q: string) {
|
||||
const cachedData = server.CacheController.get(`searchadn-${q}`)
|
||||
|
||||
if (cachedData) {
|
||||
return cachedData
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`https://gw.api.animationdigitalnetwork.fr/show/catalog?maxAgeCategory=18&search=${q}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-target-distribution': 'de'
|
||||
}
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const data: {
|
||||
shows: Array<any>
|
||||
} = JSON.parse(await response.text())
|
||||
|
||||
server.CacheController.set(`searchadn-${q}`, data.shows, 1000)
|
||||
|
||||
return data.shows
|
||||
} else {
|
||||
throw new Error('Failed to fetch ADN')
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(e as string)
|
||||
}
|
||||
}
|
||||
|
||||
export async function loginADN() {
|
||||
const cachedData = server.CacheController.get('adnlogin')
|
||||
const cachedToken = server.CacheController.get('adntoken')
|
||||
|
||||
if (cachedData) {
|
||||
return cachedData
|
||||
}
|
||||
|
||||
if (cachedToken) {
|
||||
const data = await loginADNToken(cachedToken as string)
|
||||
return data
|
||||
}
|
||||
|
||||
const body = {
|
||||
source: 'Web',
|
||||
rememberMe: true
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`https://gw.api.animationdigitalnetwork.fr/authentication/login`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'x-target-distribution': 'de',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const data: {
|
||||
accessToken: string
|
||||
} = JSON.parse(await response.text())
|
||||
|
||||
server.CacheController.set('adnlogin', data, 100)
|
||||
server.CacheController.set('adntoken', data.accessToken, 300)
|
||||
server.CacheController.del('adnplayerconfig')
|
||||
|
||||
return data
|
||||
} else {
|
||||
throw new Error('Failed to fetch ADN')
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(e as string)
|
||||
}
|
||||
}
|
||||
|
||||
async function loginADNToken(t: string) {
|
||||
const body = {
|
||||
source: 'Web',
|
||||
rememberMe: true
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`https://gw.api.animationdigitalnetwork.fr/authentication/refresh`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'x-target-distribution': 'de',
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${t}`,
|
||||
'X-Access-Token': t
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const data: {
|
||||
accessToken: string
|
||||
} = JSON.parse(await response.text())
|
||||
|
||||
server.CacheController.set('adnlogin', data, 100)
|
||||
server.CacheController.set('adntoken', data.accessToken, 300)
|
||||
server.CacheController.del('adnplayerconfig')
|
||||
|
||||
return data
|
||||
} else {
|
||||
console.log(await response.text())
|
||||
throw new Error('Failed to fetch ADN')
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(e as string)
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPlayerConfigADN() {
|
||||
const cachedData: ADNPlayerConfig | undefined = server.CacheController.get('adnplayerconfig')
|
||||
|
||||
if (cachedData) {
|
||||
return cachedData
|
||||
}
|
||||
|
||||
const token: any = await loginADN()
|
||||
|
||||
try {
|
||||
const response = await fetch(`https://gw.api.animationdigitalnetwork.fr/player/video/19830/configuration`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-target-distribution': 'de',
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token.accessToken}`
|
||||
}
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const data: ADNPlayerConfig = JSON.parse(await response.text())
|
||||
|
||||
server.CacheController.set('adnplayerconfig', data, 300)
|
||||
|
||||
return data
|
||||
} else {
|
||||
throw new Error('Failed to fetch ADN')
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(e as string)
|
||||
}
|
||||
}
|
||||
|
||||
async function getPlayerToken() {
|
||||
const r = await getPlayerConfigADN()
|
||||
|
||||
try {
|
||||
const response = await fetch(`https://gw.api.animationdigitalnetwork.fr/player/refresh/token`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'x-target-distribution': 'de',
|
||||
'Content-Type': 'application/json',
|
||||
'X-Player-Refresh-Token': r.player.options.user.refreshToken
|
||||
}
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const data: {
|
||||
token: string
|
||||
accessToken: string
|
||||
refreshToken: string
|
||||
} = JSON.parse(await response.text())
|
||||
|
||||
return data
|
||||
} else {
|
||||
throw new Error('Failed to fetch ADN')
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(e as string)
|
||||
}
|
||||
}
|
||||
|
||||
function randomHexaString(length: number) {
|
||||
const characters = '0123456789abcdef'
|
||||
let result = ''
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters[Math.floor(Math.random() * characters.length)]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async function getPlayerEncryptedToken() {
|
||||
const token = await getPlayerToken()
|
||||
|
||||
var key = new JSEncrypt()
|
||||
var random = randomHexaString(16)
|
||||
|
||||
key.setPublicKey(
|
||||
'-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCbQrCJBRmaXM4gJidDmcpWDssgnumHinCLHAgS4buMtdH7dEGGEUfBofLzoEdt1jqcrCDT6YNhM0aFCqbLOPFtx9cg/X2G/G5bPVu8cuFM0L+ehp8s6izK1kjx3OOPH/kWzvstM5tkqgJkNyNEvHdeJl6KhS+IFEqwvZqgbBpKuwIDAQAB-----END PUBLIC KEY-----'
|
||||
)
|
||||
|
||||
const data = {
|
||||
k: random,
|
||||
t: String(token.token)
|
||||
}
|
||||
|
||||
const finisheddata = JSON.stringify(data)
|
||||
|
||||
const encryptedData = key.encrypt(finisheddata) || ''
|
||||
|
||||
return { data: encryptedData, random: random }
|
||||
}
|
||||
|
||||
export async function getPlayerPlaylists(animeid: number) {
|
||||
const token = await getPlayerEncryptedToken()
|
||||
|
||||
try {
|
||||
const response = await fetch(`https://gw.api.animationdigitalnetwork.fr/player/video/${animeid}/link`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-target-distribution': 'de',
|
||||
'X-Player-Token': token.data
|
||||
}
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const data: {
|
||||
links: {
|
||||
subtitles: {
|
||||
all: string
|
||||
}
|
||||
}
|
||||
} = await JSON.parse(await response.text())
|
||||
|
||||
const subtitlelink = await fetch(data.links.subtitles.all, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-target-distribution': 'de'
|
||||
}
|
||||
})
|
||||
|
||||
const link: {
|
||||
location: string
|
||||
} = await JSON.parse(await subtitlelink.text())
|
||||
|
||||
const subs = await parseSubs(link.location, token.random)
|
||||
|
||||
return subs
|
||||
} else {
|
||||
const data: {
|
||||
token: string
|
||||
accessToken: string
|
||||
refreshToken: string
|
||||
} = JSON.parse(await response.text())
|
||||
|
||||
return data
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(e as string)
|
||||
}
|
||||
}
|
||||
|
||||
export async function parseSubs(url: string, grant: string) {
|
||||
const response = await fetch(url)
|
||||
|
||||
const data = await response.text()
|
||||
|
||||
var key = grant + '7fac1178830cfe0c'
|
||||
|
||||
console.log(key)
|
||||
|
||||
var parsedSubtitle = CryptoJS.enc.Base64.parse(data.substring(0, 24))
|
||||
var sec = CryptoJS.enc.Hex.parse(key)
|
||||
var som = data.substring(24)
|
||||
|
||||
try {
|
||||
// Fuck You ADN
|
||||
var decrypted: any = CryptoJS.AES.decrypt(som, sec, { iv: parsedSubtitle })
|
||||
decrypted = decrypted.toString(CryptoJS.enc.Utf8)
|
||||
return decrypted
|
||||
} catch (error) {
|
||||
console.error('Error decrypting subtitles:', error)
|
||||
return null
|
||||
}
|
||||
}
|
37
src/api/types/adn.ts
Normal file
37
src/api/types/adn.ts
Normal file
@ -0,0 +1,37 @@
|
||||
export interface ADNPlayerConfig {
|
||||
player: {
|
||||
image: string,
|
||||
options: {
|
||||
user: {
|
||||
hasAccess: true,
|
||||
profileId: number,
|
||||
refreshToken: string,
|
||||
refreshTokenUrl: string
|
||||
},
|
||||
chromecast: {
|
||||
appId: string,
|
||||
refreshTokenUrl: string
|
||||
},
|
||||
ios: {
|
||||
videoUrl: string,
|
||||
appUrl: string,
|
||||
title: string
|
||||
},
|
||||
video: {
|
||||
startDate: string,
|
||||
currentDate: string,
|
||||
available: boolean,
|
||||
free: boolean,
|
||||
url: string
|
||||
},
|
||||
dock: Array<string>,
|
||||
preference: {
|
||||
quality: string,
|
||||
autoplay: boolean,
|
||||
language: string,
|
||||
green: boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user