mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-03-09 15:40:26 +00:00
Fix lints and add back the option to seed after download complete
This commit is contained in:
parent
d08d14a9e4
commit
9e6143ebc9
15 changed files with 360 additions and 295 deletions
|
@ -7,12 +7,12 @@ const authenticateAllDebrid = async (
|
||||||
) => {
|
) => {
|
||||||
AllDebridClient.authorize(apiKey);
|
AllDebridClient.authorize(apiKey);
|
||||||
const result = await AllDebridClient.getUser();
|
const result = await AllDebridClient.getUser();
|
||||||
|
|
||||||
if ('error_code' in result) {
|
if ("error_code" in result) {
|
||||||
return { error_code: result.error_code };
|
return { error_code: result.error_code };
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.user;
|
return result.user;
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent("authenticateAllDebrid", authenticateAllDebrid);
|
registerEvent("authenticateAllDebrid", authenticateAllDebrid);
|
||||||
|
|
|
@ -31,9 +31,7 @@ const updateUserPreferences = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preferences.allDebridApiKey) {
|
if (preferences.allDebridApiKey) {
|
||||||
preferences.allDebridApiKey = Crypto.encrypt(
|
preferences.allDebridApiKey = Crypto.encrypt(preferences.allDebridApiKey);
|
||||||
preferences.allDebridApiKey
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preferences.torBoxApiToken) {
|
if (preferences.torBoxApiToken) {
|
||||||
|
|
|
@ -45,15 +45,11 @@ export const loadState = async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userPreferences?.allDebridApiKey) {
|
if (userPreferences?.allDebridApiKey) {
|
||||||
AllDebridClient.authorize(
|
AllDebridClient.authorize(Crypto.decrypt(userPreferences.allDebridApiKey));
|
||||||
Crypto.decrypt(userPreferences.allDebridApiKey)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userPreferences?.torBoxApiToken) {
|
if (userPreferences?.torBoxApiToken) {
|
||||||
TorBoxClient.authorize(
|
TorBoxClient.authorize(Crypto.decrypt(userPreferences.torBoxApiToken));
|
||||||
Crypto.decrypt(userPreferences.torBoxApiToken)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ludusavi.addManifestToLudusaviConfig();
|
Ludusavi.addManifestToLudusaviConfig();
|
||||||
|
@ -126,7 +122,8 @@ const migrateFromSqlite = async () => {
|
||||||
.select("*")
|
.select("*")
|
||||||
.then(async (userPreferences) => {
|
.then(async (userPreferences) => {
|
||||||
if (userPreferences.length > 0) {
|
if (userPreferences.length > 0) {
|
||||||
const { realDebridApiToken, allDebridApiKey, ...rest } = userPreferences[0];
|
const { realDebridApiToken, allDebridApiKey, ...rest } =
|
||||||
|
userPreferences[0];
|
||||||
|
|
||||||
await db.put<string, UserPreferences>(
|
await db.put<string, UserPreferences>(
|
||||||
levelKeys.userPreferences,
|
levelKeys.userPreferences,
|
||||||
|
|
|
@ -3,269 +3,310 @@ import type { AllDebridUser } from "@types";
|
||||||
import { logger } from "@main/services";
|
import { logger } from "@main/services";
|
||||||
|
|
||||||
interface AllDebridMagnetStatus {
|
interface AllDebridMagnetStatus {
|
||||||
id: number;
|
id: number;
|
||||||
|
filename: string;
|
||||||
|
size: number;
|
||||||
|
status: string;
|
||||||
|
statusCode: number;
|
||||||
|
downloaded: number;
|
||||||
|
uploaded: number;
|
||||||
|
seeders: number;
|
||||||
|
downloadSpeed: number;
|
||||||
|
uploadSpeed: number;
|
||||||
|
uploadDate: number;
|
||||||
|
completionDate: number;
|
||||||
|
links: Array<{
|
||||||
|
link: string;
|
||||||
filename: string;
|
filename: string;
|
||||||
size: number;
|
size: number;
|
||||||
status: string;
|
}>;
|
||||||
statusCode: number;
|
|
||||||
downloaded: number;
|
|
||||||
uploaded: number;
|
|
||||||
seeders: number;
|
|
||||||
downloadSpeed: number;
|
|
||||||
uploadSpeed: number;
|
|
||||||
uploadDate: number;
|
|
||||||
completionDate: number;
|
|
||||||
links: Array<{
|
|
||||||
link: string;
|
|
||||||
filename: string;
|
|
||||||
size: number;
|
|
||||||
}>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AllDebridError {
|
interface AllDebridError {
|
||||||
code: string;
|
code: string;
|
||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AllDebridDownloadUrl {
|
interface AllDebridDownloadUrl {
|
||||||
link: string;
|
link: string;
|
||||||
size?: number;
|
size?: number;
|
||||||
filename?: string;
|
filename?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AllDebridClient {
|
export class AllDebridClient {
|
||||||
private static instance: AxiosInstance;
|
private static instance: AxiosInstance;
|
||||||
private static readonly baseURL = "https://api.alldebrid.com/v4";
|
private static readonly baseURL = "https://api.alldebrid.com/v4";
|
||||||
|
|
||||||
static authorize(apiKey: string) {
|
static authorize(apiKey: string) {
|
||||||
logger.info("[AllDebrid] Authorizing with key:", apiKey ? "***" : "empty");
|
logger.info("[AllDebrid] Authorizing with key:", apiKey ? "***" : "empty");
|
||||||
this.instance = axios.create({
|
this.instance = axios.create({
|
||||||
baseURL: this.baseURL,
|
baseURL: this.baseURL,
|
||||||
params: {
|
params: {
|
||||||
agent: "hydra",
|
agent: "hydra",
|
||||||
apikey: apiKey
|
apikey: apiKey,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getUser() {
|
static async getUser() {
|
||||||
try {
|
try {
|
||||||
const response = await this.instance.get<{
|
const response = await this.instance.get<{
|
||||||
status: string;
|
status: string;
|
||||||
data?: { user: AllDebridUser };
|
data?: { user: AllDebridUser };
|
||||||
error?: AllDebridError;
|
error?: AllDebridError;
|
||||||
}>("/user");
|
}>("/user");
|
||||||
|
|
||||||
logger.info("[AllDebrid] API Response:", response.data);
|
logger.info("[AllDebrid] API Response:", response.data);
|
||||||
|
|
||||||
if (response.data.status === "error") {
|
if (response.data.status === "error") {
|
||||||
const error = response.data.error;
|
const error = response.data.error;
|
||||||
logger.error("[AllDebrid] API Error:", error);
|
logger.error("[AllDebrid] API Error:", error);
|
||||||
if (error?.code === "AUTH_MISSING_APIKEY") {
|
if (error?.code === "AUTH_MISSING_APIKEY") {
|
||||||
return { error_code: "alldebrid_missing_key" };
|
return { error_code: "alldebrid_missing_key" };
|
||||||
}
|
|
||||||
if (error?.code === "AUTH_BAD_APIKEY") {
|
|
||||||
return { error_code: "alldebrid_invalid_key" };
|
|
||||||
}
|
|
||||||
if (error?.code === "AUTH_BLOCKED") {
|
|
||||||
return { error_code: "alldebrid_blocked" };
|
|
||||||
}
|
|
||||||
if (error?.code === "AUTH_USER_BANNED") {
|
|
||||||
return { error_code: "alldebrid_banned" };
|
|
||||||
}
|
|
||||||
return { error_code: "alldebrid_unknown_error" };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.data.data?.user) {
|
|
||||||
logger.error("[AllDebrid] No user data in response");
|
|
||||||
return { error_code: "alldebrid_invalid_response" };
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("[AllDebrid] Successfully got user:", response.data.data.user.username);
|
|
||||||
return { user: response.data.data.user };
|
|
||||||
} catch (error: any) {
|
|
||||||
logger.error("[AllDebrid] Request Error:", error);
|
|
||||||
if (error.response?.data?.error) {
|
|
||||||
return { error_code: "alldebrid_invalid_key" };
|
|
||||||
}
|
|
||||||
return { error_code: "alldebrid_network_error" };
|
|
||||||
}
|
}
|
||||||
}
|
if (error?.code === "AUTH_BAD_APIKEY") {
|
||||||
|
return { error_code: "alldebrid_invalid_key" };
|
||||||
private static async uploadMagnet(magnet: string) {
|
|
||||||
try {
|
|
||||||
logger.info("[AllDebrid] Uploading magnet with params:", { magnet });
|
|
||||||
|
|
||||||
const response = await this.instance.get("/magnet/upload", {
|
|
||||||
params: {
|
|
||||||
magnets: [magnet]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.info("[AllDebrid] Upload Magnet Raw Response:", JSON.stringify(response.data, null, 2));
|
|
||||||
|
|
||||||
if (response.data.status === "error") {
|
|
||||||
throw new Error(response.data.error?.message || "Unknown error");
|
|
||||||
}
|
|
||||||
|
|
||||||
const magnetInfo = response.data.data.magnets[0];
|
|
||||||
logger.info("[AllDebrid] Magnet Info:", JSON.stringify(magnetInfo, null, 2));
|
|
||||||
|
|
||||||
if (magnetInfo.error) {
|
|
||||||
throw new Error(magnetInfo.error.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return magnetInfo.id;
|
|
||||||
} catch (error: any) {
|
|
||||||
logger.error("[AllDebrid] Upload Magnet Error:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
|
if (error?.code === "AUTH_BLOCKED") {
|
||||||
|
return { error_code: "alldebrid_blocked" };
|
||||||
|
}
|
||||||
|
if (error?.code === "AUTH_USER_BANNED") {
|
||||||
|
return { error_code: "alldebrid_banned" };
|
||||||
|
}
|
||||||
|
return { error_code: "alldebrid_unknown_error" };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.data.data?.user) {
|
||||||
|
logger.error("[AllDebrid] No user data in response");
|
||||||
|
return { error_code: "alldebrid_invalid_response" };
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"[AllDebrid] Successfully got user:",
|
||||||
|
response.data.data.user.username
|
||||||
|
);
|
||||||
|
return { user: response.data.data.user };
|
||||||
|
} catch (error: any) {
|
||||||
|
logger.error("[AllDebrid] Request Error:", error);
|
||||||
|
if (error.response?.data?.error) {
|
||||||
|
return { error_code: "alldebrid_invalid_key" };
|
||||||
|
}
|
||||||
|
return { error_code: "alldebrid_network_error" };
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static async checkMagnetStatus(magnetId: number): Promise<AllDebridMagnetStatus> {
|
private static async uploadMagnet(magnet: string) {
|
||||||
try {
|
try {
|
||||||
logger.info("[AllDebrid] Checking magnet status for ID:", magnetId);
|
logger.info("[AllDebrid] Uploading magnet with params:", { magnet });
|
||||||
|
|
||||||
const response = await this.instance.get(`/magnet/status`, {
|
|
||||||
params: {
|
|
||||||
id: magnetId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.info("[AllDebrid] Check Magnet Status Raw Response:", JSON.stringify(response.data, null, 2));
|
const response = await this.instance.get("/magnet/upload", {
|
||||||
|
params: {
|
||||||
|
magnets: [magnet],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (!response.data) {
|
logger.info(
|
||||||
throw new Error("No response data received");
|
"[AllDebrid] Upload Magnet Raw Response:",
|
||||||
}
|
JSON.stringify(response.data, null, 2)
|
||||||
|
);
|
||||||
|
|
||||||
if (response.data.status === "error") {
|
if (response.data.status === "error") {
|
||||||
throw new Error(response.data.error?.message || "Unknown error");
|
throw new Error(response.data.error?.message || "Unknown error");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verificăm noua structură a răspunsului
|
const magnetInfo = response.data.data.magnets[0];
|
||||||
const magnetData = response.data.data?.magnets;
|
logger.info(
|
||||||
if (!magnetData || typeof magnetData !== 'object') {
|
"[AllDebrid] Magnet Info:",
|
||||||
logger.error("[AllDebrid] Invalid response structure:", JSON.stringify(response.data, null, 2));
|
JSON.stringify(magnetInfo, null, 2)
|
||||||
throw new Error("Invalid magnet status response format");
|
);
|
||||||
}
|
|
||||||
|
|
||||||
// Convertim răspunsul în formatul așteptat
|
if (magnetInfo.error) {
|
||||||
const magnetStatus: AllDebridMagnetStatus = {
|
throw new Error(magnetInfo.error.message);
|
||||||
id: magnetData.id,
|
}
|
||||||
filename: magnetData.filename,
|
|
||||||
size: magnetData.size,
|
return magnetInfo.id;
|
||||||
status: magnetData.status,
|
} catch (error: any) {
|
||||||
statusCode: magnetData.statusCode,
|
logger.error("[AllDebrid] Upload Magnet Error:", error);
|
||||||
downloaded: magnetData.downloaded,
|
throw error;
|
||||||
uploaded: magnetData.uploaded,
|
}
|
||||||
seeders: magnetData.seeders,
|
}
|
||||||
downloadSpeed: magnetData.downloadSpeed,
|
|
||||||
uploadSpeed: magnetData.uploadSpeed,
|
private static async checkMagnetStatus(
|
||||||
uploadDate: magnetData.uploadDate,
|
magnetId: number
|
||||||
completionDate: magnetData.completionDate,
|
): Promise<AllDebridMagnetStatus> {
|
||||||
links: magnetData.links.map(link => ({
|
try {
|
||||||
link: link.link,
|
logger.info("[AllDebrid] Checking magnet status for ID:", magnetId);
|
||||||
|
|
||||||
|
const response = await this.instance.get(`/magnet/status`, {
|
||||||
|
params: {
|
||||||
|
id: magnetId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"[AllDebrid] Check Magnet Status Raw Response:",
|
||||||
|
JSON.stringify(response.data, null, 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.data) {
|
||||||
|
throw new Error("No response data received");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.data.status === "error") {
|
||||||
|
throw new Error(response.data.error?.message || "Unknown error");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificăm noua structură a răspunsului
|
||||||
|
const magnetData = response.data.data?.magnets;
|
||||||
|
if (!magnetData || typeof magnetData !== "object") {
|
||||||
|
logger.error(
|
||||||
|
"[AllDebrid] Invalid response structure:",
|
||||||
|
JSON.stringify(response.data, null, 2)
|
||||||
|
);
|
||||||
|
throw new Error("Invalid magnet status response format");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convertim răspunsul în formatul așteptat
|
||||||
|
const magnetStatus: AllDebridMagnetStatus = {
|
||||||
|
id: magnetData.id,
|
||||||
|
filename: magnetData.filename,
|
||||||
|
size: magnetData.size,
|
||||||
|
status: magnetData.status,
|
||||||
|
statusCode: magnetData.statusCode,
|
||||||
|
downloaded: magnetData.downloaded,
|
||||||
|
uploaded: magnetData.uploaded,
|
||||||
|
seeders: magnetData.seeders,
|
||||||
|
downloadSpeed: magnetData.downloadSpeed,
|
||||||
|
uploadSpeed: magnetData.uploadSpeed,
|
||||||
|
uploadDate: magnetData.uploadDate,
|
||||||
|
completionDate: magnetData.completionDate,
|
||||||
|
links: magnetData.links.map((link) => ({
|
||||||
|
link: link.link,
|
||||||
|
filename: link.filename,
|
||||||
|
size: link.size,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"[AllDebrid] Magnet Status:",
|
||||||
|
JSON.stringify(magnetStatus, null, 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
return magnetStatus;
|
||||||
|
} catch (error: any) {
|
||||||
|
logger.error("[AllDebrid] Check Magnet Status Error:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async unlockLink(link: string) {
|
||||||
|
try {
|
||||||
|
const response = await this.instance.get<{
|
||||||
|
status: string;
|
||||||
|
data?: { link: string };
|
||||||
|
error?: AllDebridError;
|
||||||
|
}>("/link/unlock", {
|
||||||
|
params: {
|
||||||
|
link,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data.status === "error") {
|
||||||
|
throw new Error(response.data.error?.message || "Unknown error");
|
||||||
|
}
|
||||||
|
|
||||||
|
const unlockedLink = response.data.data?.link;
|
||||||
|
if (!unlockedLink) {
|
||||||
|
throw new Error("No download link received from AllDebrid");
|
||||||
|
}
|
||||||
|
|
||||||
|
return unlockedLink;
|
||||||
|
} catch (error: any) {
|
||||||
|
logger.error("[AllDebrid] Unlock Link Error:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getDownloadUrls(
|
||||||
|
uri: string
|
||||||
|
): Promise<AllDebridDownloadUrl[]> {
|
||||||
|
try {
|
||||||
|
logger.info("[AllDebrid] Getting download URLs for URI:", uri);
|
||||||
|
|
||||||
|
if (uri.startsWith("magnet:")) {
|
||||||
|
logger.info("[AllDebrid] Detected magnet link, uploading...");
|
||||||
|
// 1. Upload magnet
|
||||||
|
const magnetId = await this.uploadMagnet(uri);
|
||||||
|
logger.info("[AllDebrid] Magnet uploaded, ID:", magnetId);
|
||||||
|
|
||||||
|
// 2. Verificăm statusul până când avem link-uri
|
||||||
|
let retries = 0;
|
||||||
|
let magnetStatus: AllDebridMagnetStatus;
|
||||||
|
|
||||||
|
do {
|
||||||
|
magnetStatus = await this.checkMagnetStatus(magnetId);
|
||||||
|
logger.info(
|
||||||
|
"[AllDebrid] Magnet status:",
|
||||||
|
magnetStatus.status,
|
||||||
|
"statusCode:",
|
||||||
|
magnetStatus.statusCode
|
||||||
|
);
|
||||||
|
|
||||||
|
if (magnetStatus.statusCode === 4) {
|
||||||
|
// Ready
|
||||||
|
// Deblocăm fiecare link în parte și aruncăm eroare dacă oricare eșuează
|
||||||
|
const unlockedLinks = await Promise.all(
|
||||||
|
magnetStatus.links.map(async (link) => {
|
||||||
|
try {
|
||||||
|
const unlockedLink = await this.unlockLink(link.link);
|
||||||
|
logger.info(
|
||||||
|
"[AllDebrid] Successfully unlocked link:",
|
||||||
|
unlockedLink
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
link: unlockedLink,
|
||||||
|
size: link.size,
|
||||||
filename: link.filename,
|
filename: link.filename,
|
||||||
size: link.size
|
};
|
||||||
}))
|
} catch (error) {
|
||||||
};
|
logger.error(
|
||||||
|
"[AllDebrid] Failed to unlock link:",
|
||||||
logger.info("[AllDebrid] Magnet Status:", JSON.stringify(magnetStatus, null, 2));
|
link.link,
|
||||||
|
error
|
||||||
return magnetStatus;
|
);
|
||||||
} catch (error: any) {
|
throw new Error("Failed to unlock all links");
|
||||||
logger.error("[AllDebrid] Check Magnet Status Error:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async unlockLink(link: string) {
|
|
||||||
try {
|
|
||||||
const response = await this.instance.get<{
|
|
||||||
status: string;
|
|
||||||
data?: { link: string };
|
|
||||||
error?: AllDebridError;
|
|
||||||
}>("/link/unlock", {
|
|
||||||
params: {
|
|
||||||
link
|
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
|
||||||
if (response.data.status === "error") {
|
logger.info(
|
||||||
throw new Error(response.data.error?.message || "Unknown error");
|
"[AllDebrid] Got unlocked download links:",
|
||||||
}
|
unlockedLinks
|
||||||
|
);
|
||||||
|
return unlockedLinks;
|
||||||
|
}
|
||||||
|
|
||||||
const unlockedLink = response.data.data?.link;
|
if (retries++ > 30) {
|
||||||
if (!unlockedLink) {
|
// Maximum 30 de încercări
|
||||||
throw new Error("No download link received from AllDebrid");
|
throw new Error("Timeout waiting for magnet to be ready");
|
||||||
}
|
}
|
||||||
|
|
||||||
return unlockedLink;
|
await new Promise((resolve) => setTimeout(resolve, 2000)); // Așteptăm 2 secunde între verificări
|
||||||
} catch (error: any) {
|
} while (true);
|
||||||
logger.error("[AllDebrid] Unlock Link Error:", error);
|
} else {
|
||||||
throw error;
|
logger.info("[AllDebrid] Regular link, unlocking...");
|
||||||
}
|
// Pentru link-uri normale, doar debridam link-ul
|
||||||
}
|
const downloadUrl = await this.unlockLink(uri);
|
||||||
|
logger.info("[AllDebrid] Got unlocked download URL:", downloadUrl);
|
||||||
public static async getDownloadUrls(uri: string): Promise<AllDebridDownloadUrl[]> {
|
return [
|
||||||
try {
|
{
|
||||||
logger.info("[AllDebrid] Getting download URLs for URI:", uri);
|
link: downloadUrl,
|
||||||
|
},
|
||||||
if (uri.startsWith("magnet:")) {
|
];
|
||||||
logger.info("[AllDebrid] Detected magnet link, uploading...");
|
}
|
||||||
// 1. Upload magnet
|
} catch (error: any) {
|
||||||
const magnetId = await this.uploadMagnet(uri);
|
logger.error("[AllDebrid] Get Download URLs Error:", error);
|
||||||
logger.info("[AllDebrid] Magnet uploaded, ID:", magnetId);
|
throw error;
|
||||||
|
|
||||||
// 2. Verificăm statusul până când avem link-uri
|
|
||||||
let retries = 0;
|
|
||||||
let magnetStatus: AllDebridMagnetStatus;
|
|
||||||
|
|
||||||
do {
|
|
||||||
magnetStatus = await this.checkMagnetStatus(magnetId);
|
|
||||||
logger.info("[AllDebrid] Magnet status:", magnetStatus.status, "statusCode:", magnetStatus.statusCode);
|
|
||||||
|
|
||||||
if (magnetStatus.statusCode === 4) { // Ready
|
|
||||||
// Deblocăm fiecare link în parte și aruncăm eroare dacă oricare eșuează
|
|
||||||
const unlockedLinks = await Promise.all(
|
|
||||||
magnetStatus.links.map(async link => {
|
|
||||||
try {
|
|
||||||
const unlockedLink = await this.unlockLink(link.link);
|
|
||||||
logger.info("[AllDebrid] Successfully unlocked link:", unlockedLink);
|
|
||||||
return {
|
|
||||||
link: unlockedLink,
|
|
||||||
size: link.size,
|
|
||||||
filename: link.filename
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("[AllDebrid] Failed to unlock link:", link.link, error);
|
|
||||||
throw new Error("Failed to unlock all links");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.info("[AllDebrid] Got unlocked download links:", unlockedLinks);
|
|
||||||
return unlockedLinks;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (retries++ > 30) { // Maximum 30 de încercări
|
|
||||||
throw new Error("Timeout waiting for magnet to be ready");
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000)); // Așteptăm 2 secunde între verificări
|
|
||||||
} while (true);
|
|
||||||
} else {
|
|
||||||
logger.info("[AllDebrid] Regular link, unlocking...");
|
|
||||||
// Pentru link-uri normale, doar debridam link-ul
|
|
||||||
const downloadUrl = await this.unlockLink(uri);
|
|
||||||
logger.info("[AllDebrid] Got unlocked download URL:", downloadUrl);
|
|
||||||
return [{
|
|
||||||
link: downloadUrl
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
logger.error("[AllDebrid] Get Download URLs Error:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,17 +17,6 @@ import { db, downloadsSublevel, gamesSublevel, levelKeys } from "@main/level";
|
||||||
import { sortBy } from "lodash-es";
|
import { sortBy } from "lodash-es";
|
||||||
import { TorBoxClient } from "./torbox";
|
import { TorBoxClient } from "./torbox";
|
||||||
import { AllDebridClient } from "./all-debrid";
|
import { AllDebridClient } from "./all-debrid";
|
||||||
import { spawn } from "child_process";
|
|
||||||
|
|
||||||
interface GamePayload {
|
|
||||||
action: string;
|
|
||||||
game_id: string;
|
|
||||||
url: string | string[];
|
|
||||||
save_path: string;
|
|
||||||
header?: string;
|
|
||||||
out?: string;
|
|
||||||
total_size?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DownloadManager {
|
export class DownloadManager {
|
||||||
private static downloadingGameId: string | null = null;
|
private static downloadingGameId: string | null = null;
|
||||||
|
@ -44,6 +33,7 @@ export class DownloadManager {
|
||||||
})
|
})
|
||||||
: undefined,
|
: undefined,
|
||||||
downloadsToSeed?.map((download) => ({
|
downloadsToSeed?.map((download) => ({
|
||||||
|
action: "seed",
|
||||||
game_id: levelKeys.game(download.shop, download.objectId),
|
game_id: levelKeys.game(download.shop, download.objectId),
|
||||||
url: download.uri,
|
url: download.uri,
|
||||||
save_path: download.downloadPath,
|
save_path: download.downloadPath,
|
||||||
|
@ -145,15 +135,45 @@ export class DownloadManager {
|
||||||
if (progress === 1 && download) {
|
if (progress === 1 && download) {
|
||||||
publishDownloadCompleteNotification(game);
|
publishDownloadCompleteNotification(game);
|
||||||
|
|
||||||
await downloadsSublevel.put(gameId, {
|
if (
|
||||||
...download,
|
userPreferences?.seedAfterDownloadComplete &&
|
||||||
status: "complete",
|
download.downloader === Downloader.Torrent
|
||||||
shouldSeed: false,
|
) {
|
||||||
queued: false,
|
downloadsSublevel.put(gameId, {
|
||||||
});
|
...download,
|
||||||
|
status: "seeding",
|
||||||
|
shouldSeed: true,
|
||||||
|
queued: false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
downloadsSublevel.put(gameId, {
|
||||||
|
...download,
|
||||||
|
status: "complete",
|
||||||
|
shouldSeed: false,
|
||||||
|
queued: false,
|
||||||
|
});
|
||||||
|
|
||||||
await this.cancelDownload(gameId);
|
this.cancelDownload(gameId);
|
||||||
this.downloadingGameId = null;
|
}
|
||||||
|
|
||||||
|
const downloads = await downloadsSublevel
|
||||||
|
.values()
|
||||||
|
.all()
|
||||||
|
.then((games) => {
|
||||||
|
return sortBy(
|
||||||
|
games.filter((game) => game.status === "paused" && game.queued),
|
||||||
|
"timestamp",
|
||||||
|
"DESC"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const [nextItemOnQueue] = downloads;
|
||||||
|
|
||||||
|
if (nextItemOnQueue) {
|
||||||
|
this.resumeDownload(nextItemOnQueue);
|
||||||
|
} else {
|
||||||
|
this.downloadingGameId = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -296,6 +316,21 @@ export class DownloadManager {
|
||||||
save_path: download.downloadPath,
|
save_path: download.downloadPath,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case Downloader.AllDebrid: {
|
||||||
|
const downloadUrls = await AllDebridClient.getDownloadUrls(download.uri);
|
||||||
|
|
||||||
|
if (!downloadUrls.length) throw new Error(DownloadError.NotCachedInAllDebrid);
|
||||||
|
|
||||||
|
const totalSize = downloadUrls.reduce((total, url) => total + (url.size || 0), 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
action: "start",
|
||||||
|
game_id: downloadId,
|
||||||
|
url: downloadUrls.map(d => d.link),
|
||||||
|
save_path: download.downloadPath,
|
||||||
|
total_size: totalSize
|
||||||
|
};
|
||||||
|
}
|
||||||
case Downloader.Torrent:
|
case Downloader.Torrent:
|
||||||
return {
|
return {
|
||||||
action: "start",
|
action: "start",
|
||||||
|
@ -315,21 +350,6 @@ export class DownloadManager {
|
||||||
save_path: download.downloadPath,
|
save_path: download.downloadPath,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case Downloader.AllDebrid: {
|
|
||||||
const downloadUrls = await AllDebridClient.getDownloadUrls(download.uri);
|
|
||||||
|
|
||||||
if (!downloadUrls.length) throw new Error(DownloadError.NotCachedInAllDebrid);
|
|
||||||
|
|
||||||
const totalSize = downloadUrls.reduce((total, url) => total + (url.size || 0), 0);
|
|
||||||
|
|
||||||
return {
|
|
||||||
action: "start",
|
|
||||||
game_id: downloadId,
|
|
||||||
url: downloadUrls.map(d => d.link),
|
|
||||||
save_path: download.downloadPath,
|
|
||||||
total_size: totalSize
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case Downloader.TorBox: {
|
case Downloader.TorBox: {
|
||||||
const { name, url } = await TorBoxClient.getDownloadInfo(download.uri);
|
const { name, url } = await TorBoxClient.getDownloadInfo(download.uri);
|
||||||
|
|
||||||
|
@ -350,4 +370,4 @@ export class DownloadManager {
|
||||||
await PythonRPC.rpc.post("/action", payload);
|
await PythonRPC.rpc.post("/action", payload);
|
||||||
this.downloadingGameId = levelKeys.game(download.shop, download.objectId);
|
this.downloadingGameId = levelKeys.game(download.shop, download.objectId);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -19,7 +19,7 @@ export const calculateETA = (
|
||||||
export const getDirSize = async (dir: string): Promise<number> => {
|
export const getDirSize = async (dir: string): Promise<number> => {
|
||||||
try {
|
try {
|
||||||
const stat = await fs.promises.stat(dir);
|
const stat = await fs.promises.stat(dir);
|
||||||
|
|
||||||
// If it's a file, return its size directly
|
// If it's a file, return its size directly
|
||||||
if (!stat.isDirectory()) {
|
if (!stat.isDirectory()) {
|
||||||
return stat.size;
|
return stat.size;
|
||||||
|
|
|
@ -10,9 +10,13 @@ import { Readable } from "node:stream";
|
||||||
import { app, dialog } from "electron";
|
import { app, dialog } from "electron";
|
||||||
|
|
||||||
interface GamePayload {
|
interface GamePayload {
|
||||||
|
action: string;
|
||||||
game_id: string;
|
game_id: string;
|
||||||
url: string;
|
url: string | string[];
|
||||||
save_path: string;
|
save_path: string;
|
||||||
|
header?: string;
|
||||||
|
out?: string;
|
||||||
|
total_size?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const binaryNameByPlatform: Partial<Record<NodeJS.Platform, string>> = {
|
const binaryNameByPlatform: Partial<Record<NodeJS.Platform, string>> = {
|
||||||
|
@ -105,4 +109,4 @@ export class PythonRPC {
|
||||||
this.pythonProcess = null;
|
this.pythonProcess = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2
src/renderer/src/declaration.d.ts
vendored
2
src/renderer/src/declaration.d.ts
vendored
|
@ -29,6 +29,7 @@ import type {
|
||||||
LibraryGame,
|
LibraryGame,
|
||||||
GameRunning,
|
GameRunning,
|
||||||
TorBoxUser,
|
TorBoxUser,
|
||||||
|
AllDebridUser,
|
||||||
} from "@types";
|
} from "@types";
|
||||||
import type { AxiosProgressEvent } from "axios";
|
import type { AxiosProgressEvent } from "axios";
|
||||||
import type disk from "diskusage";
|
import type disk from "diskusage";
|
||||||
|
@ -150,6 +151,7 @@ declare global {
|
||||||
minimized: boolean;
|
minimized: boolean;
|
||||||
}) => Promise<void>;
|
}) => Promise<void>;
|
||||||
authenticateRealDebrid: (apiToken: string) => Promise<RealDebridUser>;
|
authenticateRealDebrid: (apiToken: string) => Promise<RealDebridUser>;
|
||||||
|
authenticateAllDebrid: (apiKey: string) => Promise<AllDebridUser | { error_code: string }>;
|
||||||
authenticateTorBox: (apiToken: string) => Promise<TorBoxUser>;
|
authenticateTorBox: (apiToken: string) => Promise<TorBoxUser>;
|
||||||
onAchievementUnlocked: (cb: () => void) => () => Electron.IpcRenderer;
|
onAchievementUnlocked: (cb: () => void) => () => Electron.IpcRenderer;
|
||||||
|
|
||||||
|
|
|
@ -196,7 +196,6 @@ export function HeroPanelActions() {
|
||||||
{game.favorite ? <HeartFillIcon /> : <HeartIcon />}
|
{game.favorite ? <HeartFillIcon /> : <HeartIcon />}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setShowGameOptionsModal(true)}
|
onClick={() => setShowGameOptionsModal(true)}
|
||||||
theme="outline"
|
theme="outline"
|
||||||
|
|
|
@ -49,4 +49,4 @@
|
||||||
&__change-path-button {
|
&__change-path-button {
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -176,7 +176,6 @@ export function DownloadSettingsModal({
|
||||||
}
|
}
|
||||||
onClick={() => setSelectedDownloader(downloader)}
|
onClick={() => setSelectedDownloader(downloader)}
|
||||||
>
|
>
|
||||||
|
|
||||||
{selectedDownloader === downloader && (
|
{selectedDownloader === downloader && (
|
||||||
<CheckCircleFillIcon className="download-settings-modal__downloader-icon" />
|
<CheckCircleFillIcon className="download-settings-modal__downloader-icon" />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -9,4 +9,4 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ export function SettingsAllDebrid() {
|
||||||
form.allDebridApiKey
|
form.allDebridApiKey
|
||||||
);
|
);
|
||||||
|
|
||||||
if ('error_code' in result) {
|
if ("error_code" in result) {
|
||||||
showErrorToast(t(result.error_code));
|
showErrorToast(t(result.error_code));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -126,4 +126,4 @@ export function SettingsAllDebrid() {
|
||||||
)}
|
)}
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,12 @@ export const getDownloadersForUri = (uri: string) => {
|
||||||
return [Downloader.RealDebrid];
|
return [Downloader.RealDebrid];
|
||||||
|
|
||||||
if (uri.startsWith("magnet:")) {
|
if (uri.startsWith("magnet:")) {
|
||||||
return [Downloader.Torrent, Downloader.TorBox, Downloader.RealDebrid, Downloader.AllDebrid];
|
return [
|
||||||
|
Downloader.Torrent,
|
||||||
|
Downloader.TorBox,
|
||||||
|
Downloader.RealDebrid,
|
||||||
|
Downloader.AllDebrid,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
|
|
|
@ -181,4 +181,4 @@ export interface AllDebridUser {
|
||||||
email: string;
|
email: string;
|
||||||
isPremium: boolean;
|
isPremium: boolean;
|
||||||
premiumUntil: string;
|
premiumUntil: string;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue