first commit
This commit is contained in:
177
apps/server/src/qbit/qbit.client.ts
Normal file
177
apps/server/src/qbit/qbit.client.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
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" },
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user