Harden Mega web flow and smooth download runtime
Some checks are pending
Build and Release / build (push) Waiting to run
Some checks are pending
Build and Release / build (push) Waiting to run
This commit is contained in:
parent
40bfda2ad7
commit
583d74fcc9
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.1.21",
|
"version": "1.1.22",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.1.21",
|
"version": "1.1.22",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"adm-zip": "^0.5.16",
|
"adm-zip": "^0.5.16",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.1.21",
|
"version": "1.1.22",
|
||||||
"description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)",
|
"description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)",
|
||||||
"main": "build/main/main/main.js",
|
"main": "build/main/main/main.js",
|
||||||
"author": "Sucukdeluxe",
|
"author": "Sucukdeluxe",
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { DebridService } from "../src/main/debrid";
|
import { DebridService } from "../src/main/debrid";
|
||||||
import { defaultSettings } from "../src/main/constants";
|
import { defaultSettings } from "../src/main/constants";
|
||||||
|
import { MegaWebFallback } from "../src/main/mega-web-fallback";
|
||||||
|
|
||||||
const links = [
|
const links = [
|
||||||
"https://rapidgator.net/file/837ef967aede4935e3e0374c4e663b40/GTHDERTPIIP7P401.part1.rar.html",
|
"https://rapidgator.net/file/837ef967aede4935e3e0374c4e663b40/GTHDERTPIIP7P401.part1.rar.html",
|
||||||
@ -26,7 +27,13 @@ if (!settings.token && !(settings.megaLogin && settings.megaPassword) && !settin
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function main(): Promise<void> {
|
async function main(): Promise<void> {
|
||||||
const service = new DebridService(settings);
|
const megaWeb = new MegaWebFallback(() => ({
|
||||||
|
login: settings.megaLogin,
|
||||||
|
password: settings.megaPassword
|
||||||
|
}));
|
||||||
|
const service = new DebridService(settings, {
|
||||||
|
megaWebUnrestrict: (link) => megaWeb.unrestrict(link)
|
||||||
|
});
|
||||||
for (const link of links) {
|
for (const link of links) {
|
||||||
try {
|
try {
|
||||||
const result = await service.unrestrictLink(link);
|
const result = await service.unrestrictLink(link);
|
||||||
@ -35,6 +42,7 @@ async function main(): Promise<void> {
|
|||||||
console.log(`[FAIL] ${String(error)}`);
|
console.log(`[FAIL] ${String(error)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
megaWeb.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void main();
|
void main();
|
||||||
|
|||||||
@ -5,12 +5,14 @@ const RAPIDGATOR_LINKS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const rdToken = process.env.RD_TOKEN || "";
|
const rdToken = process.env.RD_TOKEN || "";
|
||||||
const megaToken = process.env.MEGA_TOKEN || "";
|
const megaLogin = process.env.MEGA_LOGIN || "";
|
||||||
|
const megaPassword = process.env.MEGA_PASSWORD || "";
|
||||||
const bestToken = process.env.BEST_TOKEN || "";
|
const bestToken = process.env.BEST_TOKEN || "";
|
||||||
const allDebridToken = process.env.ALLDEBRID_TOKEN || "";
|
const allDebridToken = process.env.ALLDEBRID_TOKEN || "";
|
||||||
|
let megaCookie = "";
|
||||||
|
|
||||||
if (!rdToken && !megaToken && !bestToken && !allDebridToken) {
|
if (!rdToken && !(megaLogin && megaPassword) && !bestToken && !allDebridToken) {
|
||||||
console.error("No provider token configured. Set RD_TOKEN and/or MEGA_TOKEN and/or BEST_TOKEN and/or ALLDEBRID_TOKEN.");
|
console.error("No provider credentials configured. Set RD_TOKEN and/or MEGA_LOGIN+MEGA_PASSWORD and/or BEST_TOKEN and/or ALLDEBRID_TOKEN.");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,32 +67,78 @@ async function callRealDebrid(link) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function callMegaDebrid(link) {
|
async function callMegaDebrid(link) {
|
||||||
const response = await fetch(`https://www.mega-debrid.eu/api.php?action=getLink&token=${encodeURIComponent(megaToken)}`, {
|
if (!megaCookie) {
|
||||||
|
const loginRes = await fetch("https://www.mega-debrid.eu/index.php?form=login", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
"User-Agent": "Mozilla/5.0"
|
||||||
|
},
|
||||||
|
body: new URLSearchParams({ login: megaLogin, password: megaPassword, remember: "on" }),
|
||||||
|
redirect: "manual"
|
||||||
|
});
|
||||||
|
megaCookie = (loginRes.headers.get("set-cookie") || "")
|
||||||
|
.split(",")
|
||||||
|
.map((chunk) => chunk.split(";")[0].trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.join("; ");
|
||||||
|
if (!megaCookie) {
|
||||||
|
return { ok: false, error: "Mega-Web login failed" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const debridRes = await fetch("https://www.mega-debrid.eu/index.php?form=debrid", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
"User-Agent": "RD-Node-Downloader/1.1.12"
|
"User-Agent": "Mozilla/5.0",
|
||||||
|
Cookie: megaCookie,
|
||||||
|
Referer: "https://www.mega-debrid.eu/index.php?page=debrideur&lang=de"
|
||||||
},
|
},
|
||||||
body: new URLSearchParams({ link })
|
body: new URLSearchParams({ links: link, password: "", showLinks: "1" })
|
||||||
});
|
});
|
||||||
const text = await response.text();
|
const html = await debridRes.text();
|
||||||
const payload = asRecord(safeJson(text));
|
const code = html.match(/processDebrid\(\d+,'([^']+)',0\)/i)?.[1] || "";
|
||||||
if (!response.ok) {
|
if (!code) {
|
||||||
return { ok: false, error: parseResponseError(response.status, text, payload) };
|
return { ok: false, error: "Mega-Web returned no processDebrid code" };
|
||||||
}
|
}
|
||||||
const code = pickString(payload, ["response_code"]);
|
|
||||||
if (code && code.toLowerCase() !== "ok") {
|
for (let attempt = 1; attempt <= 40; attempt += 1) {
|
||||||
return { ok: false, error: pickString(payload, ["response_text"]) || code };
|
const ajaxRes = await fetch("https://www.mega-debrid.eu/index.php?ajax=debrid&json", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
"User-Agent": "Mozilla/5.0",
|
||||||
|
Cookie: megaCookie,
|
||||||
|
Referer: "https://www.mega-debrid.eu/index.php?page=debrideur&lang=de"
|
||||||
|
},
|
||||||
|
body: new URLSearchParams({ code, autodl: "0" })
|
||||||
|
});
|
||||||
|
const txt = (await ajaxRes.text()).trim();
|
||||||
|
if (txt === "reload") {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 650));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (txt === "false") {
|
||||||
|
return { ok: false, error: "Mega-Web returned false" };
|
||||||
|
}
|
||||||
|
const payload = safeJson(txt);
|
||||||
|
const direct = String(payload?.link || "");
|
||||||
|
if (!direct) {
|
||||||
|
const msg = String(payload?.text || txt || "Mega-Web no link");
|
||||||
|
if (/hoster does not respond correctly|could not be done for this moment/i.test(msg)) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1200));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return { ok: false, error: msg };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
direct,
|
||||||
|
fileName: pickString(asRecord(payload), ["filename"]) || ""
|
||||||
|
};
|
||||||
}
|
}
|
||||||
const direct = pickString(payload, ["debridLink", "download", "link"]);
|
return { ok: false, error: "Mega-Web timeout while generating link" };
|
||||||
if (!direct) {
|
|
||||||
return { ok: false, error: "Mega-Debrid returned no debridLink" };
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
direct,
|
|
||||||
fileName: pickString(payload, ["filename", "fileName"])
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function callBestDebrid(link) {
|
async function callBestDebrid(link) {
|
||||||
@ -196,7 +244,7 @@ async function main() {
|
|||||||
if (rdToken) {
|
if (rdToken) {
|
||||||
providers.push({ name: "Real-Debrid", run: callRealDebrid });
|
providers.push({ name: "Real-Debrid", run: callRealDebrid });
|
||||||
}
|
}
|
||||||
if (megaToken) {
|
if (megaLogin && megaPassword) {
|
||||||
providers.push({ name: "Mega-Debrid", run: callMegaDebrid });
|
providers.push({ name: "Mega-Debrid", run: callMegaDebrid });
|
||||||
}
|
}
|
||||||
if (bestToken) {
|
if (bestToken) {
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import os from "node:os";
|
|||||||
import { AppSettings } from "../shared/types";
|
import { AppSettings } from "../shared/types";
|
||||||
|
|
||||||
export const APP_NAME = "Debrid Download Manager";
|
export const APP_NAME = "Debrid Download Manager";
|
||||||
export const APP_VERSION = "1.1.21";
|
export const APP_VERSION = "1.1.22";
|
||||||
export const API_BASE_URL = "https://api.real-debrid.com/rest/1.0";
|
export const API_BASE_URL = "https://api.real-debrid.com/rest/1.0";
|
||||||
|
|
||||||
export const DCRYPT_UPLOAD_URL = "https://dcrypt.it/decrypt/upload";
|
export const DCRYPT_UPLOAD_URL = "https://dcrypt.it/decrypt/upload";
|
||||||
|
|||||||
@ -755,6 +755,19 @@ export class DownloadManager extends EventEmitter {
|
|||||||
let windowBytes = 0;
|
let windowBytes = 0;
|
||||||
let windowStarted = nowMs();
|
let windowStarted = nowMs();
|
||||||
|
|
||||||
|
const waitDrain = (): Promise<void> => new Promise((resolve, reject) => {
|
||||||
|
const onDrain = (): void => {
|
||||||
|
stream.off("error", onError);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
const onError = (error: Error): void => {
|
||||||
|
stream.off("drain", onDrain);
|
||||||
|
reject(error);
|
||||||
|
};
|
||||||
|
stream.once("drain", onDrain);
|
||||||
|
stream.once("error", onError);
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const body = response.body;
|
const body = response.body;
|
||||||
if (!body) {
|
if (!body) {
|
||||||
@ -784,7 +797,9 @@ export class DownloadManager extends EventEmitter {
|
|||||||
|
|
||||||
const buffer = Buffer.from(chunk);
|
const buffer = Buffer.from(chunk);
|
||||||
await this.applySpeedLimit(buffer.length, windowBytes, windowStarted);
|
await this.applySpeedLimit(buffer.length, windowBytes, windowStarted);
|
||||||
stream.write(buffer);
|
if (!stream.write(buffer)) {
|
||||||
|
await waitDrain();
|
||||||
|
}
|
||||||
written += buffer.length;
|
written += buffer.length;
|
||||||
windowBytes += buffer.length;
|
windowBytes += buffer.length;
|
||||||
this.session.totalDownloadedBytes += buffer.length;
|
this.session.totalDownloadedBytes += buffer.length;
|
||||||
@ -806,8 +821,18 @@ export class DownloadManager extends EventEmitter {
|
|||||||
this.emitState();
|
this.emitState();
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
await new Promise<void>((resolve) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
stream.end(() => resolve());
|
const onFinish = (): void => {
|
||||||
|
stream.off("error", onError);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
const onError = (error: Error): void => {
|
||||||
|
stream.off("finish", onFinish);
|
||||||
|
reject(error);
|
||||||
|
};
|
||||||
|
stream.once("finish", onFinish);
|
||||||
|
stream.once("error", onError);
|
||||||
|
stream.end();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -83,8 +83,9 @@ async function hashFile(filePath: string, algorithm: "crc32" | "md5" | "sha1"):
|
|||||||
const stream = fs.createReadStream(filePath, { highWaterMark: 1024 * 1024 });
|
const stream = fs.createReadStream(filePath, { highWaterMark: 1024 * 1024 });
|
||||||
return await new Promise<string>((resolve, reject) => {
|
return await new Promise<string>((resolve, reject) => {
|
||||||
let crc = 0;
|
let crc = 0;
|
||||||
stream.on("data", (chunk: Buffer) => {
|
stream.on("data", (chunk: string | Buffer) => {
|
||||||
crc = crc32Buffer(chunk, crc);
|
const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
||||||
|
crc = crc32Buffer(buffer, crc);
|
||||||
});
|
});
|
||||||
stream.on("error", reject);
|
stream.on("error", reject);
|
||||||
stream.on("end", () => resolve(((crc >>> 0).toString(16)).padStart(8, "0").toLowerCase()));
|
stream.on("end", () => resolve(((crc >>> 0).toString(16)).padStart(8, "0").toLowerCase()));
|
||||||
|
|||||||
@ -20,9 +20,23 @@ function normalizeLink(link: string): string {
|
|||||||
return link.trim().toLowerCase();
|
return link.trim().toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseSetCookie(raw: string): string {
|
function parseSetCookieFromHeaders(headers: Headers): string {
|
||||||
|
const getSetCookie = (headers as unknown as { getSetCookie?: () => string[] }).getSetCookie;
|
||||||
|
if (typeof getSetCookie === "function") {
|
||||||
|
const values = getSetCookie.call(headers)
|
||||||
|
.map((entry) => entry.split(";")[0].trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
if (values.length > 0) {
|
||||||
|
return values.join("; ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const raw = headers.get("set-cookie") || "";
|
||||||
|
if (!raw) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
return raw
|
return raw
|
||||||
.split(",")
|
.split(/,(?=[^;=]+?=)/g)
|
||||||
.map((chunk) => chunk.split(";")[0].trim())
|
.map((chunk) => chunk.split(";")[0].trim())
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join("; ");
|
.join("; ");
|
||||||
@ -144,10 +158,25 @@ export class MegaWebFallback {
|
|||||||
redirect: "manual"
|
redirect: "manual"
|
||||||
});
|
});
|
||||||
|
|
||||||
const cookie = parseSetCookie(response.headers.get("set-cookie") || "");
|
const cookie = parseSetCookieFromHeaders(response.headers);
|
||||||
if (!cookie) {
|
if (!cookie) {
|
||||||
throw new Error("Mega-Web Login liefert kein Session-Cookie");
|
throw new Error("Mega-Web Login liefert kein Session-Cookie");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const verify = await fetch(DEBRID_REFERER, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"User-Agent": "Mozilla/5.0",
|
||||||
|
Cookie: cookie,
|
||||||
|
Referer: DEBRID_REFERER
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const verifyHtml = await verify.text();
|
||||||
|
const hasDebridForm = /id=["']debridForm["']/i.test(verifyHtml) || /name=["']links["']/i.test(verifyHtml);
|
||||||
|
if (!hasDebridForm) {
|
||||||
|
throw new Error("Mega-Web Login ungültig oder Session blockiert");
|
||||||
|
}
|
||||||
|
|
||||||
this.cookie = cookie;
|
this.cookie = cookie;
|
||||||
this.cookieSetAt = Date.now();
|
this.cookieSetAt = Date.now();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -170,26 +170,36 @@ export function App(): ReactElement {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onAddLinks = async (): Promise<void> => {
|
const onAddLinks = async (): Promise<void> => {
|
||||||
await window.rd.updateSettings(settingsDraft);
|
try {
|
||||||
const result = await window.rd.addLinks({ rawText: linksRaw, packageName: settingsDraft.packageName });
|
await window.rd.updateSettings(settingsDraft);
|
||||||
if (result.addedLinks > 0) {
|
const result = await window.rd.addLinks({ rawText: linksRaw, packageName: settingsDraft.packageName });
|
||||||
setStatusToast(`${result.addedPackages} Paket(e), ${result.addedLinks} Link(s) hinzugefügt`);
|
if (result.addedLinks > 0) {
|
||||||
setLinksRaw("");
|
setStatusToast(`${result.addedPackages} Paket(e), ${result.addedLinks} Link(s) hinzugefügt`);
|
||||||
} else {
|
setLinksRaw("");
|
||||||
setStatusToast("Keine gültigen Links gefunden");
|
} else {
|
||||||
|
setStatusToast("Keine gültigen Links gefunden");
|
||||||
|
}
|
||||||
|
setTimeout(() => setStatusToast(""), 2200);
|
||||||
|
} catch (error) {
|
||||||
|
setStatusToast(`Fehler beim Hinzufügen: ${String(error)}`);
|
||||||
|
setTimeout(() => setStatusToast(""), 2600);
|
||||||
}
|
}
|
||||||
setTimeout(() => setStatusToast(""), 2200);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onImportDlc = async (): Promise<void> => {
|
const onImportDlc = async (): Promise<void> => {
|
||||||
const files = await window.rd.pickContainers();
|
try {
|
||||||
if (files.length === 0) {
|
const files = await window.rd.pickContainers();
|
||||||
return;
|
if (files.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await window.rd.updateSettings(settingsDraft);
|
||||||
|
const result = await window.rd.addContainers(files);
|
||||||
|
setStatusToast(`DLC importiert: ${result.addedPackages} Paket(e), ${result.addedLinks} Link(s)`);
|
||||||
|
setTimeout(() => setStatusToast(""), 2200);
|
||||||
|
} catch (error) {
|
||||||
|
setStatusToast(`Fehler beim DLC-Import: ${String(error)}`);
|
||||||
|
setTimeout(() => setStatusToast(""), 2600);
|
||||||
}
|
}
|
||||||
await window.rd.updateSettings(settingsDraft);
|
|
||||||
const result = await window.rd.addContainers(files);
|
|
||||||
setStatusToast(`DLC importiert: ${result.addedPackages} Paket(e), ${result.addedLinks} Link(s)`);
|
|
||||||
setTimeout(() => setStatusToast(""), 2200);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDrop = async (event: DragEvent<HTMLTextAreaElement>): Promise<void> => {
|
const onDrop = async (event: DragEvent<HTMLTextAreaElement>): Promise<void> => {
|
||||||
@ -202,10 +212,15 @@ export function App(): ReactElement {
|
|||||||
if (dlc.length === 0) {
|
if (dlc.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await window.rd.updateSettings(settingsDraft);
|
try {
|
||||||
const result = await window.rd.addContainers(dlc);
|
await window.rd.updateSettings(settingsDraft);
|
||||||
setStatusToast(`Drag-and-Drop: ${result.addedPackages} Paket(e), ${result.addedLinks} Link(s)`);
|
const result = await window.rd.addContainers(dlc);
|
||||||
setTimeout(() => setStatusToast(""), 2200);
|
setStatusToast(`Drag-and-Drop: ${result.addedPackages} Paket(e), ${result.addedLinks} Link(s)`);
|
||||||
|
setTimeout(() => setStatusToast(""), 2200);
|
||||||
|
} catch (error) {
|
||||||
|
setStatusToast(`Fehler bei Drag-and-Drop: ${String(error)}`);
|
||||||
|
setTimeout(() => setStatusToast(""), 2600);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const setBool = (key: keyof AppSettings, value: boolean): void => {
|
const setBool = (key: keyof AppSettings, value: boolean): void => {
|
||||||
@ -220,6 +235,15 @@ export function App(): ReactElement {
|
|||||||
setSettingsDraft((prev: AppSettings) => ({ ...prev, [key]: value }));
|
setSettingsDraft((prev: AppSettings) => ({ ...prev, [key]: value }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const performQuickAction = async (action: () => Promise<unknown>): Promise<void> => {
|
||||||
|
try {
|
||||||
|
await action();
|
||||||
|
} catch (error) {
|
||||||
|
setStatusToast(`Fehler: ${String(error)}`);
|
||||||
|
setTimeout(() => setStatusToast(""), 2600);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app-shell">
|
<div className="app-shell">
|
||||||
<header className="top-header">
|
<header className="top-header">
|
||||||
@ -239,15 +263,17 @@ export function App(): ReactElement {
|
|||||||
className="btn accent"
|
className="btn accent"
|
||||||
disabled={!snapshot.canStart}
|
disabled={!snapshot.canStart}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await window.rd.updateSettings(settingsDraft);
|
await performQuickAction(async () => {
|
||||||
await window.rd.start();
|
await window.rd.updateSettings(settingsDraft);
|
||||||
|
await window.rd.start();
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>Start</button>
|
>Start</button>
|
||||||
<button className="btn" disabled={!snapshot.canPause} onClick={() => window.rd.togglePause()}>
|
<button className="btn" disabled={!snapshot.canPause} onClick={() => { void performQuickAction(() => window.rd.togglePause()); }}>
|
||||||
{snapshot.session.paused ? "Resume" : "Pause"}
|
{snapshot.session.paused ? "Resume" : "Pause"}
|
||||||
</button>
|
</button>
|
||||||
<button className="btn" disabled={!snapshot.canStop} onClick={() => window.rd.stop()}>Stop</button>
|
<button className="btn" disabled={!snapshot.canStop} onClick={() => { void performQuickAction(() => window.rd.stop()); }}>Stop</button>
|
||||||
<button className="btn" onClick={() => window.rd.clearAll()}>Alles leeren</button>
|
<button className="btn" onClick={() => { void performQuickAction(() => window.rd.clearAll()); }}>Alles leeren</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="speed-config">
|
<div className="speed-config">
|
||||||
<label>
|
<label>
|
||||||
@ -310,7 +336,7 @@ export function App(): ReactElement {
|
|||||||
key={pkg.id}
|
key={pkg.id}
|
||||||
pkg={pkg}
|
pkg={pkg}
|
||||||
items={pkg.itemIds.map((id) => snapshot.session.items[id]).filter(Boolean)}
|
items={pkg.itemIds.map((id) => snapshot.session.items[id]).filter(Boolean)}
|
||||||
onCancel={() => window.rd.cancelPackage(pkg.id)}
|
onCancel={() => { void performQuickAction(() => window.rd.cancelPackage(pkg.id)); }}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user