mirror of
https://github.com/karl0ss/homepage.git
synced 2025-05-03 14:03:40 +01:00
commit
fb593b6c57
@ -3,7 +3,7 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"dev": "next dev -p 8080",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
@ -17,7 +17,6 @@
|
||||
"dockerode": "^3.3.4",
|
||||
"follow-redirects": "^1.15.2",
|
||||
"i18next": "^21.9.2",
|
||||
"jdownloader-client": "^1.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"json-rpc-2.0": "^1.4.1",
|
||||
"memory-cache": "^0.2.0",
|
||||
|
@ -1,8 +1,12 @@
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
import getServiceWidget from "utils/config/service-helpers";
|
||||
import createLogger from "utils/logger";
|
||||
import crypto from 'crypto';
|
||||
import querystring from 'querystring';
|
||||
|
||||
const { JDownloaderClient } = require('jdownloader-client')
|
||||
import { sha256, uniqueRid, validateRid, createEncryptionToken, decrypt, encrypt } from "./tools"
|
||||
|
||||
import getServiceWidget from "utils/config/service-helpers";
|
||||
import { httpProxy } from "utils/proxy/http";
|
||||
import createLogger from "utils/logger";
|
||||
|
||||
const proxyName = "jdownloaderProxyHandler";
|
||||
const logger = createLogger(proxyName);
|
||||
@ -22,6 +26,108 @@ async function getWidget(req) {
|
||||
return widget;
|
||||
}
|
||||
|
||||
async function login(loginSecret, deviceSecret, params) {
|
||||
const rid = uniqueRid();
|
||||
const path = `/my/connect?${querystring.stringify({...params, rid})}`;
|
||||
|
||||
const signature = crypto
|
||||
.createHmac('sha256', loginSecret)
|
||||
.update(path)
|
||||
.digest('hex');
|
||||
const url = `${new URL(`https://api.jdownloader.org${path}&signature=${signature}`)}`
|
||||
|
||||
const [status, contentType, data] = await httpProxy(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (status !== 200) {
|
||||
logger.error("HTTP %d communicating with jdownloader. Data: %s", status, data.toString());
|
||||
return [status, data];
|
||||
}
|
||||
|
||||
try {
|
||||
const decryptedData = JSON.parse(decrypt(data.toString(), loginSecret))
|
||||
const sessionToken = decryptedData.sessiontoken;
|
||||
validateRid(decryptedData, rid);
|
||||
const serverEncryptionToken = createEncryptionToken(loginSecret, sessionToken);
|
||||
const deviceEncryptionToken = createEncryptionToken(deviceSecret, sessionToken);
|
||||
return [status, decryptedData, contentType, serverEncryptionToken, deviceEncryptionToken, sessionToken];
|
||||
} catch (e) {
|
||||
logger.error("Error decoding jdownloader API data. Data: %s", data.toString());
|
||||
return [status, null];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function getDevice(serverEncryptionToken, deviceName, params) {
|
||||
const rid = uniqueRid();
|
||||
const path = `/my/listdevices?${querystring.stringify({...params, rid})}`;
|
||||
const signature = crypto
|
||||
.createHmac('sha256', serverEncryptionToken)
|
||||
.update(path)
|
||||
.digest('hex');
|
||||
const url = `${new URL(`https://api.jdownloader.org${path}&signature=${signature}`)}`
|
||||
|
||||
const [status, , data] = await httpProxy(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (status !== 200) {
|
||||
logger.error("HTTP %d communicating with jdownloader. Data: %s", status, data.toString());
|
||||
return [status, data];
|
||||
}
|
||||
|
||||
try {
|
||||
const decryptedData = JSON.parse(decrypt(data.toString(), serverEncryptionToken))
|
||||
const filteredDevice = decryptedData.list.filter(device => device.name === deviceName);
|
||||
return [status, filteredDevice[0].id];
|
||||
} catch (e) {
|
||||
logger.error("Error decoding jdownloader API data. Data: %s", data.toString());
|
||||
return [status, null];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function createBody(rid, query, params) {
|
||||
const baseBody = {
|
||||
apiVer: 1,
|
||||
rid,
|
||||
url: query
|
||||
};
|
||||
return params ? {...baseBody, params: [JSON.stringify(params)] } : baseBody;
|
||||
}
|
||||
|
||||
async function queryPackages(deviceEncryptionToken, deviceId, sessionToken, params) {
|
||||
const rid = uniqueRid();
|
||||
const body = encrypt(JSON.stringify(createBody(rid, '/downloadsV2/queryPackages', params)), deviceEncryptionToken);
|
||||
const url = `${new URL(`https://api.jdownloader.org/t_${encodeURI(sessionToken)}_${encodeURI(deviceId)}/downloadsV2/queryPackages`)}`
|
||||
const [status, , data] = await httpProxy(url, {
|
||||
method: 'POST',
|
||||
body,
|
||||
});
|
||||
|
||||
if (status !== 200) {
|
||||
logger.error("HTTP %d communicating with jdownloader. Data: %s", status, data.toString());
|
||||
return [status, data];
|
||||
}
|
||||
|
||||
try {
|
||||
const decryptedData = JSON.parse(decrypt(data.toString(), deviceEncryptionToken))
|
||||
return decryptedData.data;
|
||||
} catch (e) {
|
||||
logger.error("Error decoding JDRss jdownloader data. Data: %s", data.toString());
|
||||
return [status, null];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default async function jdownloaderProxyHandler(req, res) {
|
||||
const widget = await getWidget(req);
|
||||
|
||||
@ -29,10 +135,24 @@ export default async function jdownloaderProxyHandler(req, res) {
|
||||
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||
}
|
||||
logger.debug("Getting data from JDRss API");
|
||||
const client = new JDownloaderClient(widget.username, widget.password)
|
||||
await client.connect()
|
||||
const devices = await client.listDevices()
|
||||
const packageStatus = await client.downloadsQueryPackages(devices[0].id, {
|
||||
const {username} = widget
|
||||
const {password} = widget
|
||||
|
||||
const appKey = "homepage"
|
||||
const loginSecret = sha256(`${username}${password}server`)
|
||||
const deviceSecret = sha256(`${username}${password}device`)
|
||||
const email = username;
|
||||
|
||||
const loginData = await login(loginSecret, deviceSecret, {
|
||||
appKey,
|
||||
email
|
||||
})
|
||||
|
||||
const deviceData = await getDevice(loginData[3], widget.client, {
|
||||
sessiontoken: loginData[5]
|
||||
})
|
||||
|
||||
const packageStatus = await queryPackages(loginData[4], deviceData[1], loginData[5], {
|
||||
"bytesLoaded": false,
|
||||
"bytesTotal": true,
|
||||
"comment": false,
|
||||
@ -48,7 +168,9 @@ export default async function jdownloaderProxyHandler(req, res) {
|
||||
"saveTo": false,
|
||||
"maxResults": -1,
|
||||
"startAt": 0,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
let totalBytes = 0;
|
||||
let totalSpeed = 0;
|
||||
packageStatus.forEach(file => {
|
||||
|
55
src/widgets/jdownloader/tools.js
Normal file
55
src/widgets/jdownloader/tools.js
Normal file
@ -0,0 +1,55 @@
|
||||
import crypto from 'crypto';
|
||||
|
||||
export function sha256(data) {
|
||||
return crypto
|
||||
.createHash('sha256')
|
||||
.update(data)
|
||||
.digest();
|
||||
}
|
||||
|
||||
export function uniqueRid() {
|
||||
return Math.floor(Math.random() * 10e12);
|
||||
}
|
||||
|
||||
export function validateRid(decryptedData, rid) {
|
||||
if (decryptedData.rid !== rid) {
|
||||
throw new Error('RequestID mismatch');
|
||||
}
|
||||
return decryptedData;
|
||||
|
||||
}
|
||||
|
||||
export function decrypt(data, ivKey) {
|
||||
const iv = ivKey.slice(0, ivKey.length / 2);
|
||||
const key = ivKey.slice(ivKey.length / 2, ivKey.length);
|
||||
const cipher = crypto.createDecipheriv('aes-128-cbc', key, iv);
|
||||
return Buffer.concat([
|
||||
cipher.update(data, 'base64'),
|
||||
cipher.final()
|
||||
]).toString();
|
||||
}
|
||||
|
||||
export function createEncryptionToken(oldTokenBuff, updateToken) {
|
||||
const updateTokenBuff = Buffer.from(updateToken, 'hex');
|
||||
const mergedBuffer = Buffer.concat([oldTokenBuff, updateTokenBuff], oldTokenBuff.length + updateTokenBuff.length);
|
||||
return sha256(mergedBuffer);
|
||||
}
|
||||
|
||||
export function encrypt(data, ivKey) {
|
||||
if (typeof data !== 'string') {
|
||||
throw new Error('data no es un string');
|
||||
}
|
||||
if (!(ivKey instanceof Buffer)) {
|
||||
throw new Error('ivKey no es un buffer');
|
||||
}
|
||||
if (ivKey.length !== 32) {
|
||||
throw new Error('ivKey tiene que tener tamaño 32');
|
||||
}
|
||||
const stringIVKey = ivKey.toString('hex');
|
||||
const stringIV = stringIVKey.substring(0, stringIVKey.length / 2);
|
||||
const stringKey = stringIVKey.substring(stringIVKey.length / 2, stringIVKey.length);
|
||||
const iv = Buffer.from(stringIV, 'hex');
|
||||
const key = Buffer.from(stringKey, 'hex');
|
||||
const cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
|
||||
return cipher.update(data, 'utf8', 'base64') + cipher.final('base64');
|
||||
}
|
@ -1,12 +1,13 @@
|
||||
import jdownloaderProxyHandler from "./proxy";
|
||||
|
||||
const widget = {
|
||||
api: "{url}/api/{endpoint}",
|
||||
api: "https://api.jdownloader.org/{endpoint}/&signature={signature}",
|
||||
proxyHandler: jdownloaderProxyHandler,
|
||||
|
||||
mappings: {
|
||||
unified: {
|
||||
endpoint: "/",
|
||||
signature: "",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user