178 lines
5.0 KiB
TypeScript
178 lines
5.0 KiB
TypeScript
import axios, { AxiosInstance } from "axios";
|
|
import { CookieJar } from "tough-cookie";
|
|
import { wrapper } from "axios-cookiejar-support";
|
|
import FormData from "form-data";
|
|
import fs from "node:fs";
|
|
import { config } from "../config"
|
|
import { logger } from "../utils/logger"
|
|
import {
|
|
QbitPeerList,
|
|
QbitTorrentInfo,
|
|
QbitTorrentProperties,
|
|
QbitTransferInfo,
|
|
} from "./qbit.types"
|
|
|
|
export class QbitClient {
|
|
private client: AxiosInstance;
|
|
private jar: CookieJar;
|
|
private loggedIn = false;
|
|
|
|
constructor() {
|
|
this.jar = new CookieJar();
|
|
this.client = wrapper(
|
|
axios.create({
|
|
baseURL: config.qbitBaseUrl,
|
|
jar: this.jar,
|
|
withCredentials: true,
|
|
})
|
|
);
|
|
}
|
|
|
|
async login(): Promise<void> {
|
|
if (!config.qbitBaseUrl) {
|
|
throw new Error("QBIT_BASE_URL missing");
|
|
}
|
|
const form = new URLSearchParams();
|
|
form.append("username", config.qbitUsername);
|
|
form.append("password", config.qbitPassword);
|
|
await this.client.post("/api/v2/auth/login", form, {
|
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
});
|
|
this.loggedIn = true;
|
|
}
|
|
|
|
private async request<T>(fn: () => Promise<T>): Promise<T> {
|
|
try {
|
|
if (!this.loggedIn) {
|
|
await this.login();
|
|
}
|
|
return await fn();
|
|
} catch (error) {
|
|
if (
|
|
axios.isAxiosError(error) &&
|
|
(error.response?.status === 401 || error.response?.status === 403)
|
|
) {
|
|
logger.warn("qBittorrent session expired, re-login");
|
|
this.loggedIn = false;
|
|
await this.login();
|
|
return await fn();
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async getVersion(): Promise<string> {
|
|
const response = await this.request(() =>
|
|
this.client.get<string>("/api/v2/app/version")
|
|
);
|
|
return response.data;
|
|
}
|
|
|
|
async getTorrentsInfo(): Promise<QbitTorrentInfo[]> {
|
|
const response = await this.request(() =>
|
|
this.client.get<QbitTorrentInfo[]>("/api/v2/torrents/info", {
|
|
params: { filter: "all" },
|
|
})
|
|
);
|
|
return response.data;
|
|
}
|
|
|
|
async getTransferInfo(): Promise<QbitTransferInfo> {
|
|
const response = await this.request(() =>
|
|
this.client.get<QbitTransferInfo>("/api/v2/transfer/info")
|
|
);
|
|
return response.data;
|
|
}
|
|
|
|
async getTorrentProperties(hash: string): Promise<QbitTorrentProperties> {
|
|
const response = await this.request(() =>
|
|
this.client.get<QbitTorrentProperties>("/api/v2/torrents/properties", {
|
|
params: { hash },
|
|
})
|
|
);
|
|
return response.data;
|
|
}
|
|
|
|
async getTorrentPeers(hash: string): Promise<QbitPeerList> {
|
|
const response = await this.request(() =>
|
|
this.client.get<QbitPeerList>("/api/v2/sync/torrentPeers", {
|
|
params: { hash },
|
|
})
|
|
);
|
|
return response.data;
|
|
}
|
|
|
|
async exportTorrent(hash: string): Promise<Buffer> {
|
|
const response = await this.request(() =>
|
|
this.client.get<ArrayBuffer>("/api/v2/torrents/export", {
|
|
params: { hashes: hash },
|
|
responseType: "arraybuffer",
|
|
})
|
|
);
|
|
return Buffer.from(response.data);
|
|
}
|
|
|
|
async addTorrentByMagnet(magnet: string, options: Record<string, string> = {}) {
|
|
const form = new URLSearchParams();
|
|
form.append("urls", magnet);
|
|
Object.entries(options).forEach(([key, value]) => form.append(key, value));
|
|
await this.request(() =>
|
|
this.client.post("/api/v2/torrents/add", form, {
|
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
})
|
|
);
|
|
}
|
|
|
|
async addTorrentByFile(filePath: string, options: Record<string, string> = {}) {
|
|
const form = new FormData();
|
|
form.append("torrents", fs.createReadStream(filePath));
|
|
Object.entries(options).forEach(([key, value]) => form.append(key, value));
|
|
await this.request(() =>
|
|
this.client.post("/api/v2/torrents/add", form, {
|
|
headers: form.getHeaders(),
|
|
})
|
|
);
|
|
}
|
|
|
|
async deleteTorrent(hash: string, deleteFiles = true) {
|
|
const form = new URLSearchParams();
|
|
form.append("hashes", hash);
|
|
form.append("deleteFiles", deleteFiles ? "true" : "false");
|
|
await this.request(() =>
|
|
this.client.post("/api/v2/torrents/delete", form, {
|
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
})
|
|
);
|
|
}
|
|
|
|
async pauseTorrent(hash: string) {
|
|
const form = new URLSearchParams();
|
|
form.append("hashes", hash);
|
|
await this.request(() =>
|
|
this.client.post("/api/v2/torrents/pause", form, {
|
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
})
|
|
);
|
|
}
|
|
|
|
async resumeTorrent(hash: string) {
|
|
const form = new URLSearchParams();
|
|
form.append("hashes", hash);
|
|
await this.request(() =>
|
|
this.client.post("/api/v2/torrents/resume", form, {
|
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
})
|
|
);
|
|
}
|
|
|
|
async banPeers(peers: string[]) {
|
|
const form = new URLSearchParams();
|
|
form.append("peers", peers.join("|"));
|
|
await this.request(() =>
|
|
this.client.post("/api/v2/transfer/banPeers", form, {
|
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
})
|
|
);
|
|
}
|
|
}
|