feat: adding aria2c

This commit is contained in:
Chubby Granny Chaser 2024-11-28 19:16:05 +00:00
parent c6d4b658a1
commit d7e06d6622
No known key found for this signature in database
11 changed files with 844 additions and 0 deletions

View file

@ -0,0 +1,32 @@
import path from "node:path";
import cp from "node:child_process";
import { app } from "electron";
export const startAria2 = () => {};
export class Aria2 {
private static process: cp.ChildProcess | null = null;
public static spawn() {
const binaryPath = app.isPackaged
? path.join(process.resourcesPath, "aria2", "aria2c")
: path.join(__dirname, "..", "..", "aria2", "aria2c");
this.process = cp.spawn(
binaryPath,
[
"--enable-rpc",
"--rpc-listen-all",
"--file-allocation=none",
"--allow-overwrite=true",
],
{ stdio: "inherit" }
);
console.log(this.process);
}
public static kill() {
this.process?.kill();
}
}

View file

@ -0,0 +1,119 @@
import axios, { AxiosInstance } from "axios";
import parseTorrent from "parse-torrent";
import type {
RealDebridAddMagnet,
RealDebridTorrentInfo,
RealDebridUnrestrictLink,
RealDebridUser,
} from "@types";
export class RealDebridClient {
private static instance: AxiosInstance;
private static baseURL = "https://api.real-debrid.com/rest/1.0";
static authorize(apiToken: string) {
this.instance = axios.create({
baseURL: this.baseURL,
headers: {
Authorization: `Bearer ${apiToken}`,
},
});
}
static async addMagnet(magnet: string) {
const searchParams = new URLSearchParams({ magnet });
const response = await this.instance.post<RealDebridAddMagnet>(
"/torrents/addMagnet",
searchParams.toString()
);
return response.data;
}
static async getTorrentInfo(id: string) {
const response = await this.instance.get<RealDebridTorrentInfo>(
`/torrents/info/${id}`
);
return response.data;
}
static async getUser() {
const response = await this.instance.get<RealDebridUser>(`/user`);
return response.data;
}
static async selectAllFiles(id: string) {
const searchParams = new URLSearchParams({ files: "all" });
return this.instance.post(
`/torrents/selectFiles/${id}`,
searchParams.toString()
);
}
static async unrestrictLink(link: string) {
const searchParams = new URLSearchParams({ link });
const response = await this.instance.post<RealDebridUnrestrictLink>(
"/unrestrict/link",
searchParams.toString()
);
return response.data;
}
private static async getAllTorrentsFromUser() {
const response =
await this.instance.get<RealDebridTorrentInfo[]>("/torrents");
return response.data;
}
static async getTorrentId(magnetUri: string) {
const userTorrents = await RealDebridClient.getAllTorrentsFromUser();
const { infoHash } = await parseTorrent(magnetUri);
const userTorrent = userTorrents.find(
(userTorrent) => userTorrent.hash === infoHash
);
if (userTorrent) return userTorrent.id;
const torrent = await RealDebridClient.addMagnet(magnetUri);
return torrent.id;
}
public static async getDownloadUrl(uri: string) {
let realDebridTorrentId: string | null = null;
if (uri.startsWith("magnet:")) {
realDebridTorrentId = await this.getTorrentId(uri);
}
if (realDebridTorrentId) {
let torrentInfo = await this.getTorrentInfo(realDebridTorrentId);
if (torrentInfo.status === "waiting_files_selection") {
await this.selectAllFiles(realDebridTorrentId);
torrentInfo = await this.getTorrentInfo(realDebridTorrentId);
}
const { links, status } = torrentInfo;
if (status === "downloaded") {
const [link] = links;
const { download } = await this.unrestrictLink(link);
return decodeURIComponent(download);
}
return null;
}
const { download } = await this.unrestrictLink(uri);
return decodeURIComponent(download);
}
}

View file

@ -0,0 +1,96 @@
import axios, { AxiosInstance } from "axios";
import parseTorrent from "parse-torrent";
import type {
TorBoxUserRequest,
TorBoxTorrentInfoRequest,
TorBoxAddTorrentRequest,
TorBoxRequestLinkRequest,
} from "@types";
export class TorBoxClient {
private static instance: AxiosInstance;
private static baseURL = "https://api.torbox.app/v1/api";
public static apiToken: string;
static authorize(apiToken: string) {
this.instance = axios.create({
baseURL: this.baseURL,
headers: {
Authorization: `Bearer ${apiToken}`,
},
});
this.apiToken = apiToken;
}
static async addMagnet(magnet: string) {
const form = new FormData();
form.append("magnet", magnet);
const response = await this.instance.post<TorBoxAddTorrentRequest>(
"/torrents/createtorrent",
form
);
return response.data.data;
}
static async getTorrentInfo(id: number) {
const response =
await this.instance.get<TorBoxTorrentInfoRequest>("/torrents/mylist");
const data = response.data.data;
const info = data.find((item) => item.id === id);
if (!info) {
return null;
}
return info;
}
static async getUser() {
const response = await this.instance.get<TorBoxUserRequest>(`/user/me`);
return response.data.data;
}
static async requestLink(id: number) {
const searchParams = new URLSearchParams({});
searchParams.set("token", this.apiToken);
searchParams.set("torrent_id", id.toString());
searchParams.set("zip_link", "true");
const response = await this.instance.get<TorBoxRequestLinkRequest>(
"/torrents/requestdl?" + searchParams.toString()
);
if (response.status !== 200) {
console.error(response.data.error);
console.error(response.data.detail);
return null;
}
return response.data.data;
}
private static async getAllTorrentsFromUser() {
const response =
await this.instance.get<TorBoxTorrentInfoRequest>("/torrents/mylist");
return response.data.data;
}
static async getTorrentId(magnetUri: string) {
const userTorrents = await this.getAllTorrentsFromUser();
const { infoHash } = await parseTorrent(magnetUri);
const userTorrent = userTorrents.find(
(userTorrent) => userTorrent.hash === infoHash
);
if (userTorrent) return userTorrent.id;
const torrent = await this.addMagnet(magnetUri);
return torrent.torrent_id;
}
}

View file

@ -0,0 +1,96 @@
import axios from "axios";
import cp from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import crypto from "node:crypto";
import { logger } from "./logger";
import { Readable } from "node:stream";
import { app, dialog } from "electron";
const binaryNameByPlatform: Partial<Record<NodeJS.Platform, string>> = {
darwin: "hydra-python-rpc",
linux: "hydra-python-rpc",
win32: "hydra-python-rpc.exe",
};
export class PythonRPC {
public static readonly BITTORRENT_PORT = "5881";
public static readonly RPC_PORT = "8084";
private static readonly RPC_PASSWORD = crypto.randomBytes(32).toString("hex");
private static pythonProcess: cp.ChildProcess | null = null;
public static rpc = axios.create({
baseURL: `http://localhost:${this.RPC_PORT}`,
headers: {
"x-hydra-rpc-password": this.RPC_PASSWORD,
},
});
private static logStderr(readable: Readable | null) {
if (!readable) return;
readable.setEncoding("utf-8");
readable.on("data", logger.log);
}
public static spawn() {
console.log([this.BITTORRENT_PORT, this.RPC_PORT, this.RPC_PASSWORD]);
const commonArgs = [this.BITTORRENT_PORT, this.RPC_PORT, this.RPC_PASSWORD];
if (app.isPackaged) {
const binaryName = binaryNameByPlatform[process.platform]!;
const binaryPath = path.join(
process.resourcesPath,
"hydra-python-rpc",
binaryName
);
if (!fs.existsSync(binaryPath)) {
dialog.showErrorBox(
"Fatal",
"Hydra Python Instance binary not found. Please check if it has been removed by Windows Defender."
);
app.quit();
}
const childProcess = cp.spawn(binaryPath, commonArgs, {
windowsHide: true,
stdio: ["inherit", "inherit"],
});
this.logStderr(childProcess.stderr);
this.pythonProcess = childProcess;
} else {
const scriptPath = path.join(
__dirname,
"..",
"..",
"python_rpc",
"main.py"
);
console.log(scriptPath);
const childProcess = cp.spawn("python3", [scriptPath, ...commonArgs], {
stdio: ["inherit", "inherit"],
});
this.logStderr(childProcess.stderr);
this.pythonProcess = childProcess;
}
}
public static kill() {
if (this.pythonProcess) {
logger.log("Killing python process");
this.pythonProcess.kill();
this.pythonProcess = null;
}
}
}

77
src/types/torbox.types.ts Normal file
View file

@ -0,0 +1,77 @@
export interface TorBoxUser {
id: number;
email: string;
plan: string;
expiration: string;
}
export interface TorBoxUserRequest {
success: boolean;
detail: string;
error: string;
data: TorBoxUser;
}
export interface TorBoxFile {
id: number;
md5: string;
s3_path: string;
name: string;
size: number;
mimetype: string;
short_name: string;
}
export interface TorBoxTorrentInfo {
id: number;
hash: string;
created_at: string;
updated_at: string;
magnet: string;
size: number;
active: boolean;
cached: boolean;
auth_id: string;
download_state:
| "downloading"
| "uploading"
| "stalled (no seeds)"
| "paused"
| "completed"
| "cached"
| "metaDL"
| "checkingResumeData";
seeds: number;
ratio: number;
progress: number;
download_speed: number;
upload_speed: number;
name: string;
eta: number;
files: TorBoxFile[];
}
export interface TorBoxTorrentInfoRequest {
success: boolean;
detail: string;
error: string;
data: TorBoxTorrentInfo[];
}
export interface TorBoxAddTorrentRequest {
success: boolean;
detail: string;
error: string;
data: {
torrent_id: number;
name: string;
hash: string;
};
}
export interface TorBoxRequestLinkRequest {
success: boolean;
detail: string;
error: string;
data: string;
}