prepared crunchyroll login fetch for the new token system

This commit is contained in:
stratuma 2024-05-22 01:09:59 +02:00
parent da2a361d08
commit 119053c443
5 changed files with 586 additions and 655 deletions

View File

@ -29,7 +29,7 @@
"@types/node-cron": "^3.0.11", "@types/node-cron": "^3.0.11",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"electron": "^30.0.2", "electron": "^30.0.6",
"electron-builder": "^24.13.3", "electron-builder": "^24.13.3",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^8.10.0", "eslint-config-prettier": "^8.10.0",
@ -39,7 +39,7 @@
"nuxt": "^3.11.2", "nuxt": "^3.11.2",
"nuxt-icon": "^0.6.10", "nuxt-icon": "^0.6.10",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"sass": "^1.77.0", "sass": "^1.77.2",
"sass-loader": "^13.3.3", "sass-loader": "^13.3.3",
"tsc-watch": "^6.2.0", "tsc-watch": "^6.2.0",
"typescript": "^5.4.5", "typescript": "^5.4.5",
@ -49,17 +49,17 @@
"@fastify/cors": "^9.0.1", "@fastify/cors": "^9.0.1",
"ass-compiler": "^0.1.11", "ass-compiler": "^0.1.11",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"electron-log": "^5.1.2", "electron-log": "^5.1.4",
"electron-settings": "^4.0.4", "electron-settings": "^4.0.4",
"electron-updater": "^6.1.8", "electron-updater": "^6.1.8",
"express": "^4.19.2", "express": "^4.19.2",
"fastify": "^4.27.0", "fastify": "^4.27.0",
"fluent-ffmpeg": "^2.1.2", "fluent-ffmpeg": "^2.1.3",
"jsencrypt": "^3.3.2", "jsencrypt": "^3.3.2",
"mpd-parser": "^1.3.0", "mpd-parser": "^1.3.0",
"node-cache": "^5.1.2", "node-cache": "^5.1.2",
"node-cron": "^3.0.3", "node-cron": "^3.0.3",
"protobufjs": "^7.2.6", "protobufjs": "^7.3.0",
"sequelize": "^6.37.3", "sequelize": "^6.37.3",
"sqlite3": "5.1.6", "sqlite3": "5.1.6",
"winston": "^3.13.0" "winston": "^3.13.0"

859
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -23,11 +23,7 @@ export async function loginController(
return reply.code(401).send({ message: 'Not Logged in' }) return reply.code(401).send({ message: 'Not Logged in' })
} }
const { data, error } = await crunchyLogin(account.username, account.password, query.geo) const login = await crunchyLogin(account.username, account.password, query.geo)
if (error) { return reply.code(200).send(login)
reply.code(400).send(error)
}
return reply.code(200).send(data)
} }

View File

@ -14,22 +14,6 @@ const crErrors = [
} }
] ]
// Login Proxies
// const proxies: { name: string; code: string; url: string; status: string | undefined }[] = [
// {
// name: 'US Proxy',
// code: 'US',
// url: 'https://us-proxy.crd.cx/',
// status: undefined
// },
// {
// name: 'UK Proxy',
// code: 'GB',
// url: 'https://uk-proxy.crd.cx/',
// status: undefined
// }
// ]
// Crunchyroll Login Handler // Crunchyroll Login Handler
export async function crunchyLogin(user: string, passw: string, geo: string) { export async function crunchyLogin(user: string, passw: string, geo: string) {
const cachedData: const cachedData:
@ -47,208 +31,197 @@ export async function crunchyLogin(user: string, passw: string, geo: string) {
if (!cachedData) { if (!cachedData) {
if (geo === 'LOCAL') { if (geo === 'LOCAL') {
const { data, error } = await crunchyLoginFetch(user, passw) const data = await crunchyLoginFetch(user, passw);
if (error) { if (!data) return
messageBox(
'error',
['Cancel'],
2,
'Failed to login',
'Failed to login to Crunchyroll',
crErrors.find((r) => r.error === (error?.error as string)) ? crErrors.find((r) => r.error === (error?.error as string))?.response : (error.error as string)
)
server.logger.log({
level: 'error',
message: 'Failed to login to Crunchyroll',
data: data,
error: error,
timestamp: new Date().toISOString(),
section: 'loginCrunchyrollFetch'
})
return { data: null, error: error.error }
}
if (!data) {
messageBox('error', ['Cancel'], 2, 'Failed to login', 'Failed to login to Crunchyroll', 'Crunchyroll returned null')
server.logger.log({
level: 'error',
message: 'Failed to login to Crunchyroll',
data: data,
error: error,
timestamp: new Date().toISOString(),
section: 'loginCrunchyrollFetch'
})
return { data: null, error: 'Crunchyroll returned null' }
}
if (!data.access_token) {
messageBox('error', ['Cancel'], 2, 'Failed to login', 'Failed to login to Crunchyroll', 'Crunchyroll returned malformed data')
server.logger.log({
level: 'error',
message: 'Failed to login to Crunchyroll',
data: data,
error: error,
timestamp: new Date().toISOString(),
section: 'loginCrunchyrollFetch'
})
return { data: null, error: 'Crunchyroll returned malformed data' }
}
server.CacheController.set(`crtoken-${geo}`, data, data.expires_in - 30) server.CacheController.set(`crtoken-${geo}`, data, data.expires_in - 30)
return { data: data, error: null } return data
} }
if (geo !== 'LOCAL') { if (geo !== 'LOCAL') {
const { data, error } = await crunchyLoginFetchProxy(user, passw, geo) const data = await crunchyLoginFetchProxy(user, passw, geo)
if (error) { if (!data) return
messageBox(
'error',
['Cancel'],
2,
'Failed to login',
'Failed to login to Crunchyroll',
crErrors.find((r) => r.error === (error?.error as string)) ? crErrors.find((r) => r.error === (error?.error as string))?.response : (error.error as string)
)
server.logger.log({
level: 'error',
message: 'Failed to login to Crunchyroll',
data: data,
error: error,
timestamp: new Date().toISOString(),
section: 'loginCrunchyrollFetch'
})
return { data: null, error: error.error }
}
if (!data) {
messageBox('error', ['Cancel'], 2, 'Failed to login', 'Failed to login to Crunchyroll', 'Crunchyroll returned null')
server.logger.log({
level: 'error',
message: 'Failed to login to Crunchyroll',
data: data,
error: error,
timestamp: new Date().toISOString(),
section: 'loginCrunchyrollFetch'
})
return { data: null, error: 'Crunchyroll returned null' }
}
if (!data.access_token) {
messageBox('error', ['Cancel'], 2, 'Failed to login', 'Failed to login to Crunchyroll', 'Crunchyroll returned malformed data')
server.logger.log({
level: 'error',
message: 'Failed to login to Crunchyroll',
data: data,
error: error,
timestamp: new Date().toISOString(),
section: 'loginCrunchyrollFetch'
})
return { data: null, error: 'Crunchyroll returned malformed data' }
}
server.CacheController.set(`crtoken-${geo}`, data, data.expires_in - 30) server.CacheController.set(`crtoken-${geo}`, data, data.expires_in - 30)
return { data: data, error: null } return data
} }
} }
return { data: cachedData, error: null } return cachedData
} }
// Crunchyroll Login Fetch Proxy // Crunchyroll Login Fetch Proxy
async function crunchyLoginFetchProxy(user: string, passw: string, geo: string) { async function crunchyLoginFetchProxy(user: string, passw: string, geo: string) {
const proxies = await checkProxies() const proxies = await checkProxies()
var host: string | undefined var headers
var body
host = proxies.find((p) => p.code === geo)?.url var endpoint = await settings.get('CREndpoint')
const drmL3blob = await settings.get('l3blob')
const drmL3key = await settings.get('l3key')
var proxy: {
name: string;
code: string;
url: string;
status: string | undefined;
} | undefined;
const headers = { proxy = proxies.find((p) => p.code === geo);
Authorization: 'Basic dC1rZGdwMmg4YzNqdWI4Zm4wZnE6eWZMRGZNZnJZdktYaDRKWFMxTEVJMmNDcXUxdjVXYW4=',
'Content-Type': 'application/json', if (!proxy) {
'User-Agent': 'Crunchyroll/3.46.2 Android/13 okhttp/4.12.0' messageBox('error', ['Cancel'], 2, 'Login Proxy not found', 'Login Proxy not found', `Login Proxy ${geo} not found`)
server.logger.log({
level: 'error',
message: `Login Proxy ${geo} not found`,
timestamp: new Date().toISOString(),
section: 'loginCrunchyrollFetchProxy'
})
return
} }
const body: any = { if (proxy.status === 'offline') {
username: user, messageBox('error', ['Cancel'], 2, 'Login Proxy is offline', 'Login Proxy is offline', `Login Proxy ${geo} is offline`)
password: passw, server.logger.log({
grant_type: 'password', level: 'error',
scope: 'offline_access', message: `Login Proxy ${geo} is offline`,
device_name: 'RMX2170', timestamp: new Date().toISOString(),
device_type: 'realme RMX2170' section: 'loginCrunchyrollFetchProxy'
})
return
} }
const { data, error } = await useFetch<{ if (endpoint !== 1 && drmL3blob && drmL3key) {
access_token: string headers = {
refresh_token: string Authorization: 'Basic dm52cHJyN21ubW1la2Uyd2xwNTM6V19IdWlNekxUS1JqSnlKZTBHRlFYZXFoTldDREdUM2M=',
expires_in: number 'Content-Type': 'application/x-www-form-urlencoded',
token_type: string 'User-Agent': 'Crunchyroll/4.51.0 (bundle_identifier:com.crunchyroll.iphone; build_number:3634220.454824296) iOS/17.4.1 Gravity/4.51.0'
scope: string }
country: string
account_id: string
profile_id: string
}>(host + 'auth/v1/token', {
type: 'POST',
body: JSON.stringify(body),
header: headers,
credentials: 'same-origin'
})
if (error) { body = {
return { data: null, error: error } username: user,
password: passw,
grant_type: 'password',
scope: 'offline_access',
device_name: 'iPhone',
device_type: 'iPhone 13'
}
} }
if (!data) { if (!headers || !body) return
return { data: null, error: null }
}
return { data: data, error: null } try {
const response = await fetch(proxy.url + 'auth/v1/token', {
method: 'POST',
body: JSON.stringify(body),
headers: headers,
credentials: 'same-origin'
})
if (response.ok) {
const data: {
access_token: string
refresh_token: string
expires_in: number
token_type: string
scope: string
country: string
account_id: string
profile_id: string
} = JSON.parse(await response.text())
return data
} else {
throw new Error(JSON.stringify(response))
}
} catch (e) {
messageBox('error', ['Cancel'], 2, 'Failed to Login to Crunchyroll', 'Failed to Login to Crunchyroll', String(e))
server.logger.log({
level: 'error',
message: 'Failed to Login to Crunchyroll',
error: String(e),
timestamp: new Date().toISOString(),
section: 'loginCrunchyrollFetchProxy'
})
throw new Error(e as string)
}
} }
async function crunchyLoginFetch(user: string, passw: string) { async function crunchyLoginFetch(user: string, passw: string) {
const headers = { var headers
Authorization: 'Basic dC1rZGdwMmg4YzNqdWI4Zm4wZnE6eWZMRGZNZnJZdktYaDRKWFMxTEVJMmNDcXUxdjVXYW4=', var body
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'Crunchyroll/3.46.2 Android/13 okhttp/4.12.0' var endpoint = await settings.get('CREndpoint')
const drmL3blob = await settings.get('l3blob')
const drmL3key = await settings.get('l3key')
if (!drmL3blob || !drmL3key) {
await settings.set('CREndpoint', 1)
endpoint = 1
} }
const body: any = { if (endpoint !== 1 && drmL3blob && drmL3key) {
username: user, headers = {
password: passw, Authorization: 'Basic dm52cHJyN21ubW1la2Uyd2xwNTM6V19IdWlNekxUS1JqSnlKZTBHRlFYZXFoTldDREdUM2M=',
grant_type: 'password', 'Content-Type': 'application/x-www-form-urlencoded',
scope: 'offline_access', 'User-Agent': 'Crunchyroll/4.51.0 (bundle_identifier:com.crunchyroll.iphone; build_number:3634220.454824296) iOS/17.4.1 Gravity/4.51.0'
device_name: 'RMX2170', }
device_type: 'realme RMX2170'
body = {
username: user,
password: passw,
grant_type: 'password',
scope: 'offline_access',
device_name: 'iPhone',
device_type: 'iPhone 13'
}
} }
const { data, error } = await useFetch<{ if (!headers || !body) return
access_token: string
refresh_token: string
expires_in: number
token_type: string
scope: string
country: string
account_id: string
profile_id: string
}>('https://beta-api.crunchyroll.com/auth/v1/token', {
type: 'POST',
body: new URLSearchParams(body).toString(),
header: headers,
credentials: 'same-origin'
})
if (error) { try {
return { data: null, error: error } const response = await fetch('https://beta-api.crunchyroll.com/auth/v1/token', {
method: 'POST',
body: new URLSearchParams(body).toString(),
headers: headers,
credentials: 'same-origin'
})
if (response.ok) {
const data: {
access_token: string
refresh_token: string
expires_in: number
token_type: string
scope: string
country: string
account_id: string
profile_id: string
} = JSON.parse(await response.text())
return data
} else {
const error = {
status: response.status,
message: await response.text()
}
throw new Error(JSON.stringify(error))
}
} catch (e) {
messageBox('error', ['Cancel'], 2, 'Failed to Login to Crunchyroll', 'Failed to Login to Crunchyroll', String(e))
server.logger.log({
level: 'error',
message: 'Failed to Login to Crunchyroll',
error: String(e),
timestamp: new Date().toISOString(),
section: 'loginCrunchyroll Fetch'
})
throw new Error(e as string)
} }
if (!data) {
return { data: null, error: null }
}
return { data: data, error: null }
} }
// Crunchyroll Playlist Fetch // Crunchyroll Playlist Fetch
@ -347,12 +320,12 @@ export async function crunchyGetPlaylist(q: string, geo: string | undefined) {
if (!account) return if (!account) return
const { data: loginLocal, error } = await crunchyLogin(account.username, account.password, geo ? geo : 'LOCAL') const login = await crunchyLogin(account.username, account.password, geo ? geo : 'LOCAL')
if (!loginLocal) return if (!login) return
const headersLoc = { const headersLoc = {
Authorization: `Bearer ${loginLocal.access_token}`, Authorization: `Bearer ${login.access_token}`,
'X-Cr-Disable-Drm': 'true' 'X-Cr-Disable-Drm': 'true'
} }
@ -391,13 +364,13 @@ export async function crunchyGetPlaylist(q: string, geo: string | undefined) {
if (isProxyActive) if (isProxyActive)
for (const p of proxies) { for (const p of proxies) {
if (p.code !== loginLocal.country) { if (p.code !== login.country) {
const { data: login, error } = await crunchyLogin(account.username, account.password, p.code) const logindata = await crunchyLogin(account.username, account.password, p.code)
if (!login) return if (!logindata) return
const headers = { const headers = {
Authorization: `Bearer ${login.access_token}`, Authorization: `Bearer ${logindata.access_token}`,
'X-Cr-Disable-Drm': 'true' 'X-Cr-Disable-Drm': 'true'
} }
@ -439,7 +412,7 @@ export async function crunchyGetPlaylist(q: string, geo: string | undefined) {
} }
} }
return { data: playlist, account_id: loginLocal.account_id } return { data: playlist, account_id: login.account_id }
} }
// Crunchyroll Delete Video Token Fetch // Crunchyroll Delete Video Token Fetch
@ -448,7 +421,7 @@ export async function deleteVideoToken(content: string, token: string) {
if (!account) return if (!account) return
const { data: login, error } = await crunchyLogin(account.username, account.password, 'LOCAL') const login = await crunchyLogin(account.username, account.password, 'LOCAL')
if (!login) return if (!login) return
@ -479,12 +452,12 @@ export async function crunchyGetPlaylistMPD(q: string, geo: string | undefined)
if (!account) return if (!account) return
const { data } = await crunchyLogin(account.username, account.password, geo ? geo : 'LOCAL') const login = await crunchyLogin(account.username, account.password, geo ? geo : 'LOCAL')
if (!data) return if (!login) return
const headers = { const headers = {
Authorization: `Bearer ${data.access_token}` Authorization: `Bearer ${login.access_token}`
} }
try { try {

View File

@ -43,12 +43,13 @@ export async function loginController(
return reply.code(404).send({ message: 'Already Logged In' }) return reply.code(404).send({ message: 'Already Logged In' })
} }
var responseData var response
var responseError var responseError
var responseData
if (params.id === 'CR') { if (params.id === 'CR') {
const { data, error } = await crunchyLogin(body.user, body.password, 'LOCAL') const login = await crunchyLogin(body.user, body.password, 'LOCAL');
;(responseError = error), (responseData = data) response = login
} }
if (params.id === 'ADN') { if (params.id === 'ADN') {
@ -56,7 +57,7 @@ export async function loginController(
;(responseError = error), (responseData = data) ;(responseError = error), (responseData = data)
} }
if (responseError || !responseData) { if (responseError && !responseData && !response) {
return reply.code(404).send({ return reply.code(404).send({
message: 'Invalid Email or Password' message: 'Invalid Email or Password'
}) })