From 9f0140dd3c8bc4520e07e111b066c0cf0c78c937 Mon Sep 17 00:00:00 2001 From: Sucukdeluxe Date: Sun, 9 Aug 2026 21:32:09 +0200 Subject: [PATCH] feat: refine account management and targeted editing Add local provider icons, larger compact account rows, the requested column order, and full username display while keeping secrets masked. Replace provider-wide editing with a row-specific editor that preserves sibling accounts, exact limits, live usage counters, and status metadata. Add targeted credential validation, release notices, and regression coverage. --- README.md | 15 +- THIRD_PARTY_NOTICES.md | 14 + assets/provider-icons/alldebrid.png | Bin 0 -> 631 bytes assets/provider-icons/bestdebrid.ico | Bin 0 -> 1150 bytes assets/provider-icons/ddownload.ico | Bin 0 -> 15406 bytes assets/provider-icons/debrid-link.ico | Bin 0 -> 18613 bytes assets/provider-icons/linksnappy.png | Bin 0 -> 387 bytes assets/provider-icons/mega-debrid.png | Bin 0 -> 1977 bytes assets/provider-icons/onefichier.png | Bin 0 -> 6984 bytes assets/provider-icons/real-debrid.png | Bin 0 -> 1475 bytes package-lock.json | 4 +- package.json | 7 +- scripts/verify_public_release.mjs | 8 +- src/main/account-status-persistence.ts | 8 + src/main/app-controller.ts | 27 +- src/main/main.ts | 4 +- src/main/settings-live-overlay.ts | 30 ++ src/preload/preload.ts | 2 +- src/renderer/App.tsx | 332 +++++++++++----- src/renderer/account-edit.ts | 458 +++++++++++++++++++++++ src/renderer/account-service-icons.ts | 13 + src/renderer/account-ui.ts | 4 + src/renderer/styles.css | 132 ++++++- src/shared/preload-api.ts | 2 +- tests/account-edit.test.ts | 298 +++++++++++++++ tests/account-service-icons.test.ts | 34 ++ tests/account-status-persistence.test.ts | 28 ++ tests/account-ui.test.ts | 9 + tests/public-release-metadata.test.ts | 10 + tests/settings-live-overlay.test.ts | 47 +++ 30 files changed, 1356 insertions(+), 130 deletions(-) create mode 100644 THIRD_PARTY_NOTICES.md create mode 100644 assets/provider-icons/alldebrid.png create mode 100644 assets/provider-icons/bestdebrid.ico create mode 100644 assets/provider-icons/ddownload.ico create mode 100644 assets/provider-icons/debrid-link.ico create mode 100644 assets/provider-icons/linksnappy.png create mode 100644 assets/provider-icons/mega-debrid.png create mode 100644 assets/provider-icons/onefichier.png create mode 100644 assets/provider-icons/real-debrid.png create mode 100644 src/main/account-status-persistence.ts create mode 100644 src/main/settings-live-overlay.ts create mode 100644 src/renderer/account-edit.ts create mode 100644 src/renderer/account-service-icons.ts create mode 100644 tests/account-edit.test.ts create mode 100644 tests/account-service-icons.test.ts create mode 100644 tests/account-status-persistence.test.ts create mode 100644 tests/settings-live-overlay.test.ts diff --git a/README.md b/README.md index 80a6601..cabe654 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,18 @@ Desktop downloader for Windows with package-based queue management, multi-provid ## Changelog +### 2.0.10 + +- Add locally bundled provider logos throughout account management. +- Reorder the account table to Hoster, Status, Download Traffic Remaining, Username, Expiry Date, and Password / Access. +- Increase account-row and status text sizes while keeping the table compact. +- Show full account usernames while continuing to protect passwords and API keys. +- Replace the provider-wide edit form with a compact editor for the selected account or API key. +- Preserve sibling accounts, usage counters, limits, and status metadata during account-specific edits. + ### 2.0.9 -- Rebuild the Accounts view as a viewport-filling, JDownloader-style table with flat 24-pixel rows and bottom-aligned actions. +- Rebuild the Accounts view as a viewport-filling compact table with flat rows and bottom-aligned actions. - Separate account usage rules from the overview so rotation and routing controls no longer reduce the visible account list. - Redesign the account dialog with one-line provider rows, an immediate default selection, compact credential fields, and split cancel/save actions. - Add a global account enable/disable control while preserving unrelated provider state. @@ -35,7 +44,7 @@ Desktop downloader for Windows with package-based queue management, multi-provid ## Why this tool? -- JDownloader-style workflow with packages, progress, extraction, history, and clean post-processing. +- Package-based workflow with progress, extraction, history, and clean post-processing. - Multiple debrid accounts in one app, including provider order, automatic fallback, and per-hoster routing. - Built for large queues with session persistence, retries, reconnect handling, resume support, and integrity checks. - Includes an in-app updater for releases published on GitHub. @@ -344,3 +353,5 @@ Detailed release history is published on [GitHub Releases](https://github.com/Su The project is licensed under the MIT License. See `LICENSE`. Bundled JVM extractor licenses and redistribution notices are available in `resources/extractor-jvm`. + +Bundled provider icon notices are available in `THIRD_PARTY_NOTICES.md`. diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md new file mode 100644 index 0000000..b02ff6c --- /dev/null +++ b/THIRD_PARTY_NOTICES.md @@ -0,0 +1,14 @@ +# Third-Party Notices + +The provider names, logos, and favicons bundled with this application are trademarks or brand assets of their respective owners. They are included unchanged solely to identify the supported services and are not covered by this project's MIT license. Inclusion does not imply endorsement or affiliation. + +| Provider | Source | Retrieved | SHA-256 | +| --- | --- | --- | --- | +| Real-Debrid | https://fcdn.real-debrid.com/0831/favicons/favicon-32x32.png | 2026-08-09 | `542D80BDDB948F240B100C1872E83B0228BC1BFE5C46E8FFD95AFA419CB11F52` | +| Mega-Debrid | https://cdn.mega-debrid.eu/images/favicon.png | 2026-08-09 | `12D3DE939965D13F852853A2A5BB873B0D7A89D411BEAFA02518B48356803AE4` | +| BestDebrid | https://bestdebrid.com/en/files/assets/images/favicon.ico | 2026-08-09 | `11E54B1B0DCBBB8F5BCAA21D0EDC0CF182276C513ED733ACEEEBC35120CD9F33` | +| AllDebrid | https://cdn.alldebrid.com/lib/images/default/favicon.png | 2026-08-09 | `AE296FBE43A6CCEABC85033E5C063BFCA01932EB3677FB679AF63C23535CD9EF` | +| DDownload | https://ddownload.com/images/favicon.ico | 2026-08-09 | `E0413F73F8A2A5AD85BE8082B0897E82AE3B354FE2490884D7A992BB18F34DF3` | +| 1Fichier | https://img.1fichier.com/favicon.png | 2026-08-09 | `C3567E0299879FE67924C6119BF082BE128A5E8962FC0D61A0D8AAA0B1304CB5` | +| Debrid-Link | https://cdn.debrid-link.com/img/fav/favicon.ico?i=2 | 2026-08-09 | `1AB89ADA0EB9CB665F5E05209F27B50869FCA1972620C90D57BF682E87F98FA4` | +| LinkSnappy | https://linksnappy.com/templates/v3/img/favicon.png | 2026-08-09 | `D9DB0F5E4BDB2BF659ADD6D666F8464455196EC47201F27B126083754126497E` | diff --git a/assets/provider-icons/alldebrid.png b/assets/provider-icons/alldebrid.png new file mode 100644 index 0000000000000000000000000000000000000000..844166becb4a7608e862a257c8bca86ac853e045 GIT binary patch literal 631 zcmV--0*L*IP)cf~adVM}>n8i`GU+l$C^pAMbn4q0^(M_Jt1*2j2HQzyJGx|1W{x&7i6uSFh}8 zKlIx3)A_{Y;(Z4>d#aMUfY2=#X&HZ63uU8Zbg-j-cuRtIOhr_Vh zAN&24=vTH3UN4bI;_*1gas0qR0Dfe%SrFE0wML^605~U=N(~1v z#+^>bZnsONQmfVKbUM{)wNk0fWHQ46*d<94B*8`^kq86=U`(Y_<@5O=08P`aRttmy zuvjeZcH86efHyd#SS;=WsGi=Dk4B?l2IOP07%bgxHxHCbB_2TA2?8(M(Dx;VVVcdR z*=z<6Hk+-{Xmq<>m&?Tig+c)U^yLL!J~@K6Vk6(kLnr3v9=?o3B0`~1GAIZD9HK!b zlS#+8gxv^oT0eo;gUsga&%zm#=_o`Adb}$rSQ-olI5ST@z~u?_=^n0LCh*cIS`Xd6 zF(Vd>ArPO>2k(%}mezN6EYoRW8|_SCt&NDkz`{n* zHl(n$7~gk$V~*%0$nkFW_U65rotZVO=&99A??r1(o6VcqlAtZ^qW3u)mnLwGa7JTKHX1ml151$(cfXO zq=7GS;i7?7Nj>1F{38dfG{_kZxSS>GG13>f)L;Aq~%^s%APAZ`^+2RWa?l;n9g&6<< literal 0 HcmV?d00001 diff --git a/assets/provider-icons/ddownload.ico b/assets/provider-icons/ddownload.ico new file mode 100644 index 0000000000000000000000000000000000000000..d4e93162f7dec96c22302dc5dd747ff65fe495c2 GIT binary patch literal 15406 zcmeGjXLD3l^f~$i@B^ZV8bJ}pLbG8@2muMb7`l{5kuvl;iXsX#A_59C(nhN3J){s4 zN_q&AkRAd_NP(11Z@by-9`CuEw{G^m`}SqCyRO6DnVtJ`-f6eIbI(2JgyWv#p62@X z<4Etsef*d$x zq?@GopF9MrG7|MPs845G3;nz0(Ln%5&arp+vWkPmPUaJEssj|IAXlz zz@k^V3HTx)d|}VCCgs370iL?Uyfe{IucRMV3HWxYlmeeCdi6vB9s>N_U+vQzq#fKO z;M-AOE8rR8F;aks0CUe|eh!lMtrqY_pUK$g^l^I;K&uttUR^&`pexT?P9ME`f`BIi zE-(2=z*pH+FThRs&QpLVM=p?{Nd5UGZ3emwt3-4?Lc4Z+etv z+%%mkxW0X!jyLfMbI$*2j6(m;Ch*?K62lFcHWWJQs)hW?dN&m3K`ZfHS*?hi31U2Z z3&VZH`|2!NV(^N@Xp6{4eWO;6ho;hdVq&19p%xVRcLeVgd4F3>KI-5@_G2QkNe{1u zKx1(}y~EuPzX-u|-iCsJQv&&|Rpl0yFSTx2XAcVGQ(GzXoR#7&=iTk!cMIerz<gY_y(?@YpWh?iE(6Hj*qPpu z@ve{l3IEQ~JMK~Twg*^ofhPryaS{@u<(843BTnM@KJD%Mbm-&Qla4(=_5ks#Lg$T! zxJ|R*#?FPb51l^-yvIGS_pdO|61RCaxu1Uwcz3p!c?+2K1{4OKftJb=P&O#2kBU9! zT}tDEA=1;C>KeCY4#jD!u7JwatB`VFJ@}iE7qQs5$=x1xYkDYt@8lhREy=;;dL5(s zs*K_w>h@MBy&4GDcPxMqj}blA%VK&m?s!ov-Jr1~pI1||$J`{w4Drq=+DN`7UQHC6OkM*VbNY@xn;32;6C7O4!;-t zrwoC+XAY3H=nx~JFkC0h|MN7~>M^z`)ym@|MB^$xZUX_|v6@D$hC9diSY7|j)B9N} zP&md^S?5#w(OnX>TF5>}Y88lGw$|JuU(BVk8rsQ>KlU=^C+u0qBBgMQ6IvrjXE+}v zv4Kn-x;m94HnW4Nf6N+pCM~|eIt;(hUt!W4>S6A4$rWFw4j6mKJ;LIR=aW<`Pl$p5 zE5{`BNuHL~Fn5S{#>-iJ598iI99FB&NP?gl!%1O z+Qsq}-5l$SvPr!B9VTsW^km^){&K9O-_Y z^I3-VUA%0*M;nxOaI>fmzHWn{t@aU*D(Ep++}FIMoH3>npFpeO>)ixu&^rL;bi7^v zy}zJc5zDt3QscJ~L29q$=+GCO;Ms41BKNkTXu)$!WhrEmdbpdr7eV6po}?!2oJk)D znV`?q7v1F0VjLdtPkfD=K>2O}*1=)T2Kr`F>t#rvynn6L^~W*#xDF)u#?yc{-;#~5 zVU#nEeZ@gLnRDSVsG1sN55bflT^M-ECiBma-FLzuK);5sL6sT^)+d?r|H^KJ*;Idi z9C)Y9II)k|)=W|_P(|xz@P6Le)(jYPCUV&?%nR0rtB5_7YJt;- z+3gyH&3{+Yhrn@Rj<+v!WKCME$KvbjS!XDg2K zp><1GPiE?WHhS=|qi+Or95HL%$sKIW|6~5`XzuKFO&xmKryX>xi(_5*JwVEX9L+HU HX3xMsT8d?g literal 0 HcmV?d00001 diff --git a/assets/provider-icons/debrid-link.ico b/assets/provider-icons/debrid-link.ico new file mode 100644 index 0000000000000000000000000000000000000000..41be0fe937bb18a297f0be7f13de1436d5facbb9 GIT binary patch literal 18613 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 literal 0 HcmV?d00001 diff --git a/assets/provider-icons/mega-debrid.png b/assets/provider-icons/mega-debrid.png new file mode 100644 index 0000000000000000000000000000000000000000..6416a0cb10fe82101ddb06d8019b67a8b451dc4a GIT binary patch literal 1977 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 literal 0 HcmV?d00001 diff --git a/assets/provider-icons/real-debrid.png b/assets/provider-icons/real-debrid.png new file mode 100644 index 0000000000000000000000000000000000000000..ab9bf9584ef86eca16d148b5cfc7c78c3c468ab7 GIT binary patch literal 1475 zcmZ`%X;4#F6uvAG1Q8fEali&pi&Y>ZP>L%U29SaXsJKxO(4ruVu@R<@8LJ&6Aw)tp z0tAs-cpxOQ#)^@BNgyOFSttZ$Q3#?^r3Ins&9u{*{^*-?@0s_V?>pyvXWreg&;S#o z%|-wKlb}FsILv76G1P-Mwk*37CjE=PA-(|ca*bDGQQBf$V0Z`sncD#%+y-C;LWI`< zT*Ck`7XyF~69Ajk!Y9Y}0ibIg_I0GcMx%KsA|bumRXy32-NZ*CB1b@|=*dQUh?PA= z?m%u$KN%4c2?7E{s>NiUI2Tr{M5M=qlqLo3@2DNls}d1mtD3$XEuy6J<+Mf_1JNK) zl(a?#tx>6EKI$VO{XbUsLk^>1G*8QsQ>#Se$}VEVFttfR7pORVIkiDfg*K33j*%FDG zqf#{~ndQO~mT-zAookSnatE2r)_!V>oZUCg8JOcM7$seGB!Pn2HO`T!f0DlA%Icv1 z+94{vZHORH<+qJ;B=ZeQCSOUb5*N`r#`0QZ`OV|BwqId?-f(%nQcPf+|q3YG{;CZiY3yNNM_0g<=g?YAVJ?*|@~rN8S>0P^9QwV~(4jnI-H>wqR{YxD>rYGcr5|GLS8W_zJn!osSPK4Q z+pc$MZk9Ifb~+wk??lEYFAyD=y)dB+~HDM75-jrI9~d z(wvR>OA;#-dt~(ed-Ym{(Vo_YsidfK+3IiYr`LNPwB_BMI|S|OoDUAbf-IDMAbeS$ z8hGj|#HzJNC)$rb4nYH4P>8?5#5yxeFS{Q;4HIE4Itc3<=`C!g9FNfRHM?ztKT3#c z8T;pPX!^xsQw*0ASmMe*YaKnycr#w}^LLjGmoM(vQCW_x1r2+8)Gu_Ho=>(v*Q{!K z*5ZW8boVIb7V~;cC*Ow%O!r(xxpFC_GpA3DnmN|h*Wy9I=T};mZ3%B3iPcvYw%9Gq zq9)!r-JDdva%1S~>fE|z6>Vb9Jz$C6Rk(uJzwD$_>b%9iZ6%7}jhJyL&hcUVJNWGPKZdkfWyb8CjdMi@0xh!^3}N5 z)CAX*^o#6;eVZZaWM*U(E<6^GPEAj_keHN!#$}`?pc7MZaR6K|oLt;sn0CV1x%a40 z6f_+U^qh=c*`bV9v%Ix9I0d PgCjtYe<-%vFXq-?okjd? literal 0 HcmV?d00001 diff --git a/package-lock.json b/package-lock.json index 6eb9193..fd4a437 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "real-debrid-downloader", - "version": "2.0.9", + "version": "2.0.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "real-debrid-downloader", - "version": "2.0.9", + "version": "2.0.10", "license": "MIT", "dependencies": { "adm-zip": "0.6.0", diff --git a/package.json b/package.json index 986af41..c90ce88 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "real-debrid-downloader", - "version": "2.0.9", + "version": "2.0.10", "description": "Desktop downloader", "main": "build/main/main/main.js", "author": "Sucukdeluxe", @@ -65,12 +65,17 @@ "build/renderer/**/*", "resources/extractor-jvm/**/*", "LICENSE", + "THIRD_PARTY_NOTICES.md", "package.json" ], "extraResources": [ { "from": "LICENSE", "to": "LICENSE" + }, + { + "from": "THIRD_PARTY_NOTICES.md", + "to": "THIRD_PARTY_NOTICES.md" } ], "asarUnpack": [ diff --git a/scripts/verify_public_release.mjs b/scripts/verify_public_release.mjs index a7af7c6..187e0d4 100644 --- a/scripts/verify_public_release.mjs +++ b/scripts/verify_public_release.mjs @@ -16,7 +16,8 @@ const EXPECTED_NSIS_ARTIFACT_NAME = "${productName}-Setup-${version}.${ext}"; const EXPECTED_PORTABLE_ARTIFACT_NAME = "${productName}-${version}-portable.${ext}"; const REQUIRED_BUILD_FILES = Object.freeze([ "resources/extractor-jvm/**/*", - "LICENSE" + "LICENSE", + "THIRD_PARTY_NOTICES.md" ]); const EXPECTED_EXTRA_RESOURCE = Object.freeze({ from: "LICENSE", @@ -28,6 +29,11 @@ const REDISTRIBUTION_FILES = Object.freeze([ packagedPath: "resources/LICENSE", sha256: "f2c1bc02d9ba5235cc67dfea734e7dc90559b00d8cb2d142bad7a984ff96d3f6" }), + Object.freeze({ + sourcePath: "THIRD_PARTY_NOTICES.md", + packagedPath: "resources/THIRD_PARTY_NOTICES.md", + sha256: "b5d923900e9bf932fbf173191973d0892a091ac5afdc4907b8b9f4f846e8126c" + }), Object.freeze({ sourcePath: "resources/extractor-jvm/licenses/LGPL-2.1.txt", packagedPath: "resources/app.asar.unpacked/resources/extractor-jvm/licenses/LGPL-2.1.txt", diff --git a/src/main/account-status-persistence.ts b/src/main/account-status-persistence.ts new file mode 100644 index 0000000..68c73ec --- /dev/null +++ b/src/main/account-status-persistence.ts @@ -0,0 +1,8 @@ +import type { DebridAccountStatus } from "../shared/types"; + +export function canPersistExpectedAccountStatus(statuses: readonly DebridAccountStatus[], expectedAccountId: string | undefined): boolean { + return Boolean(expectedAccountId) + && statuses.length === 1 + && statuses[0].valid + && statuses[0].accountId === expectedAccountId; +} diff --git a/src/main/app-controller.ts b/src/main/app-controller.ts index 09d848d..93a49db 100644 --- a/src/main/app-controller.ts +++ b/src/main/app-controller.ts @@ -2,7 +2,6 @@ import path from "node:path"; import os from "node:os"; import v8 from "node:v8"; import { app } from "electron"; -import { getDebridLinkApiKeyIds } from "../shared/debrid-link-keys"; import { AddLinksPayload, AllDebridHostInfo, @@ -29,7 +28,7 @@ import { APP_VERSION, ONLINE_BACKUP_API_URL } from "./constants"; import { DownloadManager } from "./download-manager"; import { fetchAllDebridHostInfo, fetchDebridLinkHostLimits } from "./debrid"; import { checkAllDebridAccounts, checkMegaDebridAccount } from "./account-check"; -import { parseMegaDebridAccounts } from "../shared/mega-debrid-accounts"; +import { parseMegaDebridAccounts } from "../shared/mega-debrid-accounts"; import { parseCollectorInput } from "./link-parser"; import { configureLogger, getLogFilePath, logger } from "./logger"; import { AllDebridWebFallback } from "./all-debrid-web"; @@ -59,6 +58,8 @@ import { buildSupportBundle, getSupportBundleDefaultFileName } from "./support-b import { getTraceConfig, getTraceLogPath, initTraceLog, logTraceEvent, setTraceEnabled, shutdownTraceLog } from "./trace-log"; import type { DebugSetupCheckResult, SupportTraceConfig } from "../shared/types"; import { createOnlineBackup, downloadOnlineBackup, uploadOnlineBackup } from "./online-backup"; +import { overlayLiveUsageCounters } from "./settings-live-overlay"; +import { canPersistExpectedAccountStatus } from "./account-status-persistence"; function sanitizeSettingsPatch(partial: Partial): Partial { const entries = Object.entries(partial || {}).filter(([, value]) => value !== undefined); @@ -417,20 +418,8 @@ export class AppController { // All-time totals take the max; daily/total usage and account statuses are taken // live; per-key Debrid-Link usage is filtered to keys that still exist. private overlayLiveUsageCounters(target: AppSettings): void { - const liveSettings = this.manager.getSettings(); - target.totalDownloadedAllTime = Math.max(target.totalDownloadedAllTime || 0, liveSettings.totalDownloadedAllTime || 0); - target.totalCompletedFilesAllTime = Math.max(target.totalCompletedFilesAllTime || 0, liveSettings.totalCompletedFilesAllTime || 0); - target.totalRuntimeAllTimeMs = Math.max(target.totalRuntimeAllTimeMs || 0, this.manager.getLiveTotalRuntimeMs()); - target.providerDailyUsageDay = liveSettings.providerDailyUsageDay; - target.providerDailyUsageBytes = { ...(liveSettings.providerDailyUsageBytes || {}) }; - target.providerTotalUsageBytes = { ...(liveSettings.providerTotalUsageBytes || {}) }; - target.debridLinkApiKeyDailyUsageBytes = Object.fromEntries( - Object.entries(liveSettings.debridLinkApiKeyDailyUsageBytes || {}).filter(([keyId]) => getDebridLinkApiKeyIds(target.debridLinkApiKeys).includes(keyId)) - ); - target.debridLinkApiKeyTotalUsageBytes = Object.fromEntries( - Object.entries(liveSettings.debridLinkApiKeyTotalUsageBytes || {}).filter(([keyId]) => getDebridLinkApiKeyIds(target.debridLinkApiKeys).includes(keyId)) - ); - target.debridAccountStatuses = { ...(liveSettings.debridAccountStatuses || {}) }; + const liveSettings = this.manager.getSettings(); + overlayLiveUsageCounters(target, liveSettings, this.manager.getLiveTotalRuntimeMs()); } private applySettingsOnlyBackup(importedSettings: AppSettings, remoteDiagnostics?: unknown, restoreRemoteDiagnostics = false): void { @@ -546,10 +535,12 @@ export class AppController { return fetchDebridLinkHostLimits(this.settings.debridLinkApiKeys, host); } -public async checkDebridAccounts(settingsOverride?: AppSettings): Promise { +public async checkDebridAccounts(settingsOverride?: AppSettings, persistValidOverride = false, expectedAccountId?: string): Promise { const statuses = await checkAllDebridAccounts(settingsOverride ? normalizeSettings(settingsOverride) : this.settings); - if (!settingsOverride) { + if (!settingsOverride || (persistValidOverride && canPersistExpectedAccountStatus(statuses, expectedAccountId))) { this.manager.applyDebridAccountStatuses(statuses); + } + if (!settingsOverride) { this.audit("INFO", "Debrid-Accounts geprueft", { total: statuses.length, valid: statuses.filter((s) => s.valid).length, diff --git a/src/main/main.ts b/src/main/main.ts index 98da62c..82d1c84 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -747,8 +747,8 @@ function registerIpcHandlers(): void { return controller.getDebridLinkHostLimits(); }); - ipcMain.handle(IPC_CHANNELS.CHECK_DEBRID_ACCOUNTS, async (_event, settings?: AppSettings) => { - return controller.checkDebridAccounts(settings); + ipcMain.handle(IPC_CHANNELS.CHECK_DEBRID_ACCOUNTS, async (_event, settings?: AppSettings, persistValidOverride = false, expectedAccountId?: string) => { + return controller.checkDebridAccounts(settings, persistValidOverride === true, expectedAccountId); }); ipcMain.handle(IPC_CHANNELS.CHECK_MEGA_DEBRID_ACCOUNT, async (_event, login: string, password: string) => { diff --git a/src/main/settings-live-overlay.ts b/src/main/settings-live-overlay.ts new file mode 100644 index 0000000..bc7a4ce --- /dev/null +++ b/src/main/settings-live-overlay.ts @@ -0,0 +1,30 @@ +import { getDebridLinkApiKeyIds } from "../shared/debrid-link-keys"; +import { getMegaDebridAccountIds } from "../shared/mega-debrid-accounts"; +import type { AppSettings } from "../shared/types"; + +export function overlayLiveUsageCounters(target: AppSettings, liveSettings: AppSettings, liveTotalRuntimeMs: number): void { + const debridLinkKeyIds = new Set(getDebridLinkApiKeyIds(target.debridLinkApiKeys)); + const megaAccountIds = new Set(getMegaDebridAccountIds(target.megaCredentials || "", target.megaPassword || "")); + const validAccountIds = new Set([...debridLinkKeyIds, ...megaAccountIds]); + target.totalDownloadedAllTime = Math.max(target.totalDownloadedAllTime || 0, liveSettings.totalDownloadedAllTime || 0); + target.totalCompletedFilesAllTime = Math.max(target.totalCompletedFilesAllTime || 0, liveSettings.totalCompletedFilesAllTime || 0); + target.totalRuntimeAllTimeMs = Math.max(target.totalRuntimeAllTimeMs || 0, liveTotalRuntimeMs); + target.providerDailyUsageDay = liveSettings.providerDailyUsageDay; + target.providerDailyUsageBytes = { ...(liveSettings.providerDailyUsageBytes || {}) }; + target.providerTotalUsageBytes = { ...(liveSettings.providerTotalUsageBytes || {}) }; + target.debridLinkApiKeyDailyUsageBytes = Object.fromEntries( + Object.entries(liveSettings.debridLinkApiKeyDailyUsageBytes || {}).filter(([keyId]) => debridLinkKeyIds.has(keyId)) + ); + target.debridLinkApiKeyTotalUsageBytes = Object.fromEntries( + Object.entries(liveSettings.debridLinkApiKeyTotalUsageBytes || {}).filter(([keyId]) => debridLinkKeyIds.has(keyId)) + ); + target.megaDebridAccountDailyUsageBytes = Object.fromEntries( + Object.entries(liveSettings.megaDebridAccountDailyUsageBytes || {}).filter(([accountId]) => megaAccountIds.has(accountId)) + ); + target.megaDebridAccountTotalUsageBytes = Object.fromEntries( + Object.entries(liveSettings.megaDebridAccountTotalUsageBytes || {}).filter(([accountId]) => megaAccountIds.has(accountId)) + ); + target.debridAccountStatuses = Object.fromEntries( + Object.entries(liveSettings.debridAccountStatuses || {}).filter(([accountId]) => validAccountIds.has(accountId)) + ); +} diff --git a/src/preload/preload.ts b/src/preload/preload.ts index 380373c..9c3dc16 100644 --- a/src/preload/preload.ts +++ b/src/preload/preload.ts @@ -87,7 +87,7 @@ const api: ElectronApi = { importBestDebridCookies: (): Promise => ipcRenderer.invoke(IPC_CHANNELS.IMPORT_BESTDEBRID_COOKIES), getAllDebridHostInfo: (): Promise => ipcRenderer.invoke(IPC_CHANNELS.GET_ALLDEBRID_HOST_INFO), getDebridLinkHostLimits: (): Promise => ipcRenderer.invoke(IPC_CHANNELS.GET_DEBRIDLINK_HOST_LIMITS), - checkDebridAccounts: (settings?: AppSettings): Promise => ipcRenderer.invoke(IPC_CHANNELS.CHECK_DEBRID_ACCOUNTS, settings), + checkDebridAccounts: (settings?: AppSettings, persistValidOverride = false, expectedAccountId?: string): Promise => ipcRenderer.invoke(IPC_CHANNELS.CHECK_DEBRID_ACCOUNTS, settings, persistValidOverride, expectedAccountId), checkMegaDebridAccount: (login: string, password: string): Promise => ipcRenderer.invoke(IPC_CHANNELS.CHECK_MEGA_DEBRID_ACCOUNT, login, password), retryExtraction: (packageId: string): Promise => ipcRenderer.invoke(IPC_CHANNELS.RETRY_EXTRACTION, packageId), extractNow: (packageId: string): Promise => ipcRenderer.invoke(IPC_CHANNELS.EXTRACT_NOW, packageId), diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 917d6b3..400a18a 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -38,8 +38,11 @@ import { } from "../shared/provider-daily-limits"; import { reorderPackageOrderByDrop, sortPackageOrderByName, sortPackagesForDisplay } from "./package-order"; import { pruneSelection } from "./selection"; -import { buildBulkAccountEnabledState, buildConfiguredProviderOrder, getAccountDialogSelectableOptions, isAccountRowSelectionKey, matchesAccountModeFilter, pruneAccountRowSelection, resolveVisibleAccountKind } from "./account-ui"; +import { buildBulkAccountEnabledState, buildConfiguredProviderOrder, getAccountDialogSelectableOptions, isAccountRowSelectionKey, matchesAccountModeFilter, pruneAccountRowSelection, resolveAccountUsername, resolveVisibleAccountKind } from "./account-ui"; import type { AccountModeFilter } from "./account-ui"; +import { applyAccountEdit, buildAccountEditCheckSettings, createAccountEditState, getAccountEditExpectedStatusId, removeAccountTarget, validateAccountEdit, validateAccountEditStatuses } from "./account-edit"; +import type { AccountEditState, AccountEditTarget, AccountKind, AccountService, SingleAccountKind } from "./account-edit"; +import { ACCOUNT_SERVICE_ICONS } from "./account-service-icons"; import { DOWNLOAD_SPEED_MAX_SAMPLES, updateDownloadSpeedHistory } from "./download-speed-state"; import type { DownloadSpeedHistoryState } from "./download-speed-state"; @@ -87,22 +90,7 @@ interface LinkPopupState { isPackage: boolean; } -type AccountService = "realdebrid" | "megadebrid-api" | "megadebrid-web" | "bestdebrid" | "alldebrid" | "ddownload" | "onefichier" | "debridlink" | "linksnappy"; -type AccountKind = - | "realdebrid-api" - | "realdebrid-web" - | "megadebrid-api" - | "megadebrid-web" - | "bestdebrid-api" - | "bestdebrid-web" - | "alldebrid-api" - | "alldebrid-web" - | "ddownload-login" - | "onefichier-api" - | "debridlink-api" - | "linksnappy-login"; - -type AccountQuickAction = "realdebrid-login" | "bestdebrid-cookies" | "alldebrid-login" | "alldebrid-status"; +type AccountQuickAction = "realdebrid-login" | "bestdebrid-cookies" | "alldebrid-login" | "alldebrid-status"; interface AccountOption { kind: AccountKind; service: AccountService; @@ -183,6 +171,7 @@ interface AccountTableRow { toggleKind: "mega" | "dl" | "single"; megaLogin?: string; dlKey?: DebridLinkAccountKeyEntry; + editTarget: AccountEditTarget; } interface AccountContextMenuState { @@ -190,6 +179,25 @@ interface AccountContextMenuState { y: number; row: AccountTableRow; } + +function AccountServiceLogo({ service, className }: { service: AccountService; className: string }): ReactElement { + return ; +} + +function getAccountQuickActionMeta(kind: AccountKind): { label: string; action: AccountQuickAction } | null { + switch (kind) { + case "realdebrid-web": + return { label: "Login", action: "realdebrid-login" }; + case "bestdebrid-web": + return { label: "Cookies", action: "bestdebrid-cookies" }; + case "alldebrid-api": + return { label: "Status", action: "alldebrid-status" }; + case "alldebrid-web": + return { label: "Login", action: "alldebrid-login" }; + default: + return null; + } +} function buildDebugSetupDetails(setup: DebugSetupCheckResult): string { const formatDiskLine = (label: string, value: DebugSetupCheckResult["diskSpace"]["runtime"]): string => { @@ -586,7 +594,7 @@ function summarizeAccount(kind: AccountKind, settings: AppSettings): string { } } -function summarizeAccountLines(kind: AccountKind, settings: AppSettings): string[] { +function summarizeAccountLines(kind: AccountKind, settings: AppSettings): string[] { if (kind === "debridlink-api" && settings.accountListShowDetailedDebridLinkKeys) { const keys = parseDebridLinkApiKeys(settings.debridLinkApiKeys || ""); if (keys.length > 1) { @@ -599,8 +607,24 @@ function summarizeAccountLines(kind: AccountKind, settings: AppSettings): string return accounts.map((entry) => `${entry.label}: ${entry.maskedLogin}`); } } - return [summarizeAccount(kind, settings)]; -} + return [summarizeAccount(kind, settings)]; +} + +function getStoredAccountUsername(kind: AccountKind, settings: AppSettings): string { + switch (kind) { + case "ddownload-login": + return settings.ddownloadLogin; + case "linksnappy-login": + return settings.linkSnappyLogin; + case "realdebrid-web": + case "alldebrid-web": + return "Browser-Login"; + case "bestdebrid-web": + return "Cookies.txt"; + default: + return "API-Account"; + } +} function createAccountDialogState(mode: "create" | "edit", kind: AccountKind | null, settings: AppSettings): AccountDialogState { const baseMega: Pick = { megaAccounts: [], megaNewLogin: "", megaNewPassword: "", megaDisabledIds: [] }; @@ -1795,6 +1819,7 @@ export function App(): ReactElement { const accountContextMenuRef = useRef(null); const [linkPopup, setLinkPopup] = useState(null); const [accountDialog, setAccountDialog] = useState(null); + const [accountEditDialog, setAccountEditDialog] = useState(null); const [accountDialogSearch, setAccountDialogSearch] = useState(""); const [accountDialogModeFilter, setAccountDialogModeFilter] = useState("all"); const [keyStatsPopup, setKeyStatsPopup] = useState(null); @@ -2525,10 +2550,10 @@ export function App(): ReactElement { const limit = getMegaDebridAccountDailyLimitBytes(settingsDraft, acc.id); rows.push({ rowKey: `mega-${entry.kind}-${acc.id}`, - entry, + entry, hosterLabel: entry.serviceLabel, modeLabel: entry.modeLabel, - username: acc.maskedLogin, + username: acc.login, credentialLabel: "••••••", accountId: acc.id, checkable: true, @@ -2537,8 +2562,15 @@ export function App(): ReactElement { dailyLimitBytes: limit, dailyRemainingBytes: limit > 0 ? Math.max(0, limit - used) : 0, totalUsedBytes: getMegaDebridAccountTotalUsageBytes(snapshot.settings, acc.id), - toggleKind: "mega", - megaLogin: acc.login + toggleKind: "mega", + megaLogin: acc.login, + editTarget: { + type: "mega", + rowKey: `mega-${entry.kind}-${acc.id}`, + kind: entry.kind, + service: entry.service as "megadebrid-api" | "megadebrid-web", + accountId: acc.id + } }); } } else if (entry.kind === "debridlink-api") { @@ -2548,7 +2580,7 @@ export function App(): ReactElement { entry, hosterLabel: entry.serviceLabel, modeLabel: entry.modeLabel, - username: key.masked, + username: "", credentialLabel: "API-Key", accountId: key.id, checkable: true, @@ -2557,8 +2589,15 @@ export function App(): ReactElement { dailyLimitBytes: key.dailyLimitBytes, dailyRemainingBytes: key.dailyLimitBytes > 0 ? Math.max(0, key.dailyLimitBytes - key.dailyUsedBytes) : 0, totalUsedBytes: key.totalUsedBytes, - toggleKind: "dl", - dlKey: key + toggleKind: "dl", + dlKey: key, + editTarget: { + type: "debridlink", + rowKey: `dl-${key.id}`, + kind: "debridlink-api", + service: "debridlink", + keyId: key.id + } }); } } else { @@ -2567,7 +2606,7 @@ export function App(): ReactElement { entry, hosterLabel: entry.serviceLabel, modeLabel: entry.modeLabel, - username: entry.summary, + username: getStoredAccountUsername(entry.kind, settingsDraft), credentialLabel: getAccountCredentialLabel(entry.kind), accountId: null, checkable: false, @@ -2576,7 +2615,14 @@ export function App(): ReactElement { dailyLimitBytes: entry.dailyLimitBytes, dailyRemainingBytes: entry.dailyLimitBytes > 0 ? Math.max(0, entry.dailyRemainingBytes ?? 0) : 0, totalUsedBytes: entry.totalUsedBytes, - toggleKind: "single" + toggleKind: "single", + editTarget: { + type: "single", + rowKey: `svc-${entry.service}`, + kind: entry.kind as SingleAccountKind, + service: entry.service, + provider: entry.provider + } }); } } @@ -2624,8 +2670,8 @@ export function App(): ReactElement { else if (!st.isPremium) { statusCls = "free"; statusText = "Free Account"; } else { statusCls = "ok"; statusText = st.message || "Premium Account"; } const isProblem = statusCls === "invalid"; - const username = row.username; - const usernameTitle = st && st.email && st.email.toLowerCase() !== row.username.toLowerCase() ? `${row.username} (Mail: ${st.email})` : row.username; + const username = resolveAccountUsername(row.username, st?.email); + const usernameTitle = username; const expiry = st && st.premiumUntilMs && st.premiumUntilMs > 0 ? new Date(st.premiumUntilMs).toLocaleDateString("de-DE") : "—"; const traffic = row.dailyLimitBytes > 0 ? `${humanSize(row.dailyRemainingBytes)} von ${humanSize(row.dailyLimitBytes)} übrig` @@ -2644,7 +2690,7 @@ export function App(): ReactElement { setSelectedAccountRowKey(row.rowKey); } }} - onDoubleClick={() => openEditAccountDialog(row.entry.kind)} + onDoubleClick={() => openEditAccountDialog(row)} onContextMenu={(event) => { event.preventDefault(); event.stopPropagation(); @@ -2654,30 +2700,31 @@ export function App(): ReactElement { { if (row.toggleKind === "mega" && row.megaLogin) { void onToggleMegaAccountEnabled(row.megaLogin, row.disabled); } else if (row.toggleKind === "dl" && row.dlKey) { void onToggleDebridLinkApiKeyEnabled(row.entry, row.dlKey); } else { void onToggleAccountEnabled(row.entry); } }} /> - + - {row.hosterLabel.slice(0, 1).toUpperCase()} + {row.hosterLabel} {row.modeLabel} - - {traffic} - - {statusCls === "none" - ? - : {statusText}} + + {statusCls === "none" + ? + : {statusText}} + + {traffic} {username} {expiry} - {row.credentialLabel} + {row.credentialLabel} + Download-Traffic übrig Benutzername Verfallsdatum Passwort / Zugang @@ -6306,8 +6396,82 @@ export function App(): ReactElement { - )} - + )} + + {accountEditDialog && accountEditOption && ( +
+
event.stopPropagation()}> +
+ +
+

Account bearbeiten

+ {accountEditOption.serviceLabel} · {accountEditOption.modeLabel} +
+
+ +
+
+ Benutzername + {resolveAccountUsername(accountEditRow?.username || accountEditDialog.login, accountEditStatus?.email)} +
+
+ Status + {accountEditRow?.disabled + ? "Deaktiviert" + : accountEditStatus + ? accountEditStatus.valid + ? accountEditStatus.isPremium ? accountEditStatus.message || "Premium Account" : "Free Account" + : accountEditStatus.message || "Ungültig" + : accountEditRow?.entry.statusLabel || "Noch nicht geprüft"} +
+
+ +
+ {(accountEditDialog.target.type === "mega" || accountEditOption.needsCredentials) && ( +
+
+ + setAccountEditDialog((prev) => prev ? { ...prev, login: event.target.value } : prev)} /> +
+
+ + setAccountEditDialog((prev) => prev ? { ...prev, password: event.target.value } : prev)} /> +
+
+ )} + + {(accountEditDialog.target.type === "debridlink" || (accountEditOption.needsToken && accountEditDialog.target.type === "single")) && ( +
+ + setAccountEditDialog((prev) => prev ? { ...prev, token: event.target.value } : prev)} /> +
+ )} + +
+ + setAccountEditDialog((prev) => prev ? { ...prev, dailyLimitGb: event.target.value } : prev)} /> +
Leer oder 0 bedeutet unbegrenzt. Der Zähler wird täglich um 00:00 Uhr zurückgesetzt.
+
+ + {!accountEditOption.needsCredentials && !accountEditOption.needsToken && accountEditDialog.target.type === "single" && ( +
Dieser Account verwendet den gespeicherten Browser- oder Cookie-Login.
+ )} + + {accountEditQuickAction && ( + + )} +
+ +
+ + +
+
+
+ )} + {accountDialog && (
event.stopPropagation()}> @@ -6358,7 +6522,7 @@ export function App(): ReactElement { className={`account-picker-row${accountDialog.kind === option.kind ? " active" : ""}`} onClick={() => updateAccountDialogKind(option.kind)} > - {option.serviceLabel.slice(0, 1).toUpperCase()}{option.title} + {option.title} {getAccountPickerFunctionLabel(option)} ))} @@ -6702,7 +6866,7 @@ export function App(): ReactElement { {accountContextMenu.row.modeLabel} · {accountContextMenu.row.username}
-