mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-03-09 15:40:26 +00:00
All-debrid API key + ui
Adds ui changes for alldebrid implementation, checks the api key and saves it with encryption like the real debrid implementation
This commit is contained in:
parent
110131f1d6
commit
2a4221e787
15 changed files with 286 additions and 6 deletions
|
@ -1,2 +0,0 @@
|
||||||
MAIN_VITE_API_URL=API_URL
|
|
||||||
MAIN_VITE_AUTH_URL=AUTH_URL
|
|
|
@ -306,7 +306,18 @@
|
||||||
"enable_torbox": "Enable Torbox",
|
"enable_torbox": "Enable Torbox",
|
||||||
"torbox_description": "TorBox is your premium seedbox service rivaling even the best servers on the market.",
|
"torbox_description": "TorBox is your premium seedbox service rivaling even the best servers on the market.",
|
||||||
"torbox_account_linked": "TorBox account linked",
|
"torbox_account_linked": "TorBox account linked",
|
||||||
"real_debrid_account_linked": "Real-Debrid account linked"
|
"real_debrid_account_linked": "Real-Debrid account linked",
|
||||||
|
"enable_all_debrid": "Enable All-Debrid",
|
||||||
|
"all_debrid_description": "All-Debrid is an unrestricted downloader that allows you to quickly download files from various sources.",
|
||||||
|
"all_debrid_free_account_error": "The account \"{{username}}\" is a free account. Please subscribe to All-Debrid",
|
||||||
|
"all_debrid_account_linked": "All-Debrid account linked successfully",
|
||||||
|
"alldebrid_missing_key": "Please provide an API key",
|
||||||
|
"alldebrid_invalid_key": "Invalid API key",
|
||||||
|
"alldebrid_blocked": "Your API key is geo-blocked or IP-blocked",
|
||||||
|
"alldebrid_banned": "This account has been banned",
|
||||||
|
"alldebrid_unknown_error": "An unknown error occurred",
|
||||||
|
"alldebrid_invalid_response": "Invalid response from All-Debrid",
|
||||||
|
"alldebrid_network_error": "Network error. Please check your connection"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"download_complete": "Download complete",
|
"download_complete": "Download complete",
|
||||||
|
|
|
@ -134,7 +134,11 @@
|
||||||
"real_debrid_free_account_error": "Contul \"{{username}}\" este un cont gratuit. Te rugăm să te abonezi la Real-Debrid",
|
"real_debrid_free_account_error": "Contul \"{{username}}\" este un cont gratuit. Te rugăm să te abonezi la Real-Debrid",
|
||||||
"debrid_linked_message": "Contul \"{{username}}\" a fost legat",
|
"debrid_linked_message": "Contul \"{{username}}\" a fost legat",
|
||||||
"save_changes": "Salvează modificările",
|
"save_changes": "Salvează modificările",
|
||||||
"changes_saved": "Modificările au fost salvate cu succes"
|
"changes_saved": "Modificările au fost salvate cu succes",
|
||||||
|
"enable_all_debrid": "Activează All-Debrid",
|
||||||
|
"all_debrid_description": "All-Debrid este un descărcător fără restricții care îți permite să descarci fișiere din diverse surse.",
|
||||||
|
"all_debrid_free_account_error": "Contul \"{{username}}\" este un cont gratuit. Te rugăm să te abonezi la All-Debrid",
|
||||||
|
"all_debrid_account_linked": "Contul All-Debrid a fost conectat cu succes"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"download_complete": "Descărcare completă",
|
"download_complete": "Descărcare completă",
|
||||||
|
|
|
@ -48,6 +48,7 @@ import "./user-preferences/auto-launch";
|
||||||
import "./autoupdater/check-for-updates";
|
import "./autoupdater/check-for-updates";
|
||||||
import "./autoupdater/restart-and-install-update";
|
import "./autoupdater/restart-and-install-update";
|
||||||
import "./user-preferences/authenticate-real-debrid";
|
import "./user-preferences/authenticate-real-debrid";
|
||||||
|
import "./user-preferences/authenticate-all-debrid";
|
||||||
import "./user-preferences/authenticate-torbox";
|
import "./user-preferences/authenticate-torbox";
|
||||||
import "./download-sources/put-download-source";
|
import "./download-sources/put-download-source";
|
||||||
import "./auth/sign-out";
|
import "./auth/sign-out";
|
||||||
|
|
18
src/main/events/user-preferences/authenticate-all-debrid.ts
Normal file
18
src/main/events/user-preferences/authenticate-all-debrid.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { AllDebridClient } from "@main/services/download/all-debrid";
|
||||||
|
import { registerEvent } from "../register-event";
|
||||||
|
|
||||||
|
const authenticateAllDebrid = async (
|
||||||
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
|
apiKey: string
|
||||||
|
) => {
|
||||||
|
AllDebridClient.authorize(apiKey);
|
||||||
|
const result = await AllDebridClient.getUser();
|
||||||
|
|
||||||
|
if ('error_code' in result) {
|
||||||
|
return { error_code: result.error_code };
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.user;
|
||||||
|
};
|
||||||
|
|
||||||
|
registerEvent("authenticateAllDebrid", authenticateAllDebrid);
|
|
@ -15,6 +15,12 @@ const getUserPreferences = async () =>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (userPreferences?.allDebridApiKey) {
|
||||||
|
userPreferences.allDebridApiKey = Crypto.decrypt(
|
||||||
|
userPreferences.allDebridApiKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (userPreferences?.torBoxApiToken) {
|
if (userPreferences?.torBoxApiToken) {
|
||||||
userPreferences.torBoxApiToken = Crypto.decrypt(
|
userPreferences.torBoxApiToken = Crypto.decrypt(
|
||||||
userPreferences.torBoxApiToken
|
userPreferences.torBoxApiToken
|
||||||
|
|
|
@ -30,6 +30,12 @@ const updateUserPreferences = async (
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (preferences.allDebridApiKey) {
|
||||||
|
preferences.allDebridApiKey = Crypto.encrypt(
|
||||||
|
preferences.allDebridApiKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (preferences.torBoxApiToken) {
|
if (preferences.torBoxApiToken) {
|
||||||
preferences.torBoxApiToken = Crypto.encrypt(preferences.torBoxApiToken);
|
preferences.torBoxApiToken = Crypto.encrypt(preferences.torBoxApiToken);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
startMainLoop,
|
startMainLoop,
|
||||||
} from "./services";
|
} from "./services";
|
||||||
import { RealDebridClient } from "./services/download/real-debrid";
|
import { RealDebridClient } from "./services/download/real-debrid";
|
||||||
|
import { AllDebridClient } from "./services/download/all-debrid";
|
||||||
import { HydraApi } from "./services/hydra-api";
|
import { HydraApi } from "./services/hydra-api";
|
||||||
import { uploadGamesBatch } from "./services/library-sync";
|
import { uploadGamesBatch } from "./services/library-sync";
|
||||||
import { Aria2 } from "./services/aria2";
|
import { Aria2 } from "./services/aria2";
|
||||||
|
@ -43,8 +44,16 @@ export const loadState = async () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (userPreferences?.allDebridApiKey) {
|
||||||
|
AllDebridClient.authorize(
|
||||||
|
Crypto.decrypt(userPreferences.allDebridApiKey)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (userPreferences?.torBoxApiToken) {
|
if (userPreferences?.torBoxApiToken) {
|
||||||
TorBoxClient.authorize(Crypto.decrypt(userPreferences.torBoxApiToken));
|
TorBoxClient.authorize(
|
||||||
|
Crypto.decrypt(userPreferences.torBoxApiToken)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ludusavi.addManifestToLudusaviConfig();
|
Ludusavi.addManifestToLudusaviConfig();
|
||||||
|
@ -117,7 +126,7 @@ const migrateFromSqlite = async () => {
|
||||||
.select("*")
|
.select("*")
|
||||||
.then(async (userPreferences) => {
|
.then(async (userPreferences) => {
|
||||||
if (userPreferences.length > 0) {
|
if (userPreferences.length > 0) {
|
||||||
const { realDebridApiToken, ...rest } = userPreferences[0];
|
const { realDebridApiToken, allDebridApiKey, ...rest } = userPreferences[0];
|
||||||
|
|
||||||
await db.put<string, UserPreferences>(
|
await db.put<string, UserPreferences>(
|
||||||
levelKeys.userPreferences,
|
levelKeys.userPreferences,
|
||||||
|
@ -126,6 +135,9 @@ const migrateFromSqlite = async () => {
|
||||||
realDebridApiToken: realDebridApiToken
|
realDebridApiToken: realDebridApiToken
|
||||||
? Crypto.encrypt(realDebridApiToken)
|
? Crypto.encrypt(realDebridApiToken)
|
||||||
: null,
|
: null,
|
||||||
|
allDebridApiKey: allDebridApiKey
|
||||||
|
? Crypto.encrypt(allDebridApiKey)
|
||||||
|
: null,
|
||||||
preferQuitInsteadOfHiding: rest.preferQuitInsteadOfHiding === 1,
|
preferQuitInsteadOfHiding: rest.preferQuitInsteadOfHiding === 1,
|
||||||
runAtStartup: rest.runAtStartup === 1,
|
runAtStartup: rest.runAtStartup === 1,
|
||||||
startMinimized: rest.startMinimized === 1,
|
startMinimized: rest.startMinimized === 1,
|
||||||
|
|
66
src/main/services/download/all-debrid.ts
Normal file
66
src/main/services/download/all-debrid.ts
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import axios, { AxiosInstance } from "axios";
|
||||||
|
import type { AllDebridUser } from "@types";
|
||||||
|
import { logger } from "@main/services";
|
||||||
|
|
||||||
|
export class AllDebridClient {
|
||||||
|
private static instance: AxiosInstance;
|
||||||
|
private static readonly baseURL = "https://api.alldebrid.com/v4";
|
||||||
|
|
||||||
|
static authorize(apiKey: string) {
|
||||||
|
logger.info("[AllDebrid] Authorizing with key:", apiKey ? "***" : "empty");
|
||||||
|
this.instance = axios.create({
|
||||||
|
baseURL: this.baseURL,
|
||||||
|
params: {
|
||||||
|
agent: "hydra",
|
||||||
|
apikey: apiKey
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getUser() {
|
||||||
|
try {
|
||||||
|
const response = await this.instance.get<{
|
||||||
|
status: string;
|
||||||
|
data?: { user: AllDebridUser };
|
||||||
|
error?: {
|
||||||
|
code: string;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
}>("/user");
|
||||||
|
|
||||||
|
logger.info("[AllDebrid] API Response:", response.data);
|
||||||
|
|
||||||
|
if (response.data.status === "error") {
|
||||||
|
const error = response.data.error;
|
||||||
|
logger.error("[AllDebrid] API Error:", error);
|
||||||
|
if (error?.code === "AUTH_MISSING_APIKEY") {
|
||||||
|
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" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -92,6 +92,8 @@ contextBridge.exposeInMainWorld("electron", {
|
||||||
ipcRenderer.invoke("autoLaunch", autoLaunchProps),
|
ipcRenderer.invoke("autoLaunch", autoLaunchProps),
|
||||||
authenticateRealDebrid: (apiToken: string) =>
|
authenticateRealDebrid: (apiToken: string) =>
|
||||||
ipcRenderer.invoke("authenticateRealDebrid", apiToken),
|
ipcRenderer.invoke("authenticateRealDebrid", apiToken),
|
||||||
|
authenticateAllDebrid: (apiKey: string) =>
|
||||||
|
ipcRenderer.invoke("authenticateAllDebrid", apiKey),
|
||||||
authenticateTorBox: (apiToken: string) =>
|
authenticateTorBox: (apiToken: string) =>
|
||||||
ipcRenderer.invoke("authenticateTorBox", apiToken),
|
ipcRenderer.invoke("authenticateTorBox", apiToken),
|
||||||
|
|
||||||
|
|
12
src/renderer/src/pages/settings/settings-all-debrid.scss
Normal file
12
src/renderer/src/pages/settings/settings-all-debrid.scss
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
.settings-all-debrid {
|
||||||
|
&__form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__description {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
}
|
129
src/renderer/src/pages/settings/settings-all-debrid.tsx
Normal file
129
src/renderer/src/pages/settings/settings-all-debrid.tsx
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
import { useContext, useEffect, useState } from "react";
|
||||||
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import { Button, CheckboxField, Link, TextField } from "@renderer/components";
|
||||||
|
import "./settings-all-debrid.scss";
|
||||||
|
|
||||||
|
import { useAppSelector, useToast } from "@renderer/hooks";
|
||||||
|
|
||||||
|
import { settingsContext } from "@renderer/context";
|
||||||
|
|
||||||
|
const ALL_DEBRID_API_TOKEN_URL = "https://alldebrid.com/apikeys";
|
||||||
|
|
||||||
|
export function SettingsAllDebrid() {
|
||||||
|
const userPreferences = useAppSelector(
|
||||||
|
(state) => state.userPreferences.value
|
||||||
|
);
|
||||||
|
|
||||||
|
const { updateUserPreferences } = useContext(settingsContext);
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [form, setForm] = useState({
|
||||||
|
useAllDebrid: false,
|
||||||
|
allDebridApiKey: null as string | null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { showSuccessToast, showErrorToast } = useToast();
|
||||||
|
|
||||||
|
const { t } = useTranslation("settings");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (userPreferences) {
|
||||||
|
setForm({
|
||||||
|
useAllDebrid: Boolean(userPreferences.allDebridApiKey),
|
||||||
|
allDebridApiKey: userPreferences.allDebridApiKey ?? null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [userPreferences]);
|
||||||
|
|
||||||
|
const handleFormSubmit: React.FormEventHandler<HTMLFormElement> = async (
|
||||||
|
event
|
||||||
|
) => {
|
||||||
|
setIsLoading(true);
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (form.useAllDebrid) {
|
||||||
|
if (!form.allDebridApiKey) {
|
||||||
|
showErrorToast(t("alldebrid_missing_key"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await window.electron.authenticateAllDebrid(
|
||||||
|
form.allDebridApiKey
|
||||||
|
);
|
||||||
|
|
||||||
|
if ('error_code' in result) {
|
||||||
|
showErrorToast(t(result.error_code));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.isPremium) {
|
||||||
|
showErrorToast(
|
||||||
|
t("all_debrid_free_account_error", { username: result.username })
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showSuccessToast(
|
||||||
|
t("all_debrid_account_linked"),
|
||||||
|
t("debrid_linked_message", { username: result.username })
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
showSuccessToast(t("changes_saved"));
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUserPreferences({
|
||||||
|
allDebridApiKey: form.useAllDebrid ? form.allDebridApiKey : null,
|
||||||
|
});
|
||||||
|
} catch (err: any) {
|
||||||
|
showErrorToast(t("alldebrid_unknown_error"));
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isButtonDisabled =
|
||||||
|
(form.useAllDebrid && !form.allDebridApiKey) || isLoading;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className="settings-all-debrid__form" onSubmit={handleFormSubmit}>
|
||||||
|
<p className="settings-all-debrid__description">
|
||||||
|
{t("all_debrid_description")}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<CheckboxField
|
||||||
|
label={t("enable_all_debrid")}
|
||||||
|
checked={form.useAllDebrid}
|
||||||
|
onChange={() =>
|
||||||
|
setForm((prev) => ({
|
||||||
|
...prev,
|
||||||
|
useAllDebrid: !form.useAllDebrid,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{form.useAllDebrid && (
|
||||||
|
<TextField
|
||||||
|
label={t("api_token")}
|
||||||
|
value={form.allDebridApiKey ?? ""}
|
||||||
|
type="password"
|
||||||
|
onChange={(event) =>
|
||||||
|
setForm({ ...form, allDebridApiKey: event.target.value })
|
||||||
|
}
|
||||||
|
rightContent={
|
||||||
|
<Button type="submit" disabled={isButtonDisabled}>
|
||||||
|
{t("save_changes")}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
placeholder="API Key"
|
||||||
|
hint={
|
||||||
|
<Trans i18nKey="debrid_api_token_hint" ns="settings">
|
||||||
|
<Link to={ALL_DEBRID_API_TOKEN_URL} />
|
||||||
|
</Trans>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import { Button } from "@renderer/components";
|
import { Button } from "@renderer/components";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { SettingsRealDebrid } from "./settings-real-debrid";
|
import { SettingsRealDebrid } from "./settings-real-debrid";
|
||||||
|
import { SettingsAllDebrid } from "./settings-all-debrid";
|
||||||
import { SettingsGeneral } from "./settings-general";
|
import { SettingsGeneral } from "./settings-general";
|
||||||
import { SettingsBehavior } from "./settings-behavior";
|
import { SettingsBehavior } from "./settings-behavior";
|
||||||
import torBoxLogo from "@renderer/assets/icons/torbox.webp";
|
import torBoxLogo from "@renderer/assets/icons/torbox.webp";
|
||||||
|
@ -35,6 +36,7 @@ export default function Settings() {
|
||||||
contentTitle: "TorBox",
|
contentTitle: "TorBox",
|
||||||
},
|
},
|
||||||
{ tabLabel: "Real-Debrid", contentTitle: "Real-Debrid" },
|
{ tabLabel: "Real-Debrid", contentTitle: "Real-Debrid" },
|
||||||
|
{ tabLabel: "All-Debrid", contentTitle: "All-Debrid" },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (userDetails)
|
if (userDetails)
|
||||||
|
@ -70,6 +72,10 @@ export default function Settings() {
|
||||||
return <SettingsRealDebrid />;
|
return <SettingsRealDebrid />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentCategoryIndex === 5) {
|
||||||
|
return <SettingsAllDebrid />;
|
||||||
|
}
|
||||||
|
|
||||||
return <SettingsAccount />;
|
return <SettingsAccount />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -174,3 +174,11 @@ export interface SeedingStatus {
|
||||||
status: DownloadStatus;
|
status: DownloadStatus;
|
||||||
uploadSpeed: number;
|
uploadSpeed: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* All-Debrid */
|
||||||
|
export interface AllDebridUser {
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
isPremium: boolean;
|
||||||
|
premiumUntil: string;
|
||||||
|
}
|
||||||
|
|
|
@ -70,6 +70,7 @@ export interface UserPreferences {
|
||||||
downloadsPath?: string | null;
|
downloadsPath?: string | null;
|
||||||
language?: string;
|
language?: string;
|
||||||
realDebridApiToken?: string | null;
|
realDebridApiToken?: string | null;
|
||||||
|
allDebridApiKey?: string | null;
|
||||||
torBoxApiToken?: string | null;
|
torBoxApiToken?: string | null;
|
||||||
preferQuitInsteadOfHiding?: boolean;
|
preferQuitInsteadOfHiding?: boolean;
|
||||||
runAtStartup?: boolean;
|
runAtStartup?: boolean;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue