Compare commits

..

1049 commits
v1.0 ... main

Author SHA1 Message Date
Roger Far
858bd7c95c no message
Some checks failed
dotnet test / build (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
2026-06-05 20:32:20 -06:00
Roger Far
2822653d3b Merge branch 'main' of https://github.com/rogerfar/rdt-client 2026-06-05 20:16:42 -06:00
Roger Far
e8a86cf806 Fix torbox save path mapping for single file downloads.
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
2026-06-05 20:16:14 -06:00
Roger Far
38530cd6fd
Merge pull request #991 from omgbeez/work/sabnzdb-delete
fix(sabnzbd): Fix delete implementation to respect finished action
2026-05-31 16:06:39 -06:00
omgbeez
a7fb1e3e9d
fix(sabnzbd): Fix delete implementation to respect finished action 2026-05-31 17:12:13 -04:00
Roger Far
5c8a078927 no message
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
2026-05-30 11:52:11 -06:00
Roger Far
f369e97d6e
Merge pull request #989 from omgbeez/upstream/devcontainer
Add dev container setup for streamlined development
2026-05-30 08:57:55 -06:00
Roger Far
a9013e0c2a
Merge pull request #986 from asylumexp/fix/torbox-database-error
[TorBox] fix: remove usage of GetHashInfoAsync
2026-05-30 08:55:56 -06:00
Sam Heinz
c6f6008591 Migrate existing TorBox torrents RdId 2026-05-29 23:04:22 +10:00
omgbeez
b8e467652b
add(devcontainer): Add dev container setup for streamlined development
Includes Dockerfile, scripts for post-start and post-create configuration, and README instructions for using the environment with .NET and Angular.
2026-05-28 15:44:43 -04:00
Sam Heinz
e1635a43a9 fix: remove usage of GetHashInfoAsync
Removes GetHashInfoAsync, and only uses the actual torrentId.

This was originally required as cachedtorrents endpoints used to use a different torrentId to the regular torrent endpoints for whatever reason, but luckily they figured out a while ago that that was not a good idea and just combined them which means calling API to search for hashes isnt needed anymore, hopefully this will reduce rate limiting a bit.
2026-05-28 14:50:39 +10:00
Roger Far
f5cee6e2f3 no message
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
2026-05-27 22:08:01 -06:00
Roger Far
767038528a Cleanup. 2026-05-27 22:04:30 -06:00
Roger Far
a82e895a03 no message 2026-05-27 22:03:59 -06:00
Roger Far
68edd4d80d Merge branch 'main' of https://github.com/rogerfar/rdt-client
# Conflicts:
#	server/RdtClient.Service.Test/GlobalTestConfig.cs
#	server/RdtClient.Web.Test/Controllers/QBittorrentControllerTest.cs
#	server/RdtClient.Web.Test/GlobalTestConfig.cs
#	server/RdtClient.Web/Controllers/QBittorrentController.cs
2026-05-27 21:57:27 -06:00
Roger Far
d6f747c8d8 Add quick fix to reject torrents that error out as soon as they are added. 2026-05-27 21:50:21 -06:00
Roger Far
4eb34cbef9
Merge pull request #983 from asylumexp/fix/torbox-database-error
[TorBox] Handle rate limits/database errors for unrestricted links with download delays
2026-05-27 21:49:16 -06:00
Roger Far
5b7b526390
Merge pull request #984 from ALenfant/antonin/sabnzbd-api-key
Support SABnzbd API key auth
2026-05-27 21:48:38 -06:00
Antonin Lenfant-Kodia
49ab603708 Support SABnzbd API key auth 2026-05-26 21:45:48 +02:00
Sam Heinz
1166e9ba99 bump torbox.net to 2.0 2026-05-26 20:16:53 +10:00
Sam Heinz
a86056a3ef handle rate limiting and database errors by delaying downloads 2026-05-26 20:16:07 +10:00
Roger Far
1645320199
Merge pull request #977 from ALenfant/antonin/improve_usenet
feat(usenet): improve NZB status messages and documentation
2026-05-25 20:22:08 -06:00
Roger Far
0e00062992
Merge pull request #976 from ALenfant/antonin/fix_parallel_tests
fix(tests): restore parallel execution
2026-05-25 20:21:39 -06:00
Roger Far
157fc8b2f2
Update CHANGELOG for version 2.0.134
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
Upgraded torbox.net dependency in the changelog.
2026-05-22 20:32:46 -06:00
Roger Far
45279115dc
Merge pull request #979 from asylumexp/main
Update TorBox.NET to 1.8.1
2026-05-22 20:28:01 -06:00
Sam Heinz
489bf6d7e0
Update TorBox.NET to 1.8.1 2026-05-23 12:21:27 +10:00
Sam Heinz
5518e7f06b
Update TorBox test mocks for limit parameter 2026-05-23 12:07:27 +10:00
Sam Heinz
84cbd5056f
Update TorBox.NET to 1.8.0 2026-05-23 11:59:13 +10:00
Antonin Lenfant-Kodia
c769bd08f0 feat(usenet): improve NZB status messages and documentation
- Update README.md to include Premiumize as a Usenet provider
- Add documentation for Sonarr/Radarr SABnzbd connection
- Update status messages to use 'NZB' instead of 'Torrent' for Usenet downloads in frontend and backend
2026-05-18 22:37:14 +02:00
Antonin Lenfant-Kodia
8142225ee9 fix(tests): restore parallel execution 2026-05-18 21:30:31 +02:00
Roger Far
284ef44e30
Merge pull request #975 from ALenfant/fix/ci-failure-974
Fix race condition causing CI failure
2026-05-17 15:34:39 -06:00
Antonin Lenfant-Kodia
fe6a2da266 fix(ci): Update xUnit DisableTestParallelization syntax 2026-05-17 23:31:17 +02:00
Antonin Lenfant-Kodia
8e49e500ea fix(ci): Resolve race conditions in tests and fix unused variable warning
- Refactored Torrents.cs to use local DbSettings instead of global Settings.Get

- Removed unused exception variable in Torrents.cs

- Disabled xUnit parallelization globally to prevent test race conditions caused by shared global state

- Enforced sequential test execution in CI workflow
2026-05-17 23:26:17 +02:00
Roger Far
b292df22fe 2.0.133
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
2026-05-17 14:18:01 -06:00
Roger Far
7aa6c03fee Cleanup. 2026-05-17 14:17:13 -06:00
Roger Far
62e286104a
Merge pull request #974 from ALenfant/main
Add Premiumize NZB support
2026-05-17 14:13:07 -06:00
Roger Far
bd48ea02a0 Upgrade Torbox dependency.
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
2026-05-17 13:58:46 -06:00
Roger Far
bc9fb89ecb
Merge pull request #967 from jwmann/feature/FixQBittorrentImportPath
Fix multiple qBittorrent Import Path issues for Completed Torrent & Fix compatibility with Sonarr
2026-05-17 13:51:17 -06:00
Antonin Lenfant-Kodia
d68b8b9bfe Add Premiumize NZB support 2026-05-17 21:49:21 +02:00
jwmann
cc977cfe29
Merge branch 'main' into feature/FixQBittorrentImportPath 2026-05-15 18:29:27 -04:00
Roger Far
4afee6938b 2.0.131
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
2026-05-14 19:33:30 -06:00
Roger Far
6a4603617b
Merge pull request #970 from jwmann/feature/FixTorrentDeletionOnError
Fix DeleteOnError provider retry loop, which causes the delete on error feature to never run
2026-05-14 15:20:40 -06:00
jwmann
a62d84779e
Fix DeleteOnError provider retry loop 2026-05-14 16:48:38 -04:00
jwmann
6c5d1fb39d
Remove duplicate import 2026-05-01 15:28:58 -04:00
Roger Far
f5ea1e0f10 2.0.130
Some checks failed
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
2026-04-28 20:38:40 -06:00
Roger Far
75ad8e848d Merge branch 'main' of https://github.com/rogerfar/rdt-client 2026-04-28 20:30:19 -06:00
Roger Far
2d8983050d
Merge pull request #966 from omgbeez/upstream/db-close-fix
fix(service): Clear SQLite connection pools on service stop
2026-04-28 20:30:13 -06:00
jwmann
cd1310c1e9
Add additional compatibility when a Torrent stores its media file within a Sub Folder 2026-04-28 21:20:40 -04:00
jwmann
7c1169d0dd
Fix QBit Import Path for Single File Torrents 2026-04-28 21:20:28 -04:00
omgbeez
9ea2219df7 fix(service): Clear SQLite connection pools on service stop
Should fix manual delete of Regression tests which create a temp SQLite DB for their tests.
2026-04-28 14:31:29 -04:00
Roger Far
fd5ece0e4d Update packages. 2026-04-27 21:21:50 -06:00
Roger Far
8c7c384bce Cleanup. 2026-04-27 21:18:33 -06:00
Roger Far
7f60f9de5a Cleanup, fixed failing build. 2026-04-27 21:04:26 -06:00
Roger Far
5d09053327 Merge branch 'main' of https://github.com/rogerfar/rdt-client 2026-04-27 21:00:50 -06:00
Roger Far
f2bfae5f85
Merge pull request #960 from omgbeez/upstream/perf-1
Few cleanups and perf fixes
2026-04-27 21:00:41 -06:00
Roger Far
3d21ca245c Merge branch 'main' of https://github.com/rogerfar/rdt-client 2026-04-27 20:12:38 -06:00
Roger Far
d6bf1845d8
Merge pull request #961 from omgbeez/upstream/race-fix
fix: Race conditions when deleting torrent and adding downloads.
2026-04-27 20:12:20 -06:00
Roger Far
147b1db4a5 no message 2026-04-27 20:10:34 -06:00
Roger Far
b037809c0e
Merge pull request #963 from jwmann/feature/qbittorrent-compatibility
Update & Fix qBittorrent compatibility for CleanupArr workflows and other qBit implementations
2026-04-27 20:09:15 -06:00
Roger Far
32aa57a561 Upgrade packages, fixed unit tests on Windows. 2026-04-27 20:08:07 -06:00
Roger Far
4dd6ae5626
Merge pull request #964 from XanderLuciano/bump-bezzad-version
Bump bezzad version
2026-04-27 19:56:44 -06:00
Claude
4920700853
chore: bump Downloader (bezzad) to 5.3.0
https://claude.ai/code/session_01N6b27qWMmqoJLjipXCwa2i
2026-04-27 02:11:05 +00:00
omgbeez
1efc682cca Undo local changes ... again 2026-04-25 11:48:36 -04:00
jwmann
28b7c79a6b
Allow Moq to intercept TorrentInfo in tests 2026-04-24 00:09:00 -04:00
jwmann
797c58e654
Fix qBittorrent compatibility for CleanupArr workflows 2026-04-23 22:13:38 -04:00
omgbeez
63fd8caee9 fix: Race conditions when deleting/updating a torrent and its downloadable files 2026-04-23 00:22:09 -04:00
omgbeez
5c71ecc211 Mem and CPU perf fixes 2026-04-21 22:19:06 -04:00
Roger Far
145ea9a8ff
Merge pull request #959 from omgbeez/upstream/fix-tests-macos
fix(tests): Fix tests to work on macos (and linux, and windows)
2026-04-21 07:23:15 -06:00
Clifford Roche
d576ee7f01 fix(tests): Make the test fixtures os independant 2026-04-20 22:11:42 -04:00
omgbeez
8664993ff6 fix(tests): Fix tests to work on macos (and linux, and windows)
Windows is the odd OS out here, adjusting logic for test data.
2026-04-20 19:44:07 -04:00
Roger Far
2627e03e97
Merge pull request #955 from anoraktrend/patch-1
Update README.md
2026-04-13 13:03:12 -06:00
Lucy Ada Randall
9c4ddb66ad
Update README.md
fix some missing things.
2026-04-07 18:59:43 -07:00
Roger Far
0abdde346d no message
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
2026-04-06 20:40:24 -06:00
Roger Far
ea9fc0b167
Merge pull request #953 from sylvaindd/mobile-torrent-view
Improve mobile layout and responsiveness for torrent components
2026-04-06 20:39:33 -06:00
Roger Far
75e02cfb82 Fixed some qBittorrent endpoints.
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
2026-04-06 20:38:32 -06:00
Sylvain DUARTE
59cff632eb
Improve mobile layout and responsiveness for torrent components 2026-03-30 17:24:20 +02:00
Roger Far
53f985d80d v2.0.127
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
2026-03-15 12:18:38 -06:00
Roger Far
f717f18f8e
Merge pull request #950 from vinodmishra/main
Add state filter to info endpoint and also implement the count endpoint
2026-03-15 10:48:55 -06:00
Vinod Mishra
d4e1502c60 Add state filter to info endpoint and also implement the count endpoint 2026-03-15 13:12:59 +00:00
Roger Far
5054ca4ae1 2.0.126
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
2026-03-14 13:44:53 -06:00
Roger Far
66bd4560a8 Run formatters. 2026-03-14 13:41:30 -06:00
Roger Far
683ac44c12 Upgrade packages.
Migrate Angular to new injector constructors.
2026-03-14 13:39:51 -06:00
Roger Far
351e1eb7c8
Merge pull request #949 from sylvaindd/mobile-torrent-table
Add mobile compatibility for torrent-table
2026-03-14 13:28:26 -06:00
Roger Far
390ab390da
Merge pull request #948 from sylvaindd/categories-select
Add categories list from settings in add-new-torrent
2026-03-14 13:28:00 -06:00
Roger Far
042b1e32e4
Merge pull request #946 from jfrconley/fix/torrents-info-hashes-filter
Fix /api/v2/torrents/info to filter by hashes query parameter
2026-03-14 13:27:13 -06:00
Roger Far
095a25f04a
Merge pull request #947 from AlexandreVassard/main
fix(symlink): strip duplicate torrent name from subpath
2026-03-14 13:26:56 -06:00
Roger Far
a4706a5830
Merge pull request #945 from omgbeez/upstream/even-better-ratelimitng
feat: Improved rate limit handling
2026-03-14 13:21:43 -06:00
Sylvain DUARTE
cf278e2964
Implement category dropdown for add-new-torrent component 2026-03-14 18:25:03 +01:00
Sylvain DUARTE
0d6369750f
Enhance mobile responsiveness of torrent table component 2026-03-14 18:24:31 +01:00
omgbeez
76dc3e0b98 fix: Long retry-after would generate timeout instead of RateLimitException
Needed to move the retry handler before the TimeOut handler, and drive the throw from the TimeOut configured by the user for the provider.
2026-03-14 09:32:51 -04:00
Sylvain DUARTE
e2195944e4
Add categories list from settings in add-new-torrent
(cherry picked from commit 20b08c52a73f1f2725d96a520c7cdd511906b8ef)
2026-03-14 11:48:45 +01:00
Sylvain DUARTE
004ed45144
Add mobile compatibility for torrent-table
(cherry picked from commit 65e843489dfefcb621b3874e55e81dc8cbd3ffb3)
2026-03-14 11:48:24 +01:00
Alexandre Vassard
6743bd472e fix(symlink): normalize subpath first component before comparing torrent name prefix
The previous fix compared subPath directly against the sanitized torrent
name, which fails when RdName contains characters removed by
RemoveInvalidPathChars (e.g. ':' on Windows). Extract and normalize the
first component of subPath before comparing so both sides go through the
same sanitization. Add tests covering the duplicate-prefix stripping case.
2026-03-08 21:29:33 +01:00
Alexandre Vassard
1dd7ab9c8e fix(symlink): strip duplicate torrent name from subpath
torrent.Files[].Path already includes the torrent name as its first
component (e.g. "Hijack/Saison 1/file.mkv"), but GetDownloadPath was
also initialising torrentPath with torrent.RdName, causing the name
to appear twice (e.g. "Hijack/Hijack/Saison 1"). Strip the torrent
name prefix from subPath before combining so the resolved path matches
the actual rclone mount layout.
2026-03-08 21:15:34 +01:00
John Conley
c0bbab5fa4 Fix /api/v2/torrents/info to filter by hashes query parameter
Add support for the `hashes` query parameter (pipe-separated, case-insensitive)
to match real qBittorrent API behavior. This fixes compatibility with cleanuparr
and *arr apps that rely on hash-based filtering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 17:39:42 -08:00
omgbeez
41787077db Add remaining seconds display during rate-limit and improve cooldown handling 2026-03-07 18:04:12 -05:00
omgbeez
67afe189b5 Refactor TorBoxDebridClientTest to include IRateLimitCoordinator and improve test consistency across methods. Updated logging and error handling for rate-limiting scenarios. 2026-03-07 17:57:42 -05:00
Roger Far
b7090eeb3a 2.0.125
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
2026-03-01 11:25:15 -07:00
Roger Far
0b726792cb Add torrents/trackers qBittorrent endpoint for cleanuparr compatibility. 2026-03-01 11:24:11 -07:00
Roger Far
7ec977b36e
Merge pull request #941 from ALenfant/fix_login_format
Always return plain text from `auth/login` API endpoint
2026-03-01 10:38:37 -07:00
Antonin Lenfant-Kodia
a7dd01eb42 Fix qB auth/login to always return plain text 2026-03-01 15:01:13 +01:00
Roger Far
178270084e no message
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
2026-02-23 20:14:27 -07:00
Roger Far
2743089b3f v1.0.124 2026-02-23 20:10:29 -07:00
Roger Far
41f816ab80 Cleanup. 2026-02-23 20:04:53 -07:00
Roger Far
81dd3dd9f8
Merge pull request #923 from omgbeez/upstream/resilience
Add resilience handler, predictive rate limit handling
2026-02-23 08:36:35 -07:00
Roger Far
0af0b41bb8
Merge pull request #935 from ALenfant/add_legacy_version_endpoint
Add legacy /version/api endpoint
2026-02-22 10:40:18 -07:00
Antonin Lenfant-Kodia
e01cd6a979 Add legacy /version/api endpoint 2026-02-22 14:49:07 +01:00
Roger Far
480d881d92 Reverted SharpCompress to 0.42.1 due to file locking issues.
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
2026-02-21 16:04:36 -07:00
Roger Far
11ccf5a2d4 v2.0.122
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
2026-02-21 15:36:32 -07:00
Roger Far
b7df2bfb15 Added test helper to make sure tests pass on Windows. 2026-02-21 15:33:12 -07:00
Roger Far
7c94528847
Merge pull request #931 from omgbeez/upstream/nzb-delete-actions
Handle NZB finished actions in `Sabnzbd` service
2026-02-21 15:18:41 -07:00
Roger Far
f4e1cf4717
Merge pull request #930 from omgbeez/upstream/downloads-fix
Fix broken download status in the UI
2026-02-21 15:18:08 -07:00
Roger Far
d023bddd24
Merge pull request #928 from omgbeez/upstream/tb-statuses
Adding some missing status mappings for TorBox
2026-02-21 15:17:13 -07:00
Roger Far
8e5238844e
Merge pull request #926 from omgbeez/upstream/qbt-eta-fix
Fix ETA calculation for QBittorrent API
2026-02-21 15:16:16 -07:00
Roger Far
9e69020df8
Merge pull request #925 from omgbeez/upstream/sorting
Update default torrent sorting to `added` in descending order
2026-02-21 15:15:21 -07:00
Roger Far
bcb24e3bfd
Merge pull request #924 from omgbeez/upstream/eta-fix
Update `Logger` to fetch download stats from `TorrentRunner` instead …
2026-02-21 15:14:34 -07:00
omgbeez
e36a5a07e5 Handle NZB finished actions in Sabnzbd service 2026-02-20 17:23:21 -05:00
omgbeez
db511929cd Addressing bug from qodo review 2026-02-20 16:13:25 -05:00
omgbeez
6dedb621d1 Fix sorting logic to default to descending order for new properties 2026-02-20 16:09:56 -05:00
omgbeez
2db2af4cea Fix broken download status in the UI 2026-02-20 15:59:47 -05:00
omgbeez
3eda321d64 Make sort settings persistent. 2026-02-20 14:42:38 -05:00
omgbeez
f32e8316e2 Adding some missing status mappings for TorBox 2026-02-20 11:38:57 -05:00
omgbeez
0ca2517eb6 Fix ETA calculation for QBittorrent API 2026-02-20 10:56:56 -05:00
omgbeez
6d0cb6ceb5 Minor fixes for Qodo 2026-02-20 10:34:30 -05:00
omgbeez
f2dab9f6e1 Update default sort field to 'added' and direction to 'desc' in torrent table component 2026-02-20 10:01:19 -05:00
omgbeez
ed397f8223 Update Logger to use TorrentRunner.GetStats for consistent download statistics 2026-02-20 09:57:57 -05:00
omgbeez
a023d33d90 Add resilience handler, predictive rate limit handling
- Applied to TorBox, can be extended easily to other providers
- Reads response headers commonplace for pre-emptive rate limit throttling and retry-after
- When a rate limit is reached, displays a warning in the UI
- Fix socket leak from direct allocation of HttpClient, replaced with factory which handles pooling and re-use of sockets.

While HttpClient is Disposable, it doesn't gaurantee (and does not) directly release underlying sockets for queries at the time the client is disposed. These sockets will go into a TCP WAIT state often for a very long time. The expected pattern in C# is to always use the HttClientFactory which will correctly handle re-use of the OS sockets in suqsequent queries reducing resource and memory leaks.
2026-02-20 09:47:13 -05:00
Roger Far
6572ba752c Bump to 2.0.121
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
2026-02-18 21:43:00 -07:00
Roger Far
ccee652572 Upgrade packages. 2026-02-18 21:35:02 -07:00
Roger Far
d6020594ef Cleanup. 2026-02-18 21:34:51 -07:00
Roger Far
d0242f235c
Merge pull request #911 from omgbeez/usenet
Adding usenet support (Torbox only implementation)
2026-02-18 21:29:21 -07:00
omgbeez
991ecefe3c Clarify error messages in TorrentRunner to distinguish between provider and runner errors. 2026-02-13 17:09:16 -05:00
omgbeez
33d8c2d825 Align Sabnzbd status reporting with updated TorrentStatus values. 2026-02-13 17:08:51 -05:00
omgbeez
f25cef711b Improve TorBox status mapping to TorrentStatus and log any unmapped statuses for debugging. 2026-02-13 17:06:57 -05:00
omgbeez
40be170677 Refactor download progress calculation to use centralized stats and simplify logic across services.
- Fixes an issue where progress goes back to 50% as soon as downloads are done
- Prevents copy and update of torrent/nzb list by not updating download stats on every use, most callers don't require this data, notable perf/memory improvement.
2026-02-13 17:06:12 -05:00
omgbeez
a5f9cd353b SqLite doesn't support OrderBy on DateTimeOffset types
Sorting will require a copy to fix this for the moment, could probably change the underlying type later to prevent this.
2026-02-13 13:22:41 -05:00
omgbeez
e93e4f6f22 Usenet support 2026-02-13 13:15:57 -05:00
Roger Far
d76d26131c Remove obsolete caching. 2026-02-11 20:24:27 -07:00
Roger Far
580c7132c6 Cleanup. 2026-02-11 20:22:28 -07:00
Roger Far
e51e5cde45 Merge branch 'main' of https://github.com/rogerfar/rdt-client 2026-02-11 20:21:08 -07:00
Roger Far
a68eec1bfa
Merge pull request #903 from Chesyre/main
perf: Major performance optimizations - memory leak fix, SQLite tuning, and cache improvements
2026-02-11 20:20:54 -07:00
Roger Far
0c5e38e01a
Merge branch 'main' into main 2026-02-11 20:20:47 -07:00
Roger Far
7aca0eaedd Merge branch 'main' of https://github.com/rogerfar/rdt-client 2026-02-11 20:08:21 -07:00
Roger Far
da576ef2c4
Merge pull request #913 from omgbeez/upstream/websocket-perf
Optimize websocket data sending to the UI
2026-02-11 20:08:13 -07:00
Roger Far
01a26c614b
Merge branch 'main' into upstream/websocket-perf 2026-02-11 20:04:57 -07:00
Roger Far
871802ad34 Merge branch 'main' of https://github.com/rogerfar/rdt-client 2026-02-11 20:02:41 -07:00
Roger Far
0f8921e1fb
Merge pull request #916 from omgbeez/upstream/qbt-eta
Add ETA calculation for torrents.
2026-02-11 20:02:32 -07:00
Roger Far
6b85d7bbbc
Merge branch 'main' into upstream/qbt-eta 2026-02-11 20:02:18 -07:00
Roger Far
25c06ab6fc Merge branch 'main' of https://github.com/rogerfar/rdt-client 2026-02-11 19:59:42 -07:00
Roger Far
ca325aaac1
Merge pull request #919 from jwmann/fix/qbittorrent-health-check-api
Fix qBittorrent Health Check API for Cleanuparr & others
2026-02-11 19:59:35 -07:00
Roger Far
99b2ad8e27 Update readme to reflect .NET 10. 2026-02-11 19:59:07 -07:00
Roger Far
b539e8933e no message
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
2026-02-11 19:50:14 -07:00
Roger Far
a9648248f4 Global formatting. 2026-02-11 19:44:49 -07:00
Roger Far
24321a55c9 Upgrade Angular to 21 and .NET to 10. 2026-02-11 19:40:23 -07:00
Roger Far
028c9f06ec Upgrade to Angular 21. 2026-02-11 19:17:44 -07:00
jwmann
b74b5dd7a9
Add AllowAnonymous to QBittorrent health check endpoints
This fix allows Cleanuparr to successfully connect to RDT Client by making the
health check endpoints accessible without authentication, matching real qBittorrent
behavior. The three endpoints affected are:
- /api/v2/app/version
- /api/v2/app/webapiVersion
- /api/v2/app/buildInfo

Fixes #867
2026-02-11 19:08:59 -05:00
omgbeez
42ebd2c74b Calculate remaining time based on download speed and bytes remaining 2026-01-17 12:33:31 -05:00
omgbeez
9454ea1bd1 Refactored torrent and download models with DTOs to simplify serialization and API responses. 2026-01-17 12:23:16 -05:00
omgbeez
6cef70b979 Simplify download progress and remaining time calculations in QBittorrent service. 2026-01-17 12:21:13 -05:00
destrodxbad
15af54ed24 perf(cache): Remove aggressive cache invalidation and optimize queries 2025-11-18 02:04:51 +01:00
destrodxbad
47355030b5 perf(database): Configure SQLite with WAL mode and connection pooling 2025-11-18 02:03:13 +01:00
destrodxbad
a7bb8da1d5 perf(websocket): Fix scope leak and add adaptive polling 2025-11-18 02:01:41 +01:00
Roger Far
371a13e060
Merge pull request #901 from Chesyre/main
Add automatic disk space monitoring for Bezzad downloader
2025-11-15 17:15:01 -07:00
destrodxbad
e606501834 Fix: Refresh disk space notification during pause and after reconnection 2025-11-14 20:29:38 +01:00
destrodxbad
6db556ed5c Fix: Bug refresh 2025-11-14 18:04:41 +01:00
destrodxbad
f12f28a8bb Fix: Refresh disk space status while paused 2025-11-14 15:17:11 +01:00
destrodxbad
57ea1b3881 Add proper TypeScript typing for disk space status 2025-11-14 14:44:08 +01:00
Roger Far
ed218d76ce
Merge pull request #900 from saif191020/main
Added Css to Improve Mobile support on the home page
2025-11-13 19:07:52 -07:00
destrodxbad
27398ba993 Add automatic disk space monitoring for Bezzad downloader 2025-11-09 03:05:56 +01:00
Saif Basheer Ahamed
994f167ab3 Updated Css for better support on mobiles in the torrent table in homepage 2025-11-08 14:33:09 +05:30
Roger Versluis
c51f8cb503 Removed internal downloader from the GUI.
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
2025-10-13 16:46:27 -06:00
Roger Versluis
1080143fce Added some fake qBittorrent API calls for decluttarr.
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
2025-10-08 21:26:14 -06:00
Roger Versluis
90c1092fe5 no message
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
2025-10-06 20:16:39 -06:00
Roger Versluis
123c4f29c6 Remove internal downloader. 2025-10-06 20:15:07 -06:00
Roger Far
dadab50840 no message
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
2025-08-04 22:41:25 -06:00
Roger Far
eb80e9083b Merge branch 'main' of https://github.com/rogerfar/rdt-client 2025-08-04 22:40:42 -06:00
Roger Far
02a97f3170 Add banned tracker setting. 2025-08-04 22:40:11 -06:00
Roger Far
2e8a7ce080
Merge pull request #836 from Cucumberrbob/chore/angular-v20
update to angular 20 (plus other frontend gardening)
2025-07-29 13:05:59 -06:00
Cucumberrbob
48f00e0459
Merge branch 'main' into chore/angular-v20 2025-07-29 19:29:05 +01:00
Roger Far
21dd80a061 no message
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
2025-07-28 22:35:23 -06:00
Roger Far
044e04c694 no message 2025-07-28 22:31:40 -06:00
Roger Far
f3d0b31fea Merge branch 'main' of https://github.com/rogerfar/rdt-client 2025-07-28 22:00:45 -06:00
Roger Far
25032a4e65
Merge pull request #857 from Cucumberrbob/feat/completed-torrent-grace-period
add setting to delay `FinishedAction`
2025-07-28 22:00:31 -06:00
Roger Far
d9858409db Cleanup, make sure the RD API adheres to the set timeout when trying to add a magnet or file. 2025-07-28 21:46:20 -06:00
Cucumberrbob
30b9b9a4ad
run prettier 2025-07-06 22:08:28 +01:00
Cucumberrbob
1be3e98ad7
Update server/RdtClient.Service/Services/TorrentRunner.cs
Co-authored-by: qodo-merge-pro[bot] <151058649+qodo-merge-pro[bot]@users.noreply.github.com>
2025-07-06 22:05:29 +01:00
Cucumberrbob
6bc9c06faa
add setting to delay FinishedAction 2025-07-06 21:59:36 +01:00
Cucumberrbob
1fe407ba5a
correct dockerhub image tag in docker readme
Closes #854
2025-07-03 21:05:21 +01:00
Cucumberrbob
6d57aafcfe
Add changelog for v2.0.114
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
2025-06-21 12:15:29 +01:00
Roger Far
3bc657dbb2
Merge pull request #842 from Cucumberrbob/feat/always-set-user-agent
set user agent for all `HttpClient`s from `IHttpClientFactory`
2025-06-19 11:28:53 -06:00
Cucumberrbob
0461058d86
set user agent for all HttpClients from IHttpClientFactory 2025-06-14 14:02:39 +01:00
Cucumberrbob
a24928f9eb
add standalone: true to all components 2025-06-07 18:56:40 +01:00
Cucumberrbob
a7cab6d0dc
convert unmutated lets to consts 2025-06-07 18:47:12 +01:00
Cucumberrbob
6909fb3851
assign progress in one go
rather than checking for `NaN`, just `|| 0` - it will leave `0` as is, and convert any `NaN`s to `0`
2025-06-07 18:43:42 +01:00
Cucumberrbob
f5f2760f80
merge declaration and initialisation of speed in TorrentStatusPipe 2025-06-07 18:41:06 +01:00
Cucumberrbob
08c1750def
remove unused css
`scroll-container`, `is-fullwidth-label`, `is-block`, and `strike-through` were all removed in 116ca29,
`fa-download` was removed in 3ee687f61
2025-06-07 18:35:59 +01:00
Cucumberrbob
aa512d1a33
use const not var 2025-06-07 18:35:57 +01:00
Cucumberrbob
3626f8cb97
make for attributes correspond to input ids on labels in signup/login forms 2025-06-07 18:35:54 +01:00
Cucumberrbob
6277a4ca5e
use non-deprecated throwError call signature 2025-06-07 18:35:49 +01:00
Cucumberrbob
e48f1d91a4
make auth resolver standalone 2025-06-07 18:35:45 +01:00
Cucumberrbob
2e7c97d023
use non-deprecated observer .subscribe call signature
https://rxjs.dev/deprecations/subscribe-arguments
2025-06-07 18:35:37 +01:00
Cucumberrbob
2b95b8d714
remove unused methods (references removed in 116ca29) 2025-06-07 18:28:45 +01:00
Cucumberrbob
c1e48d35a5
run ng generate @angular:core/standalone --mode "standalone-bootstrap" 2025-06-07 18:27:30 +01:00
Cucumberrbob
4fb9e972f5
run ng generate @angular:core/standalone --mode "convert-to-standalone" 2025-06-07 18:26:58 +01:00
Cucumberrbob
070d80a6fa
update node version in dockerfile so angular will build
angular 20 requires at least node 22.12 (or 20.19)
2025-06-07 18:25:12 +01:00
Cucumberrbob
ed354e93a9
drop curray
use vanilla js array methods instead
2025-06-07 18:25:12 +01:00
Cucumberrbob
9d0528902a
run ng update @angular/core@20 @angular/cli@20 2025-06-07 18:20:22 +01:00
Cucumberrbob
cb29187f72
Merge pull request #825 from mentalblank/select-all-delete-button-on-individual-torrent-screen
Fix checkboxes in #819
2025-05-31 12:42:54 +01:00
Cucumberrbob
32fe929f48
Merge pull request #816 from mentalblank/magnet-enrichment
Tracker Enrichment
2025-05-31 12:42:13 +01:00
MentalBlank
60263ca685 Tracker enrichment test changes 2025-05-29 23:54:47 +10:00
MentalBlank
63322f3b94 Fix checkboxes being on wrong side of modal 2025-05-29 23:27:38 +10:00
MentalBlank
98042f8040 Improved enrichment validation, logging, and test coverage.
Strengthened URL Validation.
Improved HTTP Client Configuration.
Logged total rejected tracker count at the information level.
Logged individual rejected tracker details at the debug level.
Debug logs for bytes and enriched bytes are now conditional.
Converted some tests from Fact to Theory.
Used Assert.Collection in EnricherTest.cs.
Standardized URL Encoding in Enricher.cs.
2025-05-25 21:50:26 +10:00
Cucumberrbob
479239c94d
Merge pull request #819 from mentalblank/select-all-delete-button-on-individual-torrent-screen
Include select all option when deleting a torrent from individual torrent details screen
2025-05-24 12:37:06 +01:00
MentalBlank
4be3280c3a Enricher code refactoring with small tweaks.
Added additional tests.
Changed enricher class to sealed.
Changed logging.
Changed how trackers are added.
Fixed some issues around edge cases.
2025-05-24 02:16:27 +10:00
Roger Far
1973b73b3c no message
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
2025-05-22 19:39:20 -06:00
Roger Far
bb9cc67ff7 Revert Synolog.Api.Client because of breaking changes. 2025-05-22 19:38:30 -06:00
MentalBlank
452d8cc1cf Split tracker grabbing and parsing logic.
Split grab and parse logic, recheck cache after semaphore lock, timeouts, add URL validation check, add torrent file tests, replace test hash with valid SHA1 format, tracker URL validation, fix token disposal issue
2025-05-23 00:02:07 +10:00
MentalBlank
408c475af5 Tracker list caching with configurable expiration
Add new option to general settings that allows the user to set the time in minutes to cache the tracker list. 0 disables caching. Default value is 60.
2025-05-21 00:45:14 +10:00
MentalBlank
2e04129b00 Include select all option when deleting a torrent from details screen 2025-05-20 03:37:01 +10:00
MentalBlank
80a8a1020c Expand enrichment to torrent files 2025-05-20 03:27:41 +10:00
Roger Far
a06e0202d9 no message
Some checks failed
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
2025-05-18 11:41:16 -06:00
Roger Far
d989e4a497 no message 2025-05-18 11:34:40 -06:00
Roger Far
4801b94c64 Upgrade packages. 2025-05-18 11:31:38 -06:00
Roger Far
772816ced0
Merge pull request #818 from Cucumberrbob/feat/angular-control-flow
migrate to new Angular Control Flow syntax
2025-05-18 11:13:50 -06:00
Roger Far
2a38d43a1b
Merge pull request #817 from Cucumberrbob/feat/turn-off-auto-unpack
Don't unpack if `UnpackLimit` is set to `0`
2025-05-18 11:11:49 -06:00
Roger Far
6ec3b1072b
Update server/RdtClient.Service/Services/TorrentRunner.cs
Co-authored-by: qodo-merge-pro[bot] <151058649+qodo-merge-pro[bot]@users.noreply.github.com>
2025-05-18 11:11:17 -06:00
Roger Far
f131726cce
Merge pull request #813 from Cucumberrbob/fix/providerupdater-logging-categoryname
fix: make `ProviderUpdater` log under the correct category name
2025-05-18 11:06:13 -06:00
Roger Far
b7efb78814
Merge pull request #812 from Cucumberrbob/fix/realdebridupdatelock-when-dequeueing
fix: lock RealDebridUpdateLock when dequeuing, not when adding to the queue
2025-05-18 11:05:53 -06:00
Cucumberrbob
a5d0f7b564
rework some if statements to be a single if/else, fix typo 2025-05-18 15:56:12 +01:00
Cucumberrbob
a0cbe54152
migrate to new Angular Control Flow syntax
also fixes a typo
2025-05-18 15:50:36 +01:00
Cucumberrbob
75202ed5a9
Merge pull request #815 from mentalblank/register-as-default-magnet-handler
Magnet handler registration contextual links
2025-05-18 14:48:49 +01:00
MentalBlank
f93109cc27 Magnet link registration contextual links
Replaced the static magnet handler help text shown when registration is unavailable (due to an insecure context or lack of browser support) with contextual HTML links.
2025-05-18 21:50:34 +10:00
Cucumberrbob
e17c8c5c1a
make setting UnpackLimit to 0 turn unpacking off 2025-05-17 13:30:22 +01:00
Cucumberrbob
5de0bac9d5
reformat EnrichMagnetLink 2025-05-15 23:37:17 +01:00
MentalBlank
ae66b40a3d Allow user to define tracker list 2025-05-16 07:57:48 +10:00
MentalBlank
e6a22f6780 Magnet Enrichment
Add magnet link enrichment using configurable tracker lists from ngosang/trackerslist
2025-05-15 19:48:41 +10:00
Cucumberrbob
ba821ebc2f
use ILogger<ProviderUpdater> for ProviderUpdater, not ILogger<TaskRunner> 2025-05-14 14:45:06 +01:00
Cucumberrbob
e7c2b42bd2
lock RealDebridUpdateLock when dequeuing, not when adding to the queue 2025-05-14 14:05:33 +01:00
Cucumberrbob
ea3839514d
update changelog for 2.0.111
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
2025-05-03 17:20:28 +01:00
Cucumberrbob
9319f099fa
Merge pull request #806 from mentalblank/register-as-default-magnet-handler
Register client as browser's default magnet handler
2025-05-03 17:16:43 +01:00
MentalBlank
fed6d022f8 Improve magnet handler registration
- Removed HTTPS protocol check in `registerMagnetHandler()`. Now using `this.canRegisterMagnetHandler = !!(window.isSecureContext && 'registerProtocolHandler' in navigator);` to determine support for protocol handler registration in a secure context.
- Updated the registration button and help text to respond dynamically based on `canRegisterMagnetHandler` and provide better user experience and messaging when unsupported.
- Wrapped `navigator.registerProtocolHandler()` in a try catch block to handle registration failures gracefully and avoid unhandled exceptions.
2025-05-04 01:41:37 +10:00
MentalBlank
94e9a8ccd0 Move magnet handler registration
Moved magnet handler registration to general settings.
2025-05-02 18:37:08 +10:00
MentalBlank
e28749e465 Register client as browser's default magnet handler 2025-05-01 00:10:14 +10:00
Roger Far
757d112d01
Fixed build number in app.
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
2025-04-24 13:04:28 -06:00
Roger Far
6faa064d94
Merge pull request #802 from Cucumberrbob/fix/version-docker-images
fix: patch version before building docker container in github action
2025-04-24 13:03:56 -06:00
Cucumberrbob
a306500d76
fix: patch version before building docker container in github action 2025-04-24 19:51:02 +01:00
Roger Far
1d707940a8 2.0.109
Some checks failed
Release Docker Image / build (map[arch:amd64 platform:linux/amd64 runs-on:ubuntu-latest]) (push) Has been cancelled
Release Docker Image / build (map[arch:arm64 platform:linux/arm64 runs-on:ubuntu-24.04-arm]) (push) Has been cancelled
Create GitHub Release / Test, Build, and Bundle (push) Has been cancelled
dotnet test / build (push) Has been cancelled
Release Docker Image / push-images (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
2025-04-23 21:52:16 -06:00
Roger Far
e6686aa35d
Merge pull request #796 from Cucumberrbob/build/multi-arch-docker
use arm GitHub Actions runner to build arm docker image
2025-04-23 21:47:49 -06:00
Roger Far
d8da1a47a7
Merge pull request #788 from Cucumberrbob/fix/build-github-release-on-windows
build: build GitHub release .zip on windows not linux
2025-04-23 21:47:41 -06:00
Roger Far
0265a48aa8
Merge pull request #798 from Cucumberrbob/fix/auto-import-host-download-action
fix: Set `HostDownloadAction` when auto-importing torrents
2025-04-23 21:45:58 -06:00
Roger Far
4f770d923a
Merge pull request #795 from Cucumberrbob/fix/censor-downloadstation-password
fix: censor download station password when logging settings at startup
2025-04-23 21:45:32 -06:00
Roger Far
fa499395bd
Merge pull request #794 from Cucumberrbob/fix/debrid-queue
Debrid Queue fixes (don't auto delete queued torrents, handle errors when dequeueing)
2025-04-23 21:45:10 -06:00
Cucumberrbob
8929ba19d2
fix: Set HostDownloadAction when auto-importing torrents
we had a setting for it, just never used it!
2025-04-22 20:16:18 +01:00
Cucumberrbob
40df762c90
use job outputs not uploaded artifacts in docker image github action 2025-04-20 18:04:13 +01:00
Cucumberrbob
7154cc55cf
use arm runner to build arm docker iamge, combine using docker imagetools 2025-04-20 13:51:50 +01:00
Cucumberrbob
03459d0129
fix: censor download station password when logging settings at startup 2025-04-20 12:14:56 +01:00
Cucumberrbob
5ba630ff52
fix: don't autodelete queued torrents 2025-04-20 12:00:47 +01:00
Cucumberrbob
a4f20fd3a4
fix: catch and handle errors when dequeuing torrent 2025-04-20 12:00:21 +01:00
Cucumberrbob
e10de75494
build: build GitHub release .zip on windows not linux 2025-04-14 22:13:08 +01:00
Roger Far
995fb1c6cf no message
Some checks failed
Release Docker Image / build (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
dotnet test / build (push) Has been cancelled
2025-04-13 16:16:36 -06:00
Roger Far
447b48dcf1
Merge pull request #785 from Cucumberrbob/fix/websocket-not-updating
fix: re-enable websocket by running background service
2025-04-13 15:13:56 -06:00
Cucumberrbob
b620e29209
Run WebsocketsUpdater as a HostedService 2025-04-13 18:32:59 +01:00
Roger Far
b1d40e6891 Fixed docker file.
Some checks failed
Release Docker Image / build (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
dotnet test / build (push) Has been cancelled
2025-04-13 10:56:42 -06:00
Roger Far
fcbc0b0b35 Fixed changelog.
Some checks failed
Release Docker Image / build (push) Has been cancelled
Create GitHub Release / Create GitHub release (push) Has been cancelled
dotnet test / build (push) Has been cancelled
2025-04-13 10:51:48 -06:00
Roger Far
49ad0f185f Fixed changelog. 2025-04-13 10:48:39 -06:00
Roger Far
11c341b7f8 no message 2025-04-13 10:41:45 -06:00
Roger Far
fab14f1f3f Changed how changelogs are fetched. 2025-04-13 10:39:14 -06:00
Roger Far
a2f031e757 Removed unsupported updating of the GHCR. 2025-04-13 10:24:35 -06:00
Roger Far
c40ea173fa no message
Some checks failed
Docker Image CI / build (push) Has been cancelled
Release / Create GitHub release (push) Has been cancelled
dotnet test / build (push) Has been cancelled
2025-04-13 10:11:01 -06:00
Roger Far
2009ed6ec9 no message 2025-04-13 10:07:23 -06:00
Roger Far
827e94ca89 no message 2025-04-13 10:04:11 -06:00
Roger Far
a1a4e1031d Fixed up github actions. 2025-04-13 09:59:55 -06:00
Roger Far
87efa9dc00 Bump to 2.0.105. 2025-04-13 09:54:29 -06:00
Roger Far
a25cd62ab0 Moved the websocket update process to its own background thread to improve UI update consistency. 2025-04-13 09:53:15 -06:00
Roger Far
781937dc8f Merge branch 'main' of https://github.com/rogerfar/rdt-client 2025-04-13 09:34:34 -06:00
Roger Far
b4385bcaad no message 2025-04-13 09:33:36 -06:00
Cucumberrbob
ea4a25ed93
make botnet-test action run on PRs to main (not master) 2025-04-13 16:23:40 +01:00
Roger Far
b0061abdeb
Merge pull request #763 from Cucumberrbob/feat/debrid-queue
feat: queue torrents before adding to debrid provider
2025-04-13 09:17:13 -06:00
Roger Far
5270375f5c
Merge branch 'main' into feat/debrid-queue 2025-04-13 09:16:46 -06:00
Roger Far
1db70100e2
Merge pull request #739 from Cucumberrbob/feat/github-action
Add `dotnet test` GitHub Action
2025-04-13 09:16:01 -06:00
Cucumberrbob
15d7e7b0cc
Merge branch 'main' into feat/github-action 2025-04-13 15:07:11 +01:00
Roger Far
d436ea2482 Merge branch 'main' of https://github.com/rogerfar/rdt-client 2025-04-13 07:55:26 -06:00
Roger Far
c651d21e81 Cleanup, upgrade packages. 2025-04-13 07:55:22 -06:00
Roger Far
a9485827ce
Merge pull request #783 from Cucumberrbob/feat/tb-optional-zipping
[TB] Make zipped downloads optional
2025-04-13 07:55:05 -06:00
Roger Far
1dff00c3f1
Merge pull request #777 from Cucumberrbob/fix/pm-all-files-excluded
fix: [PM] `GetDownloadLinks`: return empty list not null when all files excluded
2025-04-13 07:53:23 -06:00
Cucumberrbob
478cc0e714
Merge branch 'main' into fix/pm-all-files-excluded 2025-04-13 14:52:09 +01:00
Cucumberrbob
75f115fd42
[TB] Make zipped downloads optional 2025-04-13 14:47:12 +01:00
Roger Far
1ce3d20541
Merge pull request #769 from Cucumberrbob/fix/rd-exclude-all
fix: return number of files selected from `SelectFiles` so we can handle all files excluded for RD
2025-04-13 07:41:55 -06:00
Roger Far
770e32c2cb
Merge pull request #765 from Cucumberrbob/feat/rd-configurable-api-url
feat: add setting to change RD api hostname
2025-04-13 07:41:05 -06:00
Roger Far
88358c8272
Merge pull request #754 from Cucumberrbob/perf/tb-dont-refetch-torrentlist-in-providerupdater
[TB] use `torrentClientTorrent` (if passed) instead of re-fetching in `UpdateData`
2025-04-13 07:40:16 -06:00
Roger Far
ab3ff4a064
Merge pull request #83 from breagan1983/patch-1
Rootless Podman and CIFS Connections
2025-04-13 07:38:47 -06:00
Roger Far
2e028ed17c
Merge pull request #746 from Cucumberrbob/refactor/get-download-info
refactor: use `GetDownloadInfo` - a richer version of `GetDownloadLinks`
2025-04-13 07:37:57 -06:00
Roger Far
ba8dacd01c no message 2025-04-13 07:37:17 -06:00
Cucumberrbob
c34eaf2968
Add release dates for versions v2.0.103 and 104 to changelog 2025-04-12 15:30:45 +01:00
Cucumberrbob
8117d61566
🤦 remember to update the assembly version number when releasing
Some checks failed
Docker Image CI / build (push) Has been cancelled
2025-04-12 15:21:13 +01:00
Cucumberrbob
80da992d5d
Prepare changelog for v2.0.103
Some checks failed
Docker Image CI / build (push) Has been cancelled
2025-04-12 15:07:57 +01:00
Cucumberrbob
84d144ed7b
[PM] Return empty list, not null when all files filtered out in GetDownloadLinks 2025-04-09 10:41:50 +01:00
Cucumberrbob
a660a86bb4
Add release notes for#776 2025-04-09 09:09:27 +01:00
Cucumberrbob
f5317de9de
Merge pull request #776 from EugeneKallis/main
Adding a select all button for the delete torrent popup
2025-04-09 08:59:45 +01:00
Eugene Kallivrousis
ef4245006c removing comment 2025-04-08 19:05:20 -04:00
Eugene Kallivrousis
282ebe4a5e updating port 2025-04-08 19:04:06 -04:00
Eugene Kallivrousis
b0676d31d2 reverting proxy change 2025-04-08 19:03:15 -04:00
Eugene Kallivrousis
158f4587e2 Updating as per comments from pr 2025-04-08 19:02:48 -04:00
Cucumberrbob
953e208b18
run prettier 2025-04-08 20:49:06 +01:00
Eugene Kallivrousis
6f67f38600 Adding a select all button for the delete torrent popup 2025-04-08 15:20:56 -04:00
Cucumberrbob
aa3f24ff50
Return number of selected files from SelectFiles so we can handle all files being excluded 2025-03-27 12:01:53 +00:00
Cucumberrbob
3f4fb6d20a
Merge pull request #766 from nagromc/patch-1
docs: fix level for Synology Download Station
2025-03-25 17:10:46 +00:00
Morgan Courbet
d267a3451a
docs: fix level for Synology Download Station 2025-03-24 13:26:38 +01:00
Cucumberrbob
3780a954a2
add setting to change RD api hostname 2025-03-23 14:08:27 +00:00
Cucumberrbob
99ea3f008f
fix merge (again!) 2025-03-23 13:32:37 +00:00
Cucumberrbob
5a1b96b909
Merge remote-tracking branch 'origin/main' into fork/Cucumberrbob/refactor/get-download-info
# Conflicts:
#	server/RdtClient.Service.Test/Services/TorrentClients/AllDebridTorrentClientTest.cs
#	server/RdtClient.Service/Services/TorrentClients/AllDebridTorrentClient.cs
#	server/RdtClient.Service/Services/TorrentClients/TorBoxTorrentClient.cs
2025-03-23 13:30:05 +00:00
Roger Far
9841ab6399 no message 2025-03-21 16:48:55 -06:00
Roger Far
720696f5ec no message 2025-03-21 16:40:21 -06:00
Roger Far
2fbfc43f6c no message 2025-03-21 16:36:11 -06:00
Roger Far
38c8c57672 no message 2025-03-21 16:29:21 -06:00
Roger Far
f70ddb87b6 no message 2025-03-21 16:28:03 -06:00
Roger Far
c7020f1056 Tests to make releasing easier and more automated. 2025-03-21 16:26:07 -06:00
Roger Far
605868fe70 Changed to get the version from the API. 2025-03-21 16:02:45 -06:00
Roger Far
53a1407ef8 Angular cleanup. 2025-03-21 15:31:34 -06:00
Roger Far
b3f45ece20 Upgrade to latest packages, ran prettier. 2025-03-21 15:17:29 -06:00
Roger Far
091b976b9b Update packages. 2025-03-21 15:10:54 -06:00
Roger Far
83cd14f807
Merge pull request #761 from Cucumberrbob/fix/close-mobile-menu-on-navigation
fix: close mobile menu after navigating
2025-03-21 15:08:04 -06:00
Roger Far
7340fb3183
Merge pull request #760 from Cucumberrbob/fix/word-break-long-torrent-names
add `word-break: break-all` to torrent name `td`
2025-03-21 15:07:31 -06:00
Roger Far
3d4e656121
Merge pull request #755 from Cucumberrbob/perf/providerupdater-dont-update-when-all-rdfinished
`ProviderUpdater`: don't run if all torrents finished and `AutoImport == false`
2025-03-21 15:07:12 -06:00
Roger Far
2eddeaecb7
Merge pull request #747 from Cucumberrbob/feat/ignore-updates
feat: Allow ignoring update notifications (but notify anyway if GHSA found)
2025-03-21 15:04:37 -06:00
Roger Far
67b699605a
Merge pull request #744 from Cucumberrbob/fix/setup-provider-auth
fix: require auth to setup provider
2025-03-21 14:59:52 -06:00
Roger Far
d4d2689772
Merge pull request #742 from Cucumberrbob/fix/proxy-server-is-bezzad-not-internal
fix: typo - proxyserver setting applies to bezzad not internal
2025-03-21 14:57:56 -06:00
Roger Far
2be825e248
Merge pull request #741 from Cucumberrbob/feat/ad-rd-exclude-all-fail-torrent
feat: [AD] [RD] don't download anything when all files excluded
2025-03-21 14:14:18 -06:00
Roger Far
0b7749376a
Merge pull request #735 from Cucumberrbob/fix/tb-include-version-in-user-agent
include version when setting torbox api client useragent
2025-03-21 14:12:30 -06:00
Cucumberrbob
7eb1a610b4
use ternary not (Boolean) switch 2025-03-21 13:35:51 +00:00
Cucumberrbob
74e6d2cf95
don't add torrents straight to debrid provider, add to queue first, dequeue in TorrentRunner 2025-03-21 13:09:59 +00:00
Cucumberrbob
9fa0252624
fix: close mobile menu after navigating 2025-03-20 15:48:51 +00:00
Cucumberrbob
4459706e79
add word-break: break-all to torrent name td
wraps text rather than making a horizontal scroll bar
2025-03-20 15:33:08 +00:00
Cucumberrbob
e9c4544d34
ProviderUpdater: don't run if all torrents finished and AutoImport == false 2025-03-17 20:50:09 +00:00
Cucumberrbob
ab5a013edb
[TB] use torrentClientTorrent (if it's passed) instead of fetching in UpdateData
Makes `ProviderUpdater` only cause 2 API requests each run rather than `O(n)` where `n` is the number of torrents
2025-03-17 20:24:31 +00:00
Cucumberrbob
e6f7db0763
fix merge 2025-03-16 15:49:46 +00:00
Cucumberrbob
609c67e0c6
Merge branch 'master' into refactor/get-download-info 2025-03-16 15:46:36 +00:00
Cucumberrbob
aa6e6c7df5
Add release notes for #749 2025-03-16 15:38:39 +00:00
Cucumberrbob
b4bb775617
Merge pull request #749 from asylumexp/fix/tb-download-as-zip
[TB] Add downloading via zip functionality
2025-03-16 15:34:55 +00:00
Cucumberrbob
93fac23128
run dotnet format 2025-03-16 15:34:25 +00:00
Sam Heinz
afc7d1a964 [TB] fix: replace Contains with EndsWith 2025-03-17 00:50:51 +10:00
Sam Heinz
7a822bef5c [TB] fix: extract folder move logic 2025-03-17 00:28:19 +10:00
Sam Heinz
7d1aeed85c
[TB] fix: getting provider in UnpackClient
Co-authored-by: Cucumberrbob <128094686+Cucumberrbob@users.noreply.github.com>
2025-03-16 23:33:17 +10:00
Sam Heinz
bd96795f34 [TB] fix: moving hash dir files too far up 2025-03-16 20:45:34 +10:00
Sam Heinz
9d57466472 [TB] fix: download urls still not returning filename via HEAD. 2025-03-16 20:45:34 +10:00
Sam Heinz
fd7bc529c8 [TB] fix: move MoveHashDirectoryUpTB to TorBoxTorrentClient 2025-03-16 20:45:34 +10:00
Sam Heinz
bf023b333b [TB] fix: run MoveHashDirectoryUpTB only once
Signed-off-by: Sam Heinz <54530346+asylumexp@users.noreply.github.com>
2025-03-16 20:45:30 +10:00
Sam Heinz
7019c79814
[TB] fix: CLR type names in UnpackClient
Co-authored-by: Cucumberrbob <128094686+Cucumberrbob@users.noreply.github.com>
2025-03-15 22:20:50 +10:00
Sam Heinz
6b211c6e7c [TB] fix: getting provider in UnpackClient
Co-Authored-By: Cucumberrbob <128094686+Cucumberrbob@users.noreply.github.com>
2025-03-15 22:19:58 +10:00
Sam Heinz
9525498671 [TB] fix: get files from torrent name folder in TB zip 2025-03-13 00:35:42 +10:00
Sam Heinz
edf443adfc [TB] fix: move files from hash folder in TB zips to root download dir 2025-03-13 00:24:43 +10:00
Sam Heinz
10176357e4 [TB] fix: unpacking zip downloads
may have unintended consequences by fixing this as it may now unpack zips in individual file downloads that aren't intended to be unpacked?
2025-03-12 23:05:19 +10:00
Sam Heinz
ea9c90552b [TB] fix: timeout to use settings timeout val 2025-03-12 22:50:18 +10:00
Sam Heinz
087653e5b6 [TB] fix: calling fileFilter twice in getdllinks 2025-03-12 22:27:48 +10:00
Sam Heinz
85a0e7b04f [TB] fix nit about bool -> var 2025-03-12 22:18:15 +10:00
Sam Heinz
dc9c7ff050 [TB] Add downloading via zip functionality 2025-03-12 18:19:24 +10:00
Cucumberrbob
469f1dbbad
Merge pull request #748 from rogerfar/Cucumberrbob-fix-typo-bug-report-template
Fix: typo in bug report issue template
2025-03-11 20:44:38 +00:00
Cucumberrbob
b6cd6ceddd
Fix typo in bug report issue template 2025-03-11 20:44:11 +00:00
Cucumberrbob
f68911e9c2
Add UpdateChecker.IsInsecure to check if running version is insecure
Uses a hard-coded list of GHSA ids of known fixed vulnerabilities
2025-03-11 20:38:02 +00:00
Cucumberrbob
b333c738be
Add DisableUpdateNotifications setting
Lets the user turn notifications off completely
2025-03-11 20:34:52 +00:00
Cucumberrbob
d6efff2fb5
add rest of xmldoc for DownloadInfos 2025-03-11 12:44:48 +00:00
Cucumberrbob
c50afe9e64
[TB] GetDownloadLinks -> GetDownloadInfos 2025-03-11 12:14:15 +00:00
Cucumberrbob
0cc15d26c3
[RD] GetDownloadLinks -> GetDownloadInfos 2025-03-11 12:14:09 +00:00
Cucumberrbob
7615883fdb
[PM] GetDownloadLinks -> GetDownloadInfos 2025-03-11 12:14:04 +00:00
Cucumberrbob
2b2c5462f1
[DL] GetDownloadLinks -> GetDownloadInfos 2025-03-11 12:13:44 +00:00
Cucumberrbob
88af15b3bc
[AD] GetDownloadLinks -> GetDownloadInfos
Also makes the tests work wiith this new method
2025-03-11 12:13:24 +00:00
Cucumberrbob
e776236270
Use richer GetDownloadInfos instead of GetDownloadLinks
*Most* debrid providers know the filename as soon as they know the restricted links. By allowing them to tell the program this at an earlier stage, we don't need the `GetFileName` call for most debrid providers
2025-03-11 12:12:16 +00:00
Cucumberrbob
983e357a01
fix: require auth to setup provider 2025-03-09 14:50:51 +00:00
Cucumberrbob
d4ba114772
fix: typo - proxyserver setting applies to bezzad not internal 2025-03-09 12:45:51 +00:00
Cucumberrbob
f21fd6d8f3
Add dotnet test GitHub Action 2025-03-08 13:46:41 +00:00
Cucumberrbob
2ae3b85dac
[RD] mark torrent failed when all files excluded by filters 2025-03-08 13:46:07 +00:00
Cucumberrbob
097ea93410
[AD] mark torrent failed when all files excluded by filters 2025-03-08 13:46:02 +00:00
Cucumberrbob
f8587d1bc8
fixup! include version when setting torbox api client useragent 2025-03-07 22:40:02 +00:00
Cucumberrbob
818d9f06b2
include version when setting torbox api client useragent 2025-03-07 22:26:18 +00:00
Roger Versluis
a366328472 Fixed docker angular build.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2025-03-07 13:48:14 -07:00
Roger Far
193b195218 Fixed Angular build.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2025-03-07 13:28:47 -07:00
Roger Far
207767dbce Fixed Angular build.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2025-03-07 12:39:15 -07:00
Roger Far
309a91ace2 no message
Some checks failed
Docker Image CI / build (push) Has been cancelled
2025-03-07 12:33:00 -07:00
Roger Far
794eec8666 Fixed vulnerability issue where the auth Update endpoint wasn't protected. 2025-03-07 12:29:33 -07:00
Roger Far
5c987a9057
Merge pull request #732 from Cucumberrbob/feat/reduce-torbox-api-load
feat:  [TB] Set user-agent
2025-03-07 12:22:06 -07:00
Roger Far
13bb353644
Merge pull request #729 from Cucumberrbob/refactor/download-filtering
refactor!: DRY Download Filtering
2025-03-07 12:21:32 -07:00
Roger Far
861854b4f1
Merge pull request #725 from Cucumberrbob/deps/angular-19-update
Update to Angular 19, replace `@angular/flex-layout` with css
2025-03-07 12:18:55 -07:00
Roger Far
680904cb94
Merge pull request #721 from asylumexp/update-torboxnet
[TB] Update TorBox.NET to 1.5.0
2025-03-07 12:18:17 -07:00
Roger Far
e9becefb1c
Merge pull request #720 from Cucumberrbob/tests/use-theory-data
Use `TheoryData<>` instead of `Object[]` for theory data
2025-03-07 12:18:07 -07:00
Cucumberrbob
1034f342ed
Update torrent state to error when all files excluded by filters.
Only affects PM, TB and DL
2025-03-06 19:57:10 +00:00
Cucumberrbob
9cf28d8274
Revert "Use 10 second interval for TB ProviderUpdater"
This reverts commit 1abaeb598e.
2025-03-06 16:16:42 +00:00
Cucumberrbob
1abaeb598e
Use 10 second interval for TB ProviderUpdater 2025-03-06 13:25:14 +00:00
Cucumberrbob
0fd3f2a467
Set user agent for TB 2025-03-06 13:20:59 +00:00
Cucumberrbob
706be01a40
rename private methods of DownloadableFileFilter 2025-03-03 12:44:15 +00:00
Cucumberrbob
d40e686845
Remove logic in DLTorrentClient erroneously copied from RDTorrentClient
This code handled the RD-specific behaviour of choosing which files to download, and only getting those links from the API, rather than what DL does: downloading all files and having the api consumer pick which links they want to download.
2025-03-03 12:22:02 +00:00
Cucumberrbob
765a5b0543
Use DownloadableFileFilter in RealDebridTorrentClient to filter files 2025-03-03 12:04:34 +00:00
Cucumberrbob
2b6e51b5cf
Use DownloadableFileFilter in PremiumizeTorrentClient to filter files 2025-03-03 12:03:00 +00:00
Cucumberrbob
537845ea80
Use DownloadableFileFilter in DebridLinkTorrentClient to filter files.
Uses `TorrentClientTorrent.Files` instead of `.Links` to get download links. Also adds `DownloadLink` to `Files` when `Map`ping to `TorrentClientTorrent`
2025-03-03 12:00:33 +00:00
Cucumberrbob
2944101657
Use DownloadableFileFilter in AllDebridTorrentClient to filter files
Also updates unit tests to not test if filter works. Test filter is used appropriately instead.
2025-03-03 11:57:43 +00:00
Cucumberrbob
816c72f418
Use DownloadableFileFilter in TorBoxTorrentClient to filter files 2025-03-03 11:55:26 +00:00
Cucumberrbob
a02fcc6c1e
Inject DownloadableFileFilter in Service.DiConfig 2025-03-03 11:54:01 +00:00
Cucumberrbob
b95dbcc824
Add DownloadableFileFilter + unit tests
`DownloadableFileFilter` is an abstraction of the filtering we do in most of the `TorrentClient`s.
It operates on a single file at a time, not the entire torrent.
2025-03-03 11:52:19 +00:00
Cucumberrbob
7d8e6eb788
🤦 revert changes to angular-proxy.json 2025-02-28 13:21:35 +00:00
Cucumberrbob
0dc9d687b9
Uninstall flex layout 2025-02-28 13:10:56 +00:00
Cucumberrbob
8d7552609b
replace @angular/flex-layout in torrent-table.component
Use `class="flex-container"` as in `torrent.component`
2025-02-28 13:06:41 +00:00
Cucumberrbob
d11900a127
replace @angular/flex-layout in torrent-component
use `flex-container` class instead of `fxLayout` and set `style="flex: 1 1 0" to replicate `fxFlex`
2025-02-28 13:04:28 +00:00
Cucumberrbob
4a5be33247
remove @angular/flex-layout from add-torrent-component - doesn't appear to change the layout at any vw 2025-02-28 12:45:51 +00:00
Cucumberrbob
91ee1f8462
use @forward not @import for importing styles 2025-02-28 12:38:20 +00:00
Cucumberrbob
e06c1efb6a
angular 18 -> 19 + migrations 2025-02-28 12:28:21 +00:00
Cucumberrbob
b4f950bc7c
angular 17 -> 18 + migrations 2025-02-28 12:13:55 +00:00
Cucumberrbob
49e3155f9c
update ngx-filesize 2025-02-28 12:10:26 +00:00
Cucumberrbob
2846be3a33
angular 16 -> 17 2025-02-28 12:09:43 +00:00
Sam Heinz
6b7e9b2943
[TB] Update TorBox.NET to 1.5.0 2025-02-24 08:43:50 +10:00
Cucumberrbob
d683c45dbd
tests: use theorydata 2025-02-23 18:54:31 +00:00
Roger Far
85d6b820f3 Upgrade packages, fixed few formatting issues. 2025-02-23 11:42:38 -07:00
Roger Far
3fc44163a9
Merge pull request #719 from Cucumberrbob/chore/run-prettier
Run prettier
2025-02-23 11:27:07 -07:00
Roger Far
7436e2d036
Merge pull request #715 from Cucumberrbob/fix/use-getsymlinkpath-method
Actually use `GetSymlinkPath` logic
2025-02-23 11:24:23 -07:00
Roger Far
115a1dcc56
Merge pull request #709 from Wald764/pr-main-repo
⬆️ DebridLinkFr.NET to 1.0.4
2025-02-23 11:23:58 -07:00
Roger Far
a4b95923b2
Merge pull request #708 from Cucumberrbob/fix/pm-nested-files
[PM] fix: search subdirectories using `item.Id` not `item.FolderId`
2025-02-23 11:23:38 -07:00
Roger Far
f0310e2b00
Merge pull request #707 from Cucumberrbob/fix/existing-file-in-error-processed-watch-path
Delete file if exists when copying to `Error Path` or `Processed Path` (watch folder)
2025-02-23 11:23:07 -07:00
Roger Far
ee62bc3098
Merge pull request #691 from Cucumberrbob/feat/unit-tests
feat: Add unit tests
2025-02-23 11:22:20 -07:00
Cucumberrbob
206d3c56cb
Remove unused import 2025-02-23 17:29:07 +00:00
Cucumberrbob
0ec9a2bbbe
Run npm run prettier 2025-02-23 17:28:26 +00:00
Cucumberrbob
d292fbaee8
remove superfluous newline 2025-02-20 14:59:09 +00:00
Cucumberrbob
8e1f61472a
remove commented out line 2025-02-20 14:58:37 +00:00
Cucumberrbob
439b266946
Actually use GetSymlinkPath 2025-02-20 14:57:44 +00:00
Wald764
66f0ae0e7c ⬆️ DebridLinkFr.NET to 1.0.4 2025-02-18 18:38:07 +01:00
Cucumberrbob
5a78125053
[PM] fix: search subdirectories using item.Id as folderId when getting downloaod links 2025-02-18 11:51:30 +00:00
Cucumberrbob
29914138aa
cleanup 2025-02-17 23:31:13 +00:00
Cucumberrbob
c43be84507
Delete file if exists when copying to {error,processed}StorePath (watch folder) 2025-02-17 23:03:38 +00:00
Cucumberrbob
1d15b0d651
Add tests for AllDebridTorrentClient
Tests:
- `GetDownloadLinks`
  - `IncludeRegex` filter works
  - `ExcludeRegex` filter works
  - `DownloadMinSize` filter works
  - only `IncludeRegex` is used when both `IncludeRegex` and `ExcludeRegex` are set
  - gets links for all files if none of the above filters are set
  - returns null if `torrent.RdId` is null
- `GetTorrents`
  - [Both full/partial sync] returns empty list when no torrents present in api response
  - [full sync] overrides any previous torrents in cache, deleting those not present in the api response
  - [partial sync] updates torrents in cache according to the api response
  - [partial sync] adds torrents to cache according to the api response
  - `Map` (private so tested via `GetTorrents`)
    - Computes progress correctly from `Downloaded / Size`
  - `UpdateData`
    - [`torrentClientTorrent != null`] does not get from api, updates the `torrent` accordingly
    - [`torrentClientTorrent == null`] gets from api, updates the `torrent` accordingly
    - [`torrentClientTorrent == null`] when torrent deleted from rd, updates `RdStatusRaw = "deleted""`
    - when `torrent.RdId == null`, returns the `torrent` unmodified
2025-02-17 22:04:18 +00:00
Cucumberrbob
e0264a6e29
Add tests for DownloadHelper (GetDownloadPath)
Tests:
- [Both overloads] uses the `download.Link` to guess the filenamem if it's not present
- [Both overloads] returns null if `download.Link` is null
- [Both overloads] returns nuyll if `download.RdName` is null
- [Both overloads] works for a file in a deeply nested subdirectory
- [overload with `downloadPath` argument only] creates the correct directory
2025-02-17 22:01:53 +00:00
Cucumberrbob
95ef1f42b8
Add tests for DownloadStationDownloader
Tests:
- Downloads successfully when everything goes right first time
- Downloads successfully when the 5th api call is successful after 4 failures
- Fails to download when 5 api calls fail in a row
- Fails to download if the file has already been added to `Download Station` (when `GetInfoAsync` does not error)
- FAils to download if `remotePath` cannot be parsed by `Path.GetDirectoryName()`
2025-02-17 22:01:16 +00:00
Cucumberrbob
f4ca137e83
Add tests for Torrents#RunTorrentComplete
Tests:
- doesn't run command when not present
- does run command when present
- listens to events on process for stdout/stderr
2025-02-17 22:00:57 +00:00
Cucumberrbob
3dee900be6
Add test project RdtClient.Service.Test 2025-02-17 22:00:33 +00:00
Cucumberrbob
2bbbf0fd10
Use ISynologyClient (mockable) and inject IDelayProvider so SynologyTorrentClient is testable 2025-02-17 22:00:19 +00:00
Cucumberrbob
c546bd0173
Use IProcessFactory, IFileSystem in Service.Torrents#RunTorrentComplete
Also adds dependency injection to `Torrents#RunTorrentComplete` for `DbSettings` (`Settings.Get`)
2025-02-17 21:59:56 +00:00
Cucumberrbob
fd073b4762
Add interfaces for TorrentData, Downloads, Process, ProcessFactory
Making them interfaces lets us dependency inject them, allowing a given class to be tested.
2025-02-17 21:57:29 +00:00
Cucumberrbob
daf8a9c6da
DownloadHelper: use IFileSystem for testable filesystem access 2025-02-17 21:57:02 +00:00
Cucumberrbob
95307f2933
Use AllDebridNetClientFactory so AD client can be dependency-injected 2025-02-17 21:56:32 +00:00
Roger Far
fe241db5db Upgrade AllDebrid and Downloader.NET to the latest versions.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2025-02-16 09:44:21 -07:00
Roger Far
7c3c1fb24c
Merge pull request #692 from Cucumberrbob/fix/include-exclude-pm-tb
[PM] [TB] Fix: Implement `IncludeRegex`, `ExcludeRegex`, and `DownloadMinSize`
2025-02-16 09:30:19 -07:00
Cucumberrbob
9299314b14
Log when rejecting files in TB GetDownloadLinks 2025-02-13 11:50:40 +00:00
Cucumberrbob
e5e64e6946
Include torrent in logs when rejecting files in PM GetDownloadLinks 2025-02-13 11:41:41 +00:00
Roger Far
1ac325e8c9
Merge pull request #689 from Cucumberrbob/docs/downloadstation-initial
Update `readme` to include Synology Download Station.
2025-02-12 20:35:34 -07:00
Roger Far
3b8f14cc1a
Merge pull request #688 from Cucumberrbob/docs/torbox-initial
Update readme to include `TorBox` in debrid services
2025-02-12 20:35:17 -07:00
Roger Far
311156f0aa
Merge branch 'master' into docs/torbox-initial 2025-02-12 20:35:06 -07:00
Roger Far
f8505edf7f no message 2025-02-12 20:34:24 -07:00
Roger Far
09d929db17
Merge branch 'master' into docs/torbox-initial 2025-02-12 20:32:32 -07:00
Roger Far
f57ee1e53d
Merge pull request #694 from asylumexp/patch-2
[TB] Update TorBox.NET to v1.4.0
2025-02-12 20:27:43 -07:00
Roger Far
56c25770c3 Cleanup. 2025-02-12 20:27:29 -07:00
Roger Far
7b4facd46f
Merge pull request #680 from Wald764/pr-main-repo
🎉 add support of DebridLinkFr
2025-02-12 20:25:02 -07:00
Roger Far
c005bda484
Merge branch 'master' into pr-main-repo 2025-02-12 20:24:54 -07:00
Roger Far
f58b7d3e72 Refactored TorrentClientKind to Provider. Upgrade Downloader.NET 2025-02-12 20:24:22 -07:00
Roger Far
780d0e5118 Add fixes for AllDebrid and their new API. 2025-02-12 19:48:48 -07:00
Sam Heinz
82b44cccde
[TB] Update TorBox.NET to v1.4.0 2025-02-08 16:40:57 +10:00
Cucumberrbob
26f1f26cd1
Ensure MinDownloadSize, IncludeRegex and ExcludeRegex are honoured by TorBoxTorrentClient 2025-02-07 14:43:59 +00:00
Cucumberrbob
9e223de32e
Ensure MinDownloadSize, IncludeRegex and ExcludeRegex are honoured by PremiumizeTorrentClient 2025-02-07 14:40:13 +00:00
Wald764
f1bdc7d8b4 ♻️ fix a reference to RD instead of DL in DebridLinkTorrentClient 2025-02-03 00:49:36 +01:00
Cucumberrbob
48766e3d44 🐛 Fix build errors by not using Torrent = in DebridLinkTorrentClient
use `Data.Models.Data.Torrent`instead of `Torrent` and `Download = RdtClient.Data.Models.Torrent`
2025-02-03 00:25:02 +01:00
Cucumberrbob
56a61ad012 Use exact path for symlink source when using SymlinkDownloader 2025-02-02 22:11:26 +01:00
Wald764
303244127b ♻️ fix typo in Torrents.cs 2025-02-02 17:50:51 +01:00
Wald764
f2207be000 📝 update readme to include DebridLink in debrid services 2025-02-02 17:35:04 +01:00
Wald764
20fbf8833a ♻️ rename occurrences of debridlinkfr to debridlink 2025-02-02 17:19:41 +01:00
Wald764
d6d1813340 🐛 implement PR suggestions 2025-02-02 17:10:41 +01:00
Wald764
7902f56d66 🐛 endpoint /seedbox/cached removed by DebridLink 2025-02-02 16:46:35 +01:00
Wald764
4198b6ab55 🐛 fixes about the providers order and addition of GetFileName to DebridLinkFrTorrentClient 2025-02-02 16:26:46 +01:00
Cucumberrbob
a9cd30364e
Update readme to include Synology Download Station. Descriptions for configuration options taken from 047326a by YvesPa 2025-02-02 15:06:27 +00:00
Cucumberrbob
ce14a1d62e
Update readme to include TorBox in debrid services 2025-02-02 14:52:29 +00:00
Wald764
59c47bfe1a 🎉 add support of DebridLinkFr 2025-01-31 10:07:29 +01:00
Roger Far
dc2ec486d7 no message
Some checks failed
Docker Image CI / build (push) Has been cancelled
2025-01-29 19:13:43 -07:00
Roger Far
a566f47988 Set the download client based on the torrent instead of hardcoded when upload a torrent from the web GUI. 2025-01-29 19:10:14 -07:00
Roger Far
24c759e4e6 Small fixes in the download client. 2025-01-29 19:03:38 -07:00
Roger Far
809903eebb
Merge pull request #679 from Cucumberrbob/fix/bad-download-path
[Fix] Revert changes to `DownloadHelper`, move AD-symlink specific fixes to `AllDebridTorrentClient.GetSymlinkPath`
2025-01-29 18:59:38 -07:00
Roger Far
ef8142453f
Merge branch 'master' into fix/bad-download-path 2025-01-29 18:59:31 -07:00
Roger Far
0a705e6720
Merge pull request #678 from YvesPa/AddDate
Proposition : Add request date on table for sorting
2025-01-29 18:55:55 -07:00
Roger Far
f379d4efb8 DownloadStation cleanup. 2025-01-29 18:55:24 -07:00
Roger Far
881c74f2b7
Merge pull request #670 from YvesPa/master
Add Synology Download Station Support
2025-01-29 18:51:16 -07:00
Cucumberrbob
0c9de86531
rename symlinkPath -> symlinkSourcePath 2025-01-29 12:19:52 +00:00
Cucumberrbob
82f0bb8bbe
Use static GetSymlinkPath on AllDebridTorrentClient to get the symlink (since the behaviour is unique to the debrid service) 2025-01-29 12:11:25 +00:00
Cucumberrbob
beb0299a0b
revert changes to DownloadHelper.cs 2025-01-29 11:07:20 +00:00
YvesPa
1e749a9bfe Add request date on table for sorting 2025-01-28 15:08:32 +01:00
YvesPa
062787143f Throw an lisible error if not config 2025-01-28 14:12:37 +01:00
YvesPa
eeef6fb7fe Merge remote-tracking branch 'rogerfar/master'
# Conflicts:
#	server/RdtClient.Service/RdtClient.Service.csproj
#	server/RdtClient.Service/Services/DownloadClient.cs
2025-01-22 19:11:23 +01:00
YvesPa
85e07f3c90 Update to nuget Synology project 2025-01-22 17:42:04 +01:00
Roger Far
db1760b36e Update to .95.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2025-01-19 20:47:09 -07:00
Roger Far
ce223d3e2c Add qBittorrent endpoint transfer/info. 2025-01-19 20:43:28 -07:00
Roger Far
3ed80909de Upgrade packages. 2025-01-19 19:58:29 -07:00
Roger Far
b8c183c079 Refactored out merge pattern for more readable syntax. 2025-01-19 19:57:39 -07:00
Roger Far
e449bd7928 Refactored out goto statement. 2025-01-19 19:55:19 -07:00
Roger Far
353b9ca07b
Merge pull request #652 from Sculas/fix/path-single-file-torrent
fix: Improve path fixes for Alldebrid & only apply them to Alldebrid
2025-01-19 19:51:01 -07:00
Roger Far
b33ecd6b1c
Merge pull request #653 from Cucumberrbob/master
Use local files when building docker image
2025-01-19 19:49:27 -07:00
YvesPa
735912da5d Change log position 2025-01-10 16:45:50 +01:00
YvesPa
7d2d7fa35f Merge remote-tracking branch 'rogerfar/master' 2025-01-10 16:27:04 +01:00
Sculas
00e499593e
fix: Fix Alldebrid path again 2025-01-09 16:23:56 +01:00
Sculas
d6305c26f8
fix: Only add path separator in specific cases 2025-01-08 13:41:47 +01:00
Sculas
56934e3183
refactor: Improve logging 2025-01-07 15:15:31 +01:00
Sculas
38a3bc1537
perf(SymlinkDownloader): Skip file search when using Alldebrid 2025-01-07 14:34:02 +01:00
Sculas
8fe277fa52
fix(SymlinkDownloader): Add search path to potential file paths 2025-01-07 14:26:26 +01:00
Cucumberrbob
7b97ab8cd2
Use local files when building docker image instead of cloning repo from github 2025-01-06 18:53:26 +00:00
Sculas
ee9e80f7d2
fix: Only apply AllDebrid-specific path fixes to AllDebrid 2025-01-06 12:41:41 +01:00
Roger Far
d5ee5d209c Changed how files are saved. 2025-01-05 20:56:37 -07:00
Roger Far
d43774d9b5 Fix hanging Aria2 downloads when files are being copied or cannot be found on the host. 2025-01-05 20:49:55 -07:00
Roger Far
e700939cfa
Merge pull request #651 from Sculas/fix/path-single-file-torrent
fix: Fix invalid download path with single-file torrents
2025-01-05 15:21:05 -07:00
Sculas
268bb6826c
fix: Fix invalid download path with single-file torrents 2025-01-05 22:17:22 +01:00
Roger Far
79164d54ca no message
Some checks failed
Docker Image CI / build (push) Has been cancelled
2025-01-05 11:03:18 -07:00
Roger Far
c958b4960b
Merge pull request #650 from Sculas/fix/ad-filters
fix(AllDebrid): Implement regex filters & preserve folder structure
2025-01-05 11:01:59 -07:00
Sculas
0026541dca
fix(AllDebrid): Implement filters & fix file paths 2025-01-04 04:29:26 +01:00
Roger Versluis
8518cc1e10 no message
Some checks failed
Docker Image CI / build (push) Has been cancelled
2025-01-03 15:54:55 -07:00
Roger Versluis
34aec3d45d no message 2025-01-03 15:54:43 -07:00
Roger Far
8205e0e652
Merge pull request #645 from asylumexp/fix/torbox-availability
[TB] Fix Index was out of range error #644
2025-01-03 15:53:07 -07:00
Sam Heinz
409e47920e Revert "[TB] Download via zip if file count is over 50"
This reverts commit 6afef14855.
2025-01-02 21:21:52 +10:00
Sam Heinz
fa0f535858 [TB] Update TorBox.NET to 1.3.2 2025-01-02 13:15:59 +10:00
Sam Heinz
065afe9461 [TB] Set seeding based on website seeding setting
The default seed value for website dash is used to set whether to seed or not for new uncached torrents added via rdtclient
2025-01-02 13:15:42 +10:00
Sam Heinz
6afef14855 [TB] Download via zip if file count is over 50
Due to rate limiting issues this is implemented again, it works now since TB reenabled HEAD method for zip downloads on their backend
2025-01-02 13:10:36 +10:00
Sam Heinz
78daeeeaf3 [TB] Update TorBox.NET to 1.3.0 2024-12-28 16:52:45 +10:00
Sam Heinz
e21dc7279a [TB] Fix Index was out of range error
put < when shouldve put >
2024-12-28 01:36:21 +10:00
Roger Versluis
9a2d59afe0 no message
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-12-18 07:23:44 -07:00
Roger Far
f5c528645d
Merge pull request #637 from asylumexp/patch-1
[TB] Fix source parameter null log spam #618
2024-12-18 07:19:04 -07:00
Sam Heinz
1ad3dc51dd
[TB] Fix source parameter null log spam #618 2024-12-12 17:46:46 +10:00
Roger Far
ea364eefc5 Cleanup, bump to 91.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-12-11 20:15:25 -07:00
Roger Far
98c4b2ae9b Merge branch 'master' of https://github.com/rogerfar/rdt-client 2024-12-11 20:13:46 -07:00
Roger Far
1a27be8309 Update packages. 2024-12-11 20:13:42 -07:00
Roger Far
adc976067d
Merge pull request #636 from asylumexp/fix/torbox-filenames
Fix for TorBox incorrect filenames
2024-12-11 20:11:31 -07:00
Sam Heinz
e92b197ddb [TB] Fix UI errors 2024-12-11 18:59:21 +10:00
Sam Heinz
ac6300a504 [TB] Replace null with empty string for filenames in TorrentClient
Not sure if its better to do this or allow null value to db, but i had this originally and it worked so i think im better off leaving it for now, but idk.

Also got fix for incorrect log
2024-12-11 00:03:43 +10:00
Sam Heinz
9d03c20aaa [TB] Change DownloadHelper to use download.FileName
Also adds the UpdateFileName function to DownloadHelper & DownloadData to push FileName to DB
2024-12-10 23:47:55 +10:00
Sam Heinz
350185b702 [TB] Refactor GetFileName in Torrent Clients 2024-12-10 23:45:13 +10:00
Sam Heinz
a1599ee716 [TB] Add FileName value to Download model 2024-12-10 23:44:11 +10:00
YvesPa
f9335be163 Merge remote-tracking branch 'rogerfar/master' 2024-12-10 11:12:55 +01:00
YvesPa
346982fe8d Manage error 2024-12-10 11:05:39 +01:00
Sam Heinz
3364deecbb [TB] Add download state for stalledDL 2024-12-10 15:25:45 +10:00
Sam Heinz
dffce96a45 [TB] Add default for fileName in DownloadHelper 2024-12-10 15:25:30 +10:00
Sam Heinz
c34c62f879 [TB] Move handling filenames from DownloadHelper to TorrentClients 2024-12-10 15:10:55 +10:00
Roger Far
1a9a0ddf3c Update README to include the ASP.NET Core Runtime. 2024-12-06 18:18:04 -07:00
Roger Far
4351c7541c no message
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-12-06 18:13:24 -07:00
Roger Far
a91ea71dcb no message 2024-12-06 18:13:06 -07:00
Roger Far
165d83361b Removed InstantAvailability endpoint from AllDebrid. 2024-12-06 18:11:24 -07:00
Roger Far
a335ce2bdd
Merge pull request #625 from asylumexp/fix/torbox-MethodNotAllowed
[TorBox] Fix MethodNotAllowed, source parameter null, preventing download of files
2024-12-01 08:51:15 -07:00
Sam Heinz
99c30bbc4f [TB] Fix source parameter null error 2024-11-27 08:19:40 +10:00
Sam Heinz
8b16bd8ee2 [TB] Download files individually rather than via zip 2024-11-27 05:10:24 +10:00
Roger Far
8bd83dffbf no message
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-11-24 06:06:28 -07:00
Roger Far
116ca29131 Ignore the "Only download available files on debrid provider" for real-debrid and disable the instant availability completely for Real Debrid. 2024-11-24 06:05:23 -07:00
Roger Far
d932089b90 Catch disabled instant availability endpoint from Real Debrid.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-11-24 05:45:22 -07:00
Roger Versluis
f33380e77b Changed dockerfile to use .NET 9
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-11-18 03:58:53 -07:00
Roger Versluis
ee5b5f8dc3 Upgrade to .NET 9, code cleanup. 2024-11-18 03:39:12 -07:00
Roger Far
7573492bcf
Merge pull request #585 from asylumexp/master
TorBox support
2024-11-18 03:28:51 -07:00
Roger Far
c9c4b4550b
Merge pull request #600 from Ruakij/fix/qbittorrent-api-login-on-authenticationtype-none
Accept login via qbittorrent-api when AuthType is none
2024-11-18 03:25:08 -07:00
Roger Far
154b6187e7
Merge pull request #587 from GabbeHags/active_download_clients
download limit now takes in to consideration total active downloads
2024-11-18 03:24:40 -07:00
Sam Heinz
b12f6812cf update torbox.net to 1.2.1 2024-11-12 15:16:03 +10:00
Sam Heinz
ec4e160288 disable seeding
Would rather have seeding disabled and Torbox support be merged then fix it in December when I have time again.
2024-11-12 14:29:47 +10:00
Sam Heinz
b78f0cdd95
Remove zipped 2024-11-11 17:59:00 +10:00
Ruakij
3bae4501a5
Accept login via qbittorrent-api when AuthType is none 2024-10-27 18:02:32 +01:00
YvesPa
5e52e2dd8c Use local Synology for test 2024-10-17 14:32:53 +02:00
YvesPa
c562e75a97 correct Path with category 2024-10-15 21:48:48 +02:00
YvesPa
d15b1a2c01 some changes for tests 2024-10-15 18:27:11 +02:00
YvesPa
c6beedf675 Correct RealDebrid renaming sometimes 2024-09-25 18:07:34 +02:00
YvesPa
fcbd9d8096 Correct Path using qbittorrent 2024-09-25 14:51:37 +02:00
Sam Heinz
9104197000 Update TorBox.NET to v1.1.2
fixes GetInfoAsync rename to GetHashInfoAsync
2024-09-25 14:53:54 +10:00
YvesPa
047326a30f Add Synology Download Station 2024-09-23 12:51:04 +02:00
GabbeHags
df084bcebe download limit now takes in to consideration total active downloads 2024-09-15 21:04:55 +02:00
Sam Heinz
194b9fd2a3 Update TorBox.NET to 1.0.1 2024-09-13 16:00:03 +10:00
Sam Heinz
446b1c0180 Fix error from merge from upstream, fix error on processing 2024-09-12 19:52:03 +10:00
Sam Heinz
6bac21b426 Update for TorBoxNET v1.0.0.42 changes 2024-09-09 22:27:51 +10:00
Sam Heinz
0987b1f18c Merge remote-tracking branch 'upstream/master' 2024-09-08 23:57:33 +10:00
Sam Heinz
320c52c4be Handle zipped DL only w/ file count > 100 2024-09-04 17:14:36 +10:00
Roger Far
35f959ee26 Fixed the DownloadHelper creating ghost folders. Also don't delete the file when starting a symlink download.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-09-03 19:59:21 -06:00
Roger Far
96bc3308f6 Base path fix. 2024-09-03 19:22:53 -06:00
Roger Far
8266e53435 no message
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-09-03 19:15:31 -06:00
Roger Far
b66b864cae Revert "Prevent creation of download folders when using the symlink downloader."
This reverts commit 078bbe4bfb.
2024-09-03 19:14:36 -06:00
Roger Far
b1f93d1892 Replace docker libssl1.1 with libssl3
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-09-02 21:18:26 -06:00
Roger Far
af87a1e6e6 no message
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-09-02 21:12:30 -06:00
Roger Far
078bbe4bfb Prevent creation of download folders when using the symlink downloader. 2024-09-02 21:08:00 -06:00
Roger Far
cc52f5f7f5 Fixed download progress reporting to the qBittorrent API. 2024-09-02 21:07:46 -06:00
Roger Far
fae983237a Merge branch 'master' of https://github.com/rogerfar/rdt-client 2024-09-02 19:16:23 -06:00
Roger Far
f65e3fb405 Upgrade packages and build with Alpine 3.20. 2024-09-02 19:16:01 -06:00
Roger Far
227c8efeaf
Merge pull request #560 from mitchellkelly/master
Fixed issue moving downloaded files in the base directory
2024-09-02 18:58:29 -06:00
Roger Far
4a5c9a1169
Merge pull request #556 from GabbeHags/master
Update README.md with more steps for windows service
2024-09-02 18:58:02 -06:00
Roger Far
78df45a159
Merge pull request #555 from DrFrankensteinUK/master
Update README.md
2024-09-02 18:57:34 -06:00
Sam Heinz
70584d1acb Add DB values for seeding and zipped, pass to addtorrent 2024-09-01 20:14:07 +10:00
Sam Heinz
91dcdb9d7c Fix importing from TorBox, adding queued torrents 2024-09-01 16:25:36 +10:00
Sam Heinz
e20dd84e1d Fix adding torrent via file
it was a reference conflict between System.Io.File and TorBoxNet.File, but committing anyways to make sure i don't forget that i fixed it
2024-08-31 19:27:20 +10:00
Sam Heinz
e60059a5bd Fix deleting torrents from provider 2024-08-31 10:17:27 +10:00
Sam Heinz
183798db2d Fix TorBox GetAvailability
Won't display anything in the UI though as its set to only show available files for RD
2024-08-30 17:26:53 +10:00
Sam Heinz
754588091d Fix seeding 2024-08-29 07:16:54 +10:00
Sam Heinz
c34f91fc27 Add UI elements for TorBox, fix downloads. 2024-08-28 17:05:56 +10:00
Sam Heinz
395f83847e Add Getting DL link, tracking via hash 2024-08-27 19:11:24 +10:00
Sam Heinz
fc9ea4c29a Implement queudtorrents, selecting files, hash rather than id 2024-08-27 11:06:33 +10:00
Mitchell Kelly
6d4440de20 Fixed issue moving downloaded files in the base directory 2024-08-04 20:07:54 -04:00
GabbeHags
bb354b6a36
Update README.md with more steps for windows service 2024-08-03 01:53:29 +02:00
DrFrankensteinUK
c1e165c14c
Update README.md
Amended the docker section to point at the dedicated read me file as the information on the front page does not have the same detailed information or compose information.
2024-08-01 17:55:36 +01:00
Roger Far
02113a43dd Push to 81.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-07-28 21:44:46 -06:00
Roger Far
788fe654be Fixed deserialization issue in Real-Debrid provider. 2024-07-28 21:43:17 -06:00
Roger Far
7a8a0946d1 Force update torrent data when no filename is set. 2024-07-28 21:42:08 -06:00
Roger Far
a356632d50 Upgrade to MonoTorrent 3.
Catch infringed files from Real-Debrid and return a Failed. on qBittorrent.
2024-07-28 21:21:09 -06:00
Sam Heinz
772631d47f partially complete about 1/4 of the TorBoxClient
Up to available files
2024-07-18 20:14:35 +10:00
Roger Far
258a4ae34b no message
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-07-13 19:59:00 -06:00
Roger Far
8dd0eec654 Add retry handler with polly to intercept 429 rate limit errors with Real-Debrid. 2024-07-13 19:54:36 -06:00
Roger Far
343d2d078c Merge branch 'master' of https://github.com/rogerfar/rdt-client 2024-07-13 19:40:50 -06:00
Roger Far
617ae547cf
Merge pull request #537 from iPromKnight/feature/rd_rate_limit
Rate limit RD requests through the http client
2024-07-13 19:40:40 -06:00
Roger Far
2ec3a3b5ca Prevent spamming Real-Debrid when a torrent is completed, do more efficient periodic updates. Fetch more torrents in 1 go to prevent too many endpoint requests. 2024-07-13 19:40:33 -06:00
iPromKnight
297816d671
Rate limit RD requests through the http client 2024-07-13 12:13:21 +01:00
Roger Versluis
86eaccd439 Add some error handling for calculating progress for qBittorrent.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-06-03 08:12:09 -06:00
Roger Far
2bf1c997d8 Aria2c fix #2.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-05-04 19:13:56 -06:00
Roger Versluis
06c034f18a Fixed download path issue with Aria2c when a category is set.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-05-03 10:26:15 -06:00
Roger Far
73823233fd no message
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-05-02 23:08:16 -06:00
Roger Far
a79e0849d6 Fixed nullable issues in the qbittorrent endpoint. 2024-05-02 23:07:33 -06:00
Roger Far
06a667918e Merge branch 'master' of https://github.com/rogerfar/rdt-client 2024-05-02 23:07:08 -06:00
Roger Far
6a984cc145 Fixed issue where the torrent runner could crash. 2024-05-02 23:02:28 -06:00
Roger Versluis
d5aaad866b Fixed broken symlink recursive searching.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-04-24 07:29:02 -06:00
Roger Far
5cf73e97af no message
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-04-20 09:48:11 -06:00
Roger Far
dbb502a164 Add recursive searching for the symlink downloader. 2024-04-20 09:47:51 -06:00
Roger Far
0bfb4974d8 Fixed another symlink path resolver issue.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-04-11 22:26:24 -06:00
Roger Far
eb21211a0c Fixed issue with download speed test when the symlink downloader is selected.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-04-10 22:00:15 -06:00
Roger Far
ed8637931d Fixed symlink path matching bug.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-04-10 21:50:01 -06:00
Roger Far
05dd562be1 Add logging for the symlink downloader.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-04-10 17:56:11 -06:00
Roger Far
8972db8938 no message
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-04-09 21:16:49 -06:00
Roger Far
ba94039b24 Add sorting on the interface. 2024-04-09 21:03:01 -06:00
Roger Far
acb57dc1ea Changed the way the base_href is resolved in the frontend, fixing the reload issue. 2024-04-09 20:41:21 -06:00
Roger Far
575ae6af5a Add middleware fix for responses that are not 200.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-04-09 20:22:23 -06:00
Roger Far
d36b9cf889 Add fix for symlink downloader.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-04-09 20:02:07 -06:00
Roger Far
367e195ed6 Symlink fixes.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-04-08 20:36:42 -06:00
Roger Far
949bd7cfd2 Increase the default internal downloader values. 2024-04-08 18:08:59 -06:00
Roger Far
22ea602fea no message
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-04-07 00:16:59 -06:00
Roger Far
d6e549bb67 Added option to configure the buffersize for the internal downloader. 2024-04-07 00:14:48 -06:00
Roger Far
c44ed2a51c Push to 2.0.64
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-04-06 16:40:44 -06:00
Roger Far
508e2f1d7f Add migration to set log level to debug. 2024-04-06 16:38:10 -06:00
Roger Far
bec2dcd20b Add internal downloader logger. 2024-04-06 16:28:03 -06:00
Roger Far
c21d578a23 Style fixes. 2024-04-06 14:58:07 -06:00
Roger Far
e5dfb0e027 Add fixes for the symlink downloader from https://github.com/Pukabyte/rdtclient . 2024-04-06 14:52:11 -06:00
Roger Far
45c805eb00 Fixed routing. 2024-04-06 13:59:56 -06:00
Roger Far
3b72463663 Added bit of logging to debug downloader issues, upgraded packages, cleaned up C# code. 2024-04-06 13:54:58 -06:00
Roger Far
74b988effa Fixed missing downloaders from the actual torrent options. 2024-04-06 13:43:13 -06:00
Roger Far
d172da80b5 Formatting fixes. 2024-04-06 13:38:50 -06:00
Roger Far
995cee66f3
Merge pull request #445 from isc30/master
qBittorrent API improvements
2024-04-06 13:38:11 -06:00
Ivan Sanz Carasa
716d89680d add stalled state in UI 2024-03-29 18:46:09 +01:00
Ivan Sanz Carasa
2ef0ddc0b7 all changes 2024-03-28 18:59:09 +01:00
Roger Far
99a29ca6a8 Change delete behaviour for the QBittorrent Delete endpoint.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-03-05 19:05:26 -07:00
Roger Far
eef7267390
Merge pull request #428 from ThaDaVos/master
Removed unwanted `VOLUME` as it has unintended side-effects
2024-03-05 18:37:20 -07:00
ThaDaVos
2f05e7f60e Removed unwanted VOLUME as it has unintended side-effects 2024-03-04 18:40:36 +00:00
Roger Far
4fb0a8d726 Fixed issue where a lot of downloads could be tried in 1 go, causing a bit of a DOS to real-debrid, fixed the status of the torrent as error incorrectly when some downloads are errored out.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-02-17 15:34:49 -07:00
Roger Far
f96ef294a7 2.0.61
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-02-12 22:29:58 -07:00
Roger Far
74423b49e9 Add some more logging to the link selection for Real-Debrid. 2024-02-12 22:26:59 -07:00
Roger Far
b0823f596d Add regex option to include or exclude files. 2024-02-12 21:39:02 -07:00
Roger Far
ed01900f69 Upgrade packages, fixed potential logging issue? 2024-02-12 19:38:00 -07:00
Roger Far
a78ff95643 Try using the file/folder structure when downloading. 2024-01-21 22:35:28 -07:00
Roger Far
71e1aae5ab Force delete from activedownloads and activeunpacks when a torrent has been deleted. 2024-01-21 22:27:15 -07:00
Roger Far
e85366f874 no message
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-01-21 17:10:47 -07:00
Roger Far
6c47ebcfad Added the previous downloader back in, keep the Bezzad downloader in as a secondary. 2024-01-21 16:48:44 -07:00
Roger Far
c95f0171f7 no message
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-01-10 18:12:02 -07:00
Roger Far
486cea1006 Add ParallelCount setting back, don't calculate chunk count but use a setting value. 2024-01-10 18:11:46 -07:00
Roger Far
54f1d23181 no message
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-01-09 19:28:23 -07:00
Roger Far
f80f041eeb Fixed symlink retrying. 2024-01-09 19:23:46 -07:00
Roger Far
a5709ae52e Fixed torrent runner retry for downloaders.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-01-08 21:52:51 -07:00
Roger Far
48097f1eb8 no message
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-01-08 19:53:24 -07:00
Roger Far
70aae02d55 Return a fake ID from the symlink downloader when the symlink is done to accomodate a retry mechanism. 2024-01-08 19:52:00 -07:00
Roger Far
de9045d3f6 Tweaked the downloader a bit to fix the memory issues. 2024-01-08 19:51:32 -07:00
Roger Far
2e03ad35f4 Merge branch 'master' of https://github.com/rogerfar/rdt-client
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-01-07 12:09:23 -07:00
Roger Far
1817bd7508 Add logging. 2024-01-07 12:09:11 -07:00
Roger Far
3b21f830f1
Update bug_report.md 2024-01-07 11:53:40 -07:00
Roger Far
d0f7c9fb37 Fixed broken setup state.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-01-05 14:33:10 -07:00
Roger Far
d99a9e00ed no message 2024-01-05 14:32:34 -07:00
Roger Far
c83982a4d4 Add setting for aria2 to accomodate a download path that is different in aria2 than in rdt-client. 2024-01-05 14:30:49 -07:00
Roger Far
47f8c587af no message
Some checks failed
Docker Image CI / build (push) Has been cancelled
2024-01-05 12:36:02 -07:00
Roger Far
3cfb3cfdbb Fixed premiumize when a torrent is in seeding state. 2024-01-05 12:10:38 -07:00
Roger Far
22ba9c7ce6 Expose the Post Torrent Download Action setting on the Provider settings. 2024-01-05 12:03:23 -07:00
Roger Far
6b0265716e no message 2024-01-05 11:58:54 -07:00
Roger Far
dfd97b2c3c Add BASE_PATH environment variable for the base path setting. 2024-01-05 11:57:30 -07:00
Roger Far
388411748a Bump to 2.0.51. 2024-01-05 11:49:18 -07:00
Roger Far
12eefa028b Run the torrent complete before deleting the torrent. 2024-01-05 11:38:39 -07:00
Roger Far
95574847e9 Fixed setup screen and some labels. Removed 100 char limit on inputs. 2024-01-05 11:37:01 -07:00
Roger Far
2b39f92106 Fixed post torrent %F parameter. 2024-01-05 11:27:52 -07:00
Roger Far
8114baa7b8 Switch the internal downloader (again). 2024-01-05 11:07:38 -07:00
Roger Far
6ad51b036e Store the torrent name instead of the hash to the blackhole directory. 2024-01-05 10:37:07 -07:00
Roger Far
d01cb71866 Add bulk change settings modal. 2024-01-05 10:28:22 -07:00
Roger Far
ba243d3e83 Set the general downloadclient when adding torrents through *rr, watchfolder. 2024-01-05 09:59:54 -07:00
Roger Far
2cea7ed292 Prevent new torrents from being removed from the debrid provider when the symlink download client is selected.
Make the downloader an option when adding a new torrent.
2024-01-05 09:52:26 -07:00
Roger Far
ba3a12e305 Store the download client on the Torrent itself when creating the download. Make sure the torrent doesn't get automatically deleted on the debrid client when the symlink downloader is used. 2024-01-05 09:42:18 -07:00
Roger Far
f4e7a9bcf7 Add setting to store added torrents and magnets to a directory. 2024-01-05 09:24:02 -07:00
Roger Far
32dfca5fb1
Merge pull request #372 from Merrick28/master
Backs up any torrent added to /data/db/blackhole directory
2024-01-05 09:17:04 -07:00
Roger Far
d1042f62f4
Merge pull request #382 from realaashil/master
Some checks failed
Docker Image CI / build (push) Has been cancelled
docker: fix docker images for arm64
2024-01-05 09:12:21 -07:00
realaashil
5b82a2102a Bump version 2023-12-23 22:23:04 +05:30
realaashil
e87d5af7ee docker: fix docker images for arm64 2023-12-23 22:11:01 +05:30
Merrick28
d4a7ef3a83 test ok 2023-12-07 15:47:11 +01:00
Merrick28
30f334088f blackhole 2023-12-07 14:58:40 +01:00
Merrick28
4837f92fa4 test1 2023-12-07 13:32:24 +01:00
Merrick28
81c735b3f1 test1 2023-12-07 13:29:18 +01:00
Roger Versluis
dc0541eabf Upgrade downloader to solve memory issue.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2023-11-24 08:50:00 -07:00
Roger Versluis
c349cd2613 no message
Some checks failed
Docker Image CI / build (push) Has been cancelled
2023-11-21 17:01:19 -07:00
Roger Versluis
1693120d7b Revert to using the package manager to install .NET 8.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2023-11-21 10:15:34 -07:00
Roger Versluis
9f4a34b20e Add missing retry count to the internal downloader.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2023-11-15 18:59:44 -07:00
Roger Versluis
cf4527b6c9 Merge branch 'master' of github.com:rogerfar/rdt-client
Some checks failed
Docker Image CI / build (push) Has been cancelled
2023-11-15 16:54:57 -07:00
Roger Versluis
562ecef75d Update internal downloader. 2023-11-15 16:54:51 -07:00
Roger Versluis
9a5d3e9935 Update internal downloader. 2023-11-15 16:53:04 -07:00
Roger Versluis
a176bf8891 Revert broken upgrade step.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2023-11-15 08:54:11 -07:00
Roger Versluis
21490ee2d5 Revert broken upgrade. 2023-11-15 08:53:23 -07:00
Roger Versluis
5089690f4e Improvements to the internal downloader.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2023-11-15 08:39:10 -07:00
Roger Versluis
ca463fa9fd Fixed docker build as .NET8 isn't published yet.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2023-11-14 15:16:14 -07:00
Roger Versluis
b8e45417b5 Update to .NET 8.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2023-11-14 14:29:32 -07:00
Roger Versluis
ee0a631cd9 Bump to 2.0.40
Some checks failed
Docker Image CI / build (push) Has been cancelled
2023-10-03 08:16:33 -06:00
Roger Versluis
7122dff9e6 Code cleanup. 2023-10-03 08:15:06 -06:00
Roger Versluis
5a8ab0789a Reverted some changes from the gaisberg PR. 2023-10-03 08:15:00 -06:00
Roger Far
d463e690c5
Merge pull request #323 from Gaisberg/symlink_downloader_concurrency
Symlink downloader concurrency
2023-10-03 08:04:24 -06:00
Roger Far
c771662629
Merge pull request #316 from JReming85/patch-1
Auto Update script for linux
2023-10-03 08:03:29 -06:00
Gaisberg
fe77391e7f Symlink downloader performance fixes
- Added concurrent downloading for symlink downloader
- Use FinishAction configuration setting for QBittorrent delete endpoint
- Add symlink downloader readme section
2023-10-02 18:20:29 +03:00
Jeremy R
3db5cd7060
Moved from root to server/RdtClient.Web/
Moved to proper location, next to windows update script
2023-09-29 06:22:07 -04:00
Jeremy R
3488d83af4
Create Update.sh
I noticed you do not have an auto update script for us linux users so I went and created one for you.

It a simple bash file that can take 3 arguments

Update.sh = Checks to see if there is an update, if yes update
Update.sh -update = Same as above
Update.sh -force = Will update regardless if its needed or not
Update.sh -check = Will check for an update, and prompt you if you want to update or not.

---

Because there was not an easy way (that I knew of) to get the current version (maybe have an API or file with the version number) It will one first attempt assume its outdated.

After the initial update, it will create a folder + file in the rdtcs root folder called .installed/version.txt and store the installed version in the text.  It will then start comparing the latest github version to whats in the file.

When it updates, it will check for the latest version using github's API and grab the version and download link from there.  Afterwards, it will then 
1. download the zip file
2. Stop the rdtc service
3. Backup the config and DB to .backup
4. Unzip the latest version
5. Update the version.txt file
6. Restore backup
7. Restart rdtc

Would most likely be best to set a cron to run it on a set schedule either daily or weekly.  If there is no update it will just exit, otherwise its fully automated.
2023-09-23 00:06:07 -04:00
Roger Versluis
8d16f7fc57 Fixed docker build.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2023-09-21 07:25:28 -06:00
Roger Versluis
ffa59b5d4c Remove broken banner. 2023-09-21 07:24:28 -06:00
Roger Far
2c8eee24c4 Bump to 2.0.38.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2023-09-20 20:47:42 -06:00
Roger Far
89a6a748cf Add bulk retry to the index. 2023-09-20 20:45:14 -06:00
Roger Far
5c3d357019
Merge pull request #306 from ravensorb/master
A few small updates to build scripts
2023-09-20 20:35:42 -06:00
Roger Far
6ad6da5679 Add extra request logging. 2023-09-20 20:28:06 -06:00
Roger Far
89efa76f76 Add extra error logging on the RealDebrid GetClient. 2023-09-20 19:55:08 -06:00
Roger Far
9488eae8d1 Add Symlink downloader. 2023-09-20 19:43:19 -06:00
Roger Far
199296dc57 Lower password requirements. 2023-09-20 19:22:39 -06:00
Shawn Anderson
f84e698c5b Removed arm7 from default build list 2023-09-05 09:18:03 -04:00
Shawn Anderson
56a81cb764 Added new branding based on linuxserver.io guidelines 2023-09-05 09:06:13 -04:00
Shawn Anderson
67617aa969 Fixed script so that it executes docker command correctly 2023-09-05 09:05:09 -04:00
Roger Versluis
b8aa834dd1 Fixed docker build process.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2023-09-04 12:19:35 -06:00
Shawn
fe0c3a1643
Merge pull request #5 from rogerfar/master
Merging from parent
2023-09-03 23:15:12 -04:00
Roger Versluis
7f2e49ab54 Fixed docker build process and upgraded to Node18. 2023-09-03 13:22:44 -06:00
Roger Versluis
4bea2f444f Removed arm/32 support.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2023-09-03 13:09:04 -06:00
Roger Versluis
b0a0772931 Update Docker image.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2023-09-02 17:14:09 -06:00
Roger Versluis
4b71eec834 Bump alpine image to 3.18 2023-09-02 17:10:38 -06:00
Roger Versluis
86fdf23eb8 Push to 2.0.33.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2023-09-02 17:07:49 -06:00
Roger Versluis
d1da1d7ca5 Merge branch 'master' of github.com:rogerfar/rdt-client 2023-09-02 16:21:07 -06:00
Roger Versluis
9410b0c83b Add extra Add Torrent button on the "Add new torrent" page. 2023-09-02 16:21:00 -06:00
Roger Versluis
3b9a68c92f Add "Post download action" to the settings dialog. 2023-09-02 16:20:41 -06:00
Roger Versluis
ee88fb50f5 Remove illegal characters from the download path. 2023-09-02 16:05:29 -06:00
Roger Versluis
a1e9b00d2d Remove illegal characters from the download path. 2023-09-02 16:04:51 -06:00
Roger Versluis
1b082c3cd1 no message 2023-09-02 15:59:12 -06:00
Roger Far
c1ba030f22
Merge pull request #291 from crawlingcity/master
add link to tteck tto install on Proxmox (LXC)
2023-09-02 15:56:38 -06:00
Roger Versluis
db0880b843 Fixed premiumize nested folders. 2023-09-02 15:53:52 -06:00
Roger Versluis
cb291ad3b0 Upgrade premiumize API. 2023-09-02 11:41:42 -06:00
Roger Versluis
824c107a9f Upgrade packages. 2023-09-02 11:41:01 -06:00
crawlingcity
418465480a
add link to tteck tto install on Proxmox (LXC) 2023-08-12 23:55:47 +01:00
Roger Versluis
0a36a3fd20 no message 2023-07-12 16:38:41 -06:00
Roger Versluis
cac5a3b3fc Fixed baseURL content length.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2023-06-27 08:06:23 -06:00
Roger Far
14a7a6373f Bump to 2.0.31 2023-06-11 14:08:49 -06:00
Roger Far
acfe320b8b Add more logging.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2023-06-11 13:51:38 -06:00
Roger Far
7732649b4b Add request logging and include complete % in the logs. 2023-06-11 13:42:14 -06:00
Roger Far
8418b26d70 Add option to set the base href. 2023-06-11 12:31:13 -06:00
Roger Far
dbfc348945 Improved the internal downloader bandwidth limiter.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2023-04-09 20:03:40 -06:00
Roger Far
37fa505f68 Merge branch 'master' of github.com:rogerfar/rdt-client
Some checks failed
Docker Image CI / build (push) Has been cancelled
2023-04-09 11:40:07 -06:00
Roger Far
5cde1fb154 Test commit. 2023-04-09 11:39:50 -06:00
Roger Far
297b61ec61
Update docker-image.yml 2023-04-09 11:37:51 -06:00
Roger Far
e9c83e30da Add version to repository in the menu.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2023-04-09 10:47:10 -06:00
Roger Far
05225c6acc
Update docker-image.yml 2023-04-08 17:35:36 -06:00
Roger Far
8b8943c3c5 Fixed Premiumize selecting of files when there is no folder.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2023-04-08 16:46:29 -06:00
Roger Far
e1d7c07598
Update docker-image.yml 2023-03-30 21:12:04 -06:00
Roger Far
2e711ba735
Update docker-image.yml 2023-03-30 19:56:27 -06:00
Roger Far
ffa5e274b7 Add Premiumize fixes and logging.
Swapped the internal downloader for a new one.
2023-03-30 19:15:29 -06:00
Roger Far
8595e797b6 Fixed docker build files.
Some checks failed
Docker Image CI / build (push) Has been cancelled
2023-03-19 09:23:07 -06:00
Roger Versluis
f165b07421 2.0.25 2023-03-17 15:47:50 -06:00
Roger Far
87fd07940f
Merge pull request #229 from ravensorb/master
Updates to docker image related to issue 214
2023-03-17 15:43:23 -06:00
Shawn Anderson
7b6f323c84 Merge branch 'master' of https://github.com/ravensorb/rdt-client 2023-03-17 17:36:28 -04:00
Shawn Anderson
9f711611d7 Updated workflow 2023-03-17 17:36:23 -04:00
Shawn
1b9702d59a
Merge pull request #4 from rogerfar/master
Merge with parent
2023-03-17 17:32:44 -04:00
Shawn Anderson
2f30cb5223 Updated workflow 2023-03-17 17:09:07 -04:00
Shawn Anderson
7616637612 Merge branch 'master' of https://github.com/ravensorb/rdt-client 2023-03-17 16:29:03 -04:00
Shawn Anderson
f01774fb7d Updated dockerfile to use newer version of node image for building frontend. Updated dotnet restore/publish to not use a local cache. Changed s6-overlay dependency for initalizing rdt-client from legacy-services to init-mods 2023-03-17 16:28:55 -04:00
Shawn Anderson
2e8ed68ba5 Cleanup 2023-03-17 16:25:37 -04:00
Shawn Anderson
43172f2355 Changed how buildx exports to local docker instance 2023-03-17 16:25:22 -04:00
Shawn
0326bb277a
Update docker-image.yml 2023-03-17 16:13:38 -04:00
Shawn
d55bb3ce23
Update docker-image.yml 2023-03-17 16:11:55 -04:00
Shawn
158720e114
Update docker-image.yml 2023-03-17 16:10:42 -04:00
Shawn Anderson
0d9792787e Added support to change how docker progress is output to console 2023-03-17 08:05:32 -04:00
Shawn
db30217509
Update docker-image.yml 2023-03-17 08:02:01 -04:00
Shawn
36b4dc2317
Create .github/workflows/docker-image.yml 2023-03-17 08:00:35 -04:00
Roger Versluis
8b6de76321 Bump to 2.0.24 2023-03-16 11:37:54 -06:00
Roger Far
cb2fa9f155
Merge pull request #224 from ravensorb/master
Updates to Docker Build Process
2023-03-16 11:36:41 -06:00
Shawn Anderson
503af68d07 Merge branch 'master' of https://github.com/ravensorb/rdt-client 2023-03-16 10:50:48 -04:00
Shawn Anderson
ff0c8061b5 Fixed issue with the SkipCache parameter 2023-03-16 10:50:41 -04:00
Shawn Anderson
7974e33fc2 Fixed issue with the SkipCache parameter 2023-03-16 10:22:25 -04:00
Shawn Anderson
caabc769e4 Updated docker image to use Alpine (reduces in image size). Cleaned up s6-overlay config. 2023-03-16 09:40:01 -04:00
Shawn Anderson
b7a6920517 Merge branch 'master' of https://github.com/ravensorb/rdt-client 2023-03-15 15:16:44 -04:00
Shawn Anderson
311ba78b88 Fixed execute permission on service run file. Added healthcheck to Dockerfile and docker-compose file. Updated scripts for addtional functionality 2023-03-15 15:16:26 -04:00
Shawn
c550e47c2b
Merge pull request #3 from rogerfar/master
Merge with parent
2023-03-15 10:17:46 -04:00
Roger Far
0814d2266d
Merge pull request #221 from ravensorb/master
Fixed and updated docker build
2023-03-15 07:22:51 -06:00
Roger Versluis
1c2837008d Bump to 2.0.23 2023-03-15 07:20:10 -06:00
Shawn Anderson
c4ff8acbc0 Fixed docker build issues. Updated to latest linuxserver.io ubunutu image. Switched over to s6v3 for running service 2023-03-15 07:51:29 -04:00
Shawn
6d65de6f5f
Merge pull request #2 from rogerfar/master
Merge from parent
2023-03-13 07:50:43 -04:00
Roger Far
998ddb05ac Add Premiumize support. 2023-03-11 19:11:38 -07:00
Roger Versluis
3974189c5e Fixed docker build issue. 2023-03-09 08:02:09 -07:00
Roger Far
e25e86ed0e no message 2023-03-08 20:49:39 -07:00
Roger Far
9d64693c66 no message 2023-03-08 20:48:59 -07:00
Roger Far
263557a576 Fixed @angular/flex-layout 2023-03-08 20:48:01 -07:00
Roger Far
e36d04c108 Bump to 2.0.20 2023-03-08 20:46:05 -07:00
Roger Far
805233d73b Fixed APP_BASE_HREF. 2023-03-08 20:36:34 -07:00
Roger Far
b6c79d32a7 Add anonymous authentication. 2023-03-08 20:28:09 -07:00
Roger Far
729b1a2a31 Removed Serilog.Exceptions. 2023-03-08 20:08:07 -07:00
Roger Far
ab64e5dcbd Add settings to specify the ErrorPath and ProcessedPath for the watcher. 2023-03-08 20:03:35 -07:00
Roger Far
56050877e7 Upgrade packages. 2023-03-08 19:49:22 -07:00
Roger Far
46f6e06572 Migrated the simple downloader to the Downloader. 2022-11-28 21:51:25 -07:00
Roger Far
ee9ce2840b Add support for multiple layers of compression in the unpack client. 2022-11-28 20:29:03 -07:00
Roger Far
f96ba0d20d Bump to 2.0.19 2022-11-01 09:49:33 -06:00
Roger Far
1d959bd7a4 Update AllDebrid.NET. 2022-11-01 09:37:07 -06:00
Roger Far
a5aab5a358
Update README.md 2022-10-30 18:32:11 -06:00
Roger Far
bde0ad591e Publish 2.0.18 2022-10-18 12:17:49 -06:00
Roger Far
297216dea5 Move watch files to the error folder when there is an error. 2022-10-18 12:13:02 -06:00
Roger Far
187a428ba7 Add setting to skip downloading the files to the host. 2022-10-18 12:06:03 -06:00
Roger Far
95306264fd Allow updating the category of a torrent. 2022-10-18 11:15:17 -06:00
Roger Far
799a7ce83b Add finish action "Only delete from client". 2022-10-18 11:05:35 -06:00
Roger Far
43da8c2220 Fixed bulk delete torrents header checkbox to be checked when there are any torrents. 2022-10-18 11:05:20 -06:00
Roger Far
0855366afe Update filer-save to use the ES module package. 2022-10-18 11:04:57 -06:00
Roger Far
a775364bfb Add prettier. 2022-10-18 10:55:37 -06:00
Roger Far
dc5dcbb2d2 Add linter and fixed linter errors. 2022-10-18 10:53:00 -06:00
Roger Far
b33cb7ae21 Update packages. 2022-10-18 10:42:11 -06:00
Roger Far
6be9583968
Merge pull request #190 from kanazaca/feat/add_multiple_torrent_delete_to_torrents_table
feat(torrent-table): Add multiple delete option to torrent table
2022-10-18 10:36:12 -06:00
kanazaca
95e55e8971 feat(torrent-table): Add multiple delete option to torrent table 2022-10-17 14:32:53 +01:00
Roger Far
8d84b8af79
Merge pull request #172 from OptimusGREEN/linux-service
Linux Service Instructions
2022-08-16 11:09:11 -06:00
OptimusGREEN
61707108b1 Merge remote-tracking branch 'origin/linux-service' into linux-service 2022-08-12 15:32:45 +01:00
OptimusGREEN
a67cca0f2d Linux Service Instructions 2022-08-12 15:32:35 +01:00
OptimusGREEN
a2e2bb1378 Linux Service Instructions 2022-08-12 15:24:08 +01:00
Roger Far
599fc5011a Fixed issue with some settings not saving. 2022-05-31 07:41:23 -06:00
Roger Far
78408278af Set the BackgroundServiceExceptionBehavior to ignore to investigate why Raspi hosts aren't fully starting. 2022-05-24 09:01:22 -06:00
Roger Far
0247eac9e0 Fixed the login screen for macOS keychain autofill. 2022-05-24 08:58:09 -06:00
Roger Far
3b507730e4 Fixed settings for the qBittorrent integration. 2022-05-15 09:12:09 -06:00
Roger Far
aac570f697 Fixed windows service starting. 2022-05-14 08:26:51 -06:00
Roger Far
bd5d96d2c3 Fixed windows service. 2022-05-14 08:24:38 -06:00
Roger Far
9a278d4fd3 Bump to version 2.0.13. 2022-05-14 08:12:12 -06:00
Roger Far
007e154d8d Added missing migration, fixed watch folders. 2022-05-14 08:09:15 -06:00
Roger Far
e3b38d2834 Add nullable to the projects. 2022-05-13 18:51:04 -06:00
Roger Far
e65942adb7 Upgrade Node packages. 2022-05-13 16:16:44 -06:00
Roger Far
fc15569e1b Rewrote the setting store, retrieve and displaying on the settings page to make maintenance a lot easier.
Added settings to set defaults for the provider import, sonarr, gui and watch folders.
2022-05-13 14:39:52 -06:00
Roger Far
3fe9680e35 Add watch folder monitor. 2022-04-30 12:18:30 -06:00
Roger Far
cd6002b5d6 Upgrade packages
Moved C# code to file scoped namespaces
Merged Startup.cs and Program.cs
2022-04-30 11:22:15 -06:00
Roger Far
2cff8ec0a6
Merge pull request #141 from Edouard-chin/ec-filter-torrents-by-category
Filter torrents with the category query string:
2022-04-30 11:12:29 -06:00
Roger Far
bcffa7ac8c
Merge pull request #138 from Edouard-chin/patch-1
Remove duplicated unpack processes form field
2022-04-30 11:11:16 -06:00
Edouard CHIN
dcea8dcc1a Filter torrents with the category query string:
- ### Problem

  When using multiple *arr instances (sonarr, radarr ...), the queue
  on each instance displays *all* torrents being processed by RDT.
  The normal behaviour is to display torrents that have the same
  category as the one set in the DownloadClient config on
  radarr/sonarr.

  ### Solution

  *arr uses the `/api/v2/torrents/info` QBittorrent endpoint which
  accepts a `category` query string.
  This is how the Qbittorent API behaves when that query string
  exists:

  ```text
  See https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#get-torrent-list

  Get torrents with the given category.
  empty string means "without category";
  no "category" parameter means "any category"
  ```

  See [here](3125b038d5/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentProxyV2.cs (L96)) how radarr sets the categories when making the query.

  Thanks for your indulgence, first time writing dotnet :)
2022-04-28 21:18:56 +02:00
Edouard Chin
2d40f0fd0f
Remove duplicated unpack processes form field
Small change to remove a duplicated form field.

Thanks!
2022-04-24 11:51:21 +02:00
Roger Far
f25d849cfa Fixed the AllDebrid client. 2022-03-20 11:43:05 -06:00
Roger Far
38ca2a199a Fixed the download progress for the AllDebrid client. 2022-03-19 17:08:41 -06:00
Roger Far
a54075a533 Bump to 2.0.10 2022-03-19 15:15:29 -06:00
Roger Far
6ca403f7f2 Add running of external applications. 2022-03-19 15:12:37 -06:00
Roger Far
d4cb09e1a3 Fixed simple downloader speed indication. 2022-03-19 14:19:50 -06:00
Roger Far
f75ac43e49 Add QBittorrent API sync/maindata endpoint for home assistant integration. 2022-03-19 12:49:38 -06:00
Roger Far
aae367d673 Upgrade RD.NET to fix some serialization issues. 2022-03-19 12:49:08 -06:00
Roger Far
5cb095de3c no message 2022-03-12 12:38:57 -07:00
Roger Far
70a230aa1d Bump to 2.0.9 2022-03-12 12:33:08 -07:00
Roger Far
0f0fc91b64 Upgrade packages. 2022-03-12 12:31:19 -07:00
Roger Far
ffecb8507a Bump to 2.0.8 2022-02-28 10:04:01 -07:00
Roger Far
17bd28ed23
Merge pull request #121 from 23doors/fix_alldebrid
fix: alldebrid with empty .files
2022-02-28 10:01:58 -07:00
Robert Kopaczewski
97d1b519bd
fix: alldebrid with empty .files 2022-02-28 17:09:50 +01:00
Roger Far
4040b98176 Bump to 2.0.7 2022-02-06 16:46:38 -07:00
Roger Far
dc16108983 Added setting to set the category when a torrent is imported from RealDebrid or other provider. 2022-02-06 16:46:18 -07:00
Roger Far
05df0cb558 Push 2.0.6 2022-02-06 16:33:28 -07:00
Roger Far
d7b22ab433 no message 2022-02-06 16:25:13 -07:00
Roger Far
6a2533aa3b Added lifetime setting to automatically expire torrents after a certain amount of time. 2022-02-06 16:03:12 -07:00
Roger Far
25f8c70b17 Added setting to automatically delete torrents in the state of error after a certain amount of time. 2022-02-06 15:34:36 -07:00
Roger Far
444c547b15 Add profile pages to update your username and password. 2022-02-06 12:27:36 -07:00
Roger Far
7cc5de61fb Upgrade packages. 2022-02-06 12:08:15 -07:00
Roger Far
8d036847f3 Updated AllDebrid provider to fix issue with ID's not being a number. 2022-01-11 20:14:21 -07:00
Roger Far
d0ef259cae Fixed bug where the QBittorrent API didn't report the error state correctly when an error ocurred in the debrid provider. 2022-01-08 10:38:19 -07:00
Roger Far
2dd5810320 Fixed bug with auto adding of AllDebrid torrents. 2022-01-02 15:35:49 -07:00
Roger Far
5de11cea6e Upgrade packages. 2022-01-02 11:45:38 -07:00
Roger Far
be73e1b543 Add simple update checker. 2021-11-25 17:31:34 -07:00
Roger Far
3fdad6b2f5 Bump to 2.0.2 2021-11-24 13:56:33 -07:00
Roger Far
24deccccec Fixed update time for providers. 2021-11-24 13:55:28 -07:00
Roger Far
6a569e7264 no message 2021-11-24 13:18:37 -07:00
Roger Far
831c7a49e7 Merge branch 'master' of github.com:rogerfar/rdt-client 2021-11-24 07:59:52 -07:00
Roger Far
cdef655a00 Fixed issue with AllDebrid deserialization of magnet link files. 2021-11-24 07:59:45 -07:00
Roger Far
763ce5a036 Fixed potential issue with the RealDebrid data mapper. 2021-11-24 07:59:14 -07:00
Roger Far
b9320be928 Update issue templates 2021-11-24 07:13:10 -07:00
Roger Far
eb39a88e78 Update docker file to build with node 16. 2021-11-21 14:56:01 -07:00
Roger Far
f1bb018381 Add missing changelog stuff. 2021-11-21 14:44:24 -07:00
Roger Far
6e225812b2 no message 2021-11-21 14:40:36 -07:00
Roger Far
4d5dfc34a4 Add auto import and auto delete functionality. 2021-11-21 14:40:16 -07:00
Roger Far
4dbd2cf8df Upgrade to .NET6 and Angular 13. 2021-11-21 13:58:49 -07:00
Roger Far
52b5aa1b09 no message 2021-11-02 16:08:42 -06:00
Roger Far
4837c5c673 Fixed logging links, add 1 minute delay when RD only reports 1 link ready to download to see if there are more downloads. 2021-11-02 08:31:49 -06:00
Roger Far
b9889dbbc3 Add download speed limiter on the Simple downloader. 2021-10-30 20:00:20 -06:00
Roger Far
8774243d2f Add AllDebrid support. 2021-10-30 18:48:32 -06:00
Roger Far
723cc82b11 Bump to 1.9.6 2021-10-30 11:09:18 -06:00
Roger Far
dd8102b0b6 Fixed issue where a deleted torrent could be retried. 2021-10-30 11:05:55 -06:00
Roger Far
b3dd856410 Add retry count as a setting per torrent and global.
Add error status on the torrent itself too.
Improved torrent retrying.
2021-10-30 10:25:53 -06:00
Roger Far
a0d000138c Fixed settings parsing when entering an invalid full Int32.
Add Uploading status to the interface.
2021-10-28 17:26:53 -06:00
Roger Far
075f494ec6 Fixed simple downloader retrying. 2021-10-28 17:15:47 -06:00
Roger Far
0d5d28483b Fixed issues with retrying and cancelling torrents. 2021-10-28 08:29:42 -06:00
Roger Far
799316413a Bump to 1.9.3 2021-10-27 21:50:22 -06:00
Roger Far
5fa5f416c7 Removed obsolete torrent runner code, fixed queuing issue. 2021-10-27 21:47:22 -06:00
Roger Far
5d58bc9082 Add some logging to the downloaders, fixed issue with torrents and downloads not matching up. 2021-10-27 18:55:12 -06:00
Roger Far
d16e7f0c88 Changed the Aria2 updates to be done in the main TorrentRunner loop instead of each downloader making their own connections.
Added checks if Aria2 links actually got added.
Improved debug messages and set Microsoft message to Warning.
Abstracted the RealDebrid client into a ITorrentClient.
Add retry mechanism for server errors from RealDebrid.
2021-10-27 14:55:46 -06:00
Roger Far
545e476d80 Bump to 1.9.0 2021-10-24 10:53:45 -06:00
Roger Far
6ac0265d2d Add priority mechanishm and support for sonarr priority.
Changed to have /torrents/files return only files that are selected.
Add pause/unpause from Sonarr/Radar. Works only for Aria2.
2021-10-24 10:40:59 -06:00
Roger Far
e9e16bedf0 Add more checks for when torrents or downloads have been removed. 2021-10-24 09:39:15 -06:00
Roger Far
0f4d896e53 Add proper retry mechanism for Aria2. 2021-10-24 09:38:55 -06:00
Roger Far
11c65d0017 Fixed bug where real debrid would report torrent links that are empty. 2021-10-21 07:45:18 -06:00
Bryan Reagan
337b70ba01
Rootless Podman and CIFS Connections
First, thanks for the awesome utility!

Some feedback on getting the client running on Arch using rootless Podman.  Works OOB with normal paths on the host and following the tips from LinuxServer.io, but some funny errors working with paths available through a CIFS mount.  

Still having some bugs I'm working out, but thought to get  a PR going if you thought these notes would be helpful to others.  These issues seem like an edge case; let me know if you want issues created for these.

## Issues
- [x] Access Denied Errors in Web GUI Tests ([dotnet runtime issue link](https://github.com/dotnet/runtime/issues/42790))
- [ ] Directory Does Not Exist To Extract To
2021-10-15 10:11:50 -05:00
Roger Far
9583eeeeb0 Bump to 1.8.7 2021-10-11 14:26:17 -06:00
Roger Far
c90c336e70 Add aria2 test connection button on the settings. 2021-10-11 14:22:45 -06:00
Roger Far
ae41865bb2 Finished torrent retry. 2021-10-11 14:06:57 -06:00
Roger Far
399456c815 Fixed retrying of torrents. 2021-10-11 13:53:05 -06:00
Roger Far
d6327e44b6 Add torrent retry functionality. 2021-10-11 13:50:27 -06:00
Roger Far
0d6304a237 Fixed issue with the simple downloader. 2021-10-11 13:37:00 -06:00
Roger Far
1a326ab0bb Bump to 1.8.6 2021-10-09 20:38:51 -06:00
Roger Far
c728e17bb3 Add first Aria2c support. 2021-10-09 20:35:03 -06:00
Roger Far
b8b50ca55a Fixed bug in the Signalr service where it would stop updating when moving away from the index page. 2021-10-09 20:24:19 -06:00
Roger Far
dbdf2acc9c Upgrade packages. 2021-10-09 15:03:55 -06:00
Roger Far
9947a4dc97 Fixed potential error when the qbittorrent API fetched info about a torrent that doesn't have a name yet. 2021-10-09 13:35:27 -06:00
Roger Far
a15a36ccd9 no message 2021-10-07 07:16:30 -06:00
Roger Far
cd8e3b14dd Update issue templates 2021-08-17 07:19:26 -06:00
Roger Far
55560a865a Change timeout to 10 seconds for Real-Debrid API communication. 2021-08-05 14:59:27 -06:00
Roger Far
6cf8b32ba9 Bump to 1.8.3 2021-08-05 11:38:24 -06:00
Roger Far
732e8d9691 Fixed potential issue with duplicate categories. 2021-08-05 11:29:48 -06:00
Roger Far
a8dc196dfe Add version report when the host is starting. 2021-08-05 11:25:40 -06:00
Roger Far
7931568803 Fixed bug where downloads weren't starting properly.
Bump to 1.8.2.
2021-08-02 13:37:22 -06:00
Roger Far
06b4422ab8 Bump to 1.8.1 2021-07-31 14:55:57 -06:00
Roger Far
1f754daf1f pragma warning disable 4014 2021-07-31 14:52:39 -06:00
Roger Far
05e565ff1f Fixed bug where the download and unpack handlers were not properly dispatched. 2021-07-31 14:50:59 -06:00
Roger Far
a121593829 Add better distinct checking for categories. 2021-07-25 10:13:31 -06:00
Roger Far
e4c001b0ce Update database settings retrieval. 2021-07-20 22:53:55 -06:00
Roger Far
697cd58023 Update RD.NET to 2.0 2021-07-20 19:15:11 -06:00
Roger Far
553d46c68d no message 2021-07-18 23:09:25 -06:00
Roger Far
ca17db4f5e Bump to 1.8.0 2021-07-18 22:56:58 -06:00
Roger Far
3ee687f615 Created a separate page for torrent into, added download retrying per file. 2021-07-18 22:46:17 -06:00
Roger Far
fa11dc9bc7 Big update on the add download and setting screens which are now proper pages instead of modals.
Fixed issue where it would slow down on waiting for RealDebrid generating links for downloads.
2021-07-18 17:29:45 -06:00
Roger Far
4daa5ba4dc Reorganize settings in separate tabs. 2021-07-18 14:38:49 -06:00
Roger Far
3e832cc416 Fixed enter button press on the login and setup pages. 2021-07-18 14:22:12 -06:00
Roger Far
e4d522b00e Upgrade packages, clean up some small things. 2021-07-18 14:17:13 -06:00
Roger Far
0e1e547c71 Add category support by saving the categories in the settings table now. 2021-07-18 14:11:40 -06:00
Roger Far
3a2d33435e Bump to 1.7.8 2021-06-16 07:41:33 -06:00
Roger Far
16dfda50b9 Updated RD.NET to 1.0.4 2021-06-16 07:39:17 -06:00
Roger Far
a77e42f91b Bump to 1.7.7 2021-06-09 19:02:08 -06:00
Roger Far
4a87a57a65 Fixed retrying failed selecting of torrent files. 2021-06-09 18:59:06 -06:00
Roger Far
34422d7167 Fixed error handling on downloads. 2021-06-09 18:58:49 -06:00
Roger Far
fa9c839be0 Fixed rare issue where the download link would be empty or invalid. 2021-06-09 17:39:30 -06:00
Roger Far
94804d7cba Add no cache to the docker-publish.ps1 file to make sure we're building clean images. 2021-06-06 16:15:34 -06:00
Roger Far
0e2cb36080 Bump to 1.7.6 2021-06-05 14:59:03 -06:00
Roger Far
f63758521a Fixed Dockerfile to build the ARM64 build correctly. 2021-06-05 14:56:33 -06:00
Roger Far
a3ea5c61f2 Fixed cache issue. 2021-06-05 14:56:16 -06:00
Roger Far
71c9eb17fb Fixed issue with torrent data caching. 2021-06-05 14:21:29 -06:00
Roger Far
f151312d47 Fixed issue with downloading new files. 2021-06-05 12:40:24 -06:00
Roger Far
a01137daf4 Add 1.7.5 changelog 2021-06-05 12:29:05 -06:00
Roger Far
9fae26e21d no message 2021-06-05 12:27:00 -06:00
Roger Far
bf3ba9dc64 Bump to 1.7.5 2021-06-05 12:25:19 -06:00
Roger Far
3380bc6820 Update RD.NET 2021-06-05 12:20:44 -06:00
Roger Far
97b94900fd Set dynamic base href. 2021-06-05 12:08:19 -06:00
Roger Far
5e490e8b0d Add check if torrent is actually changed from RD to prevent DB updates. 2021-06-05 11:46:59 -06:00
Roger Far
7872836ae7 Add torrent caching. 2021-06-05 11:46:41 -06:00
Roger Far
9a7aefaa71 Add a thread safe lock to the settings 2021-06-05 11:25:47 -06:00
Roger Far
1f50cda389 Update packages. 2021-06-05 10:59:56 -06:00
Roger Far
75875af36a no message 2021-06-05 10:59:36 -06:00
Roger Far
5bdf224e1b Fixed bug where original filename wasn't handled properly. 2021-06-05 10:50:56 -06:00
Roger Far
fe3b7d8867 Improve handling of torrent URL's that are HTTP files. 2021-04-28 07:46:07 -06:00
Roger Far
659675d538 Update packages. 2021-04-26 19:47:33 -06:00
Roger Far
3cc3c1eeda
Merge pull request #57 from ravensorb/master
Updated dockerfile to use build image as source rather than add local file
2021-04-26 16:05:38 -06:00
Shawn Anderson
24aa54319d Updated dockerfile to use build image as sorce rather than local filesystem 2021-04-26 17:24:26 -04:00
Roger Far
ad5e5b48ce
Merge pull request #55 from ravensorb/master
Added support for dos2unix to handle windows line ending issue during build
2021-04-26 07:23:33 -06:00
Shawn Anderson
058e67b03e Updated dockerfile to allow dos2unix to correct the window line ending issue 2021-04-25 21:15:32 -04:00
Shawn
e6a261dfd6
Merge pull request #1 from rogerfar/master
Merge with main fork
2021-04-25 20:47:39 -04:00
Roger Far
6a1c2a1f0f Version bump to 1.7.4 to generate new docker containers. 2021-04-22 09:31:42 -06:00
Roger Far
cc7abc82ba
Merge pull request #53 from ravensorb/master
Updated dockerfile and related items
2021-04-22 09:23:11 -06:00
Shawn Anderson
fbcfdfc8f5 Merge branch 'master' of https://github.com/ravensorb/rdt-client 2021-04-21 20:06:28 -04:00
Shawn Anderson
8ea3f0ecaf Updated Dockerfile and related files 2021-04-21 20:06:23 -04:00
Shawn Anderson
32cb03a5b3 Updated Dockerfile and related files 2021-04-21 20:05:03 -04:00
Roger Far
49beb60894 Fixed issue where sometimes the name of the torrent would change, causing the download not to be recognized by sonarr anymore. 2021-04-19 15:43:06 -06:00
Roger Far
9c236bbe67 Fixed error in 7z script for building the zip file, zip re-uploaded to 1.7.2 2021-04-14 16:30:57 -06:00
Roger Far
3037179a27 Using new Dockerfile thanks to ravensorb 2021-04-14 16:17:09 -06:00
Roger Far
04d580bc5c Revert dockerfile. 2021-04-14 16:04:47 -06:00
Roger Far
2cb81e2c41 Added path seperator on the content_path, fixed reporting errors. 2021-04-14 15:44:30 -06:00
Roger Far
a15c2473c5 Switch the dotnet-publish to use 7z so it created a proper zip file usable on linux. 2021-04-06 07:10:27 -06:00
Roger Far
c4a7c1e5ed Fixed some bugs in the update script 2021-04-01 16:31:05 -06:00
Roger Far
d7c6c36118 no message 2021-04-01 16:19:53 -06:00
Roger Far
58a80e45e1 Update to 1.7.0 2021-04-01 16:19:33 -06:00
Roger Far
1764bdc803 Fixed bug where downloads sometimes got added twice.
Added a retry download functionality.
2021-04-01 15:45:46 -06:00
Roger Far
c7e3e14fab Bump to 1.6.3 2021-03-15 08:37:26 -06:00
Roger Far
0489ab1115 Fixed issue with downloads being added prematurely when the torrent is in the Processing state. 2021-03-15 08:33:23 -06:00
Roger Far
8817d8df3b Bump to 1.6.2 2021-03-13 08:06:30 -07:00
Roger Far
3e1b30ad1a Add a retry button in the interface.
Fixed issue with some downloads not processed.
Fixed issue with deletion and files in use.
2021-03-13 08:04:03 -07:00
Roger Far
8b85521839 Fixed a bug in the torrent runner where it could download the same file multiple times. 2021-03-12 08:11:02 -07:00
Roger Far
2a258dcf58 no message 2021-03-10 18:01:26 -07:00
Roger Far
2659759d59 Updated to 1.6.0 2021-03-10 17:59:04 -07:00
Roger Far
5265a8b442 Changed the unrestrict process to only unrestrict when the download is starting. 2021-03-10 17:56:54 -07:00
Roger Far
e291989ace Add new publish script. 2021-02-14 12:02:21 -07:00
Roger Far
bbf2217718 Upgrade 2021-02-14 11:48:07 -07:00
Roger Far
e22e5bbeab Changed docker run command. 2021-02-12 14:50:43 -07:00
Roger Far
e4411e8fa1 Update docker file. 2021-02-12 14:44:49 -07:00
Roger Far
290ed4c7de Add logging and fixed issue with the torrentrunner hanging sometimes. 2021-02-12 14:39:20 -07:00
Roger Far
fda4727439 Add user and group dotnet with ID 1000 to the Dockerfile. 2021-02-11 21:01:48 -07:00
Roger Far
5803ee306d Bump to 1.5.4 2021-02-11 19:54:15 -07:00
Roger Far
58a6fafbc2 Fixed issue with the filenames not working when unpacking. 2021-02-11 19:54:08 -07:00
Roger Far
426ff20273 no message 2021-02-11 15:35:38 -07:00
Roger Far
d46e6f8997 Fixed issue with selecting files when it would filter all files out. 2021-02-10 07:25:39 -07:00
Roger Far
83f89865c9 Bump version to 1.5.2 2021-02-09 20:04:21 -07:00
Roger Far
5e8d65fcb2 Add Proxy Server setting. 2021-02-09 19:56:24 -07:00
Roger Far
4cf25b6b37 Upgraded RD.NET to 1.0.2 2021-02-09 19:48:21 -07:00
Roger Far
b1c70b723f Url decode the filename, sometimes RD would return a url encoded string, sometimes not. 2021-02-07 21:59:47 -07:00
Roger Far
4a985cc1f4 Changed dockerfile to support armv7 2021-02-06 19:44:42 -07:00
Roger Far
64c2204af5 Fixed bug with the OnlyDownloadAvailable files setting. 2021-02-06 18:54:06 -07:00
Roger Far
9071db58a2 Fixed docker file to create 1 release again 2021-02-06 12:05:17 -07:00
Roger Far
63b04bbe33 Re-instate the service-install and service-remove bat files. 2021-02-05 21:28:06 -07:00
Roger Far
ed10c630ee Add option to only download the files that are available on RD. 2021-02-05 20:54:37 -07:00
Roger Far
8bd26b7441 Switched Downloader from sourcecode to the nuget package. 2021-01-25 07:07:45 -07:00
Roger Far
2652895ba0 Make the downloader switchable. 2021-01-23 10:02:37 -07:00
Roger Far
197305eb19 Fixed docker build file to compule arm64v8. 2021-01-23 07:31:26 -07:00
Roger Far
f02639b426 no message 2021-01-21 10:36:11 -07:00
Roger Far
67b009efcd no message 2021-01-21 10:16:18 -07:00
Roger Far
9ab889f7db Clean temp files on startup. 2021-01-21 09:22:01 -07:00
Roger Far
cd2f29e9f5 Fixed bug in the download client. 2021-01-21 09:14:13 -07:00
Roger Far
bc552612f5 Add more settings for the download client. 2021-01-21 08:41:33 -07:00
Roger Far
c4a985f8b5 Updated the docker publish to support ARMv8 2021-01-20 11:48:13 -07:00
Roger Far
5ee7ebadb1 Ignore included Downloader project. 2021-01-20 11:11:23 -07:00
Roger Far
19e5e5eb10 Use a new Downloader from https://github.com/bezzad/Downloader, add a write and download speed test to the settings interface. 2021-01-20 10:44:59 -07:00
Roger Far
49071a7f48 Increased download buffer size, change the torrentrunner to only query RDT when there are any torrents, added a simple speed test. 2021-01-19 10:02:26 -07:00
Roger Far
5f84c2b317 no message 2021-01-14 16:47:48 -07:00
Roger Far
44d7d0f9b8 Moved the responsibility for starting the Download and Unpack process to the TorrentRunner instead of the Torrents service. 2021-01-14 16:21:58 -07:00
Roger Far
63e44d02ef Fixed bug where it would redownload file that was already downloaded. 2021-01-14 16:01:05 -07:00
Roger Far
b3dd72234f Update readme. 2021-01-13 19:18:21 -07:00
Roger Far
f0030bef42 Changed default logging to warning. 2021-01-13 19:16:04 -07:00
Roger Far
3598b1f218 Fixed category setting when adding a torrent from Sonarr or Radarr. 2021-01-13 19:15:00 -07:00
Roger Far
5245a9448f no message 2021-01-13 16:46:59 -07:00
Roger Far
fe5a2a5940 Fixed process status fields in qBittorrent API 2021-01-13 16:30:42 -07:00
Roger Far
4f0d75a9f5 Fixed issue with qBittorrent category save paths and updated all qBittorrent versions. 2021-01-13 16:03:15 -07:00
Roger Far
9b74c45ec3 Fixed speed and statuses on the qBittorrent api. 2021-01-13 13:57:09 -07:00
Roger Far
b0f584a6d5 no message 2021-01-13 13:46:47 -07:00
Roger Far
c0d43769de Fixed qBittorrent API status on the torrent list. 2021-01-13 13:45:36 -07:00
Roger Far
d029319ed1 Fixed torrent content_path. 2021-01-13 13:15:44 -07:00
Roger Far
64d1250345 Add radarr and sonarr categories to make the new version of radarr work. 2021-01-13 11:46:57 -07:00
Roger Far
337ecdecd8 Release fixes 2021-01-13 11:16:39 -07:00
Roger Far
0a8793e4f9 Add simple retry mechanism for network connection drops. 2021-01-13 11:04:42 -07:00
Roger Far
860ffe95e4 no message 2021-01-13 09:31:46 -07:00
Roger Far
55f2c4a338 Fixed downloadpath and contentpath 2021-01-13 09:31:42 -07:00
Roger Far
7f9d6e6cba Rename folder to path everywhere.
Added a remote path setting for path mapping for Sonarr/Radarr.
2021-01-12 21:24:46 -07:00
Roger Far
2300b51ede Force prettier to 120 width. 2021-01-12 20:46:07 -07:00
Roger Far
67532645aa Fixed status columns, separate downloads and files in the detail list.
Add UnpackLimit setting.
2021-01-12 20:42:06 -07:00
Roger Far
21ad2ebfd6 Swapped out the timer GET for a signalr connection. 2021-01-10 20:21:49 -07:00
Roger Far
8f35952f90 Improved statuses, add more actions. 2021-01-10 15:59:44 -07:00
Roger Far
a27bd37991 Refactored download processor. 2021-01-09 19:51:36 -07:00
Roger Far
ec02b3d8bb Improved inital setup by adding a short wizard. 2021-01-09 12:34:05 -07:00
Roger Far
df39d6a671 Changed readme. 2021-01-09 11:20:34 -07:00
Roger Far
cccf30c87d Improved logging by switching to Serilog.
Refactored out hardcoded logpath and DB connection string and made them configurable through appsettings.json.
Add persistent store path in docker-compose.
2021-01-09 11:05:33 -07:00
Roger Far
d6dbb2a750 Merge branch 'master' of github.com:rogerfar/rdt-client 2021-01-09 10:34:09 -07:00
Roger Far
13d41c8ac8 Update Angular 2021-01-09 10:33:59 -07:00
Roger Far
4c9fb7e6b8 Merge pull request #17 from dmyers/patch-1
Fixed docker compose by swapping image and contain name values
2021-01-09 10:33:13 -07:00
Derek Myers
43c564e1ed Fixed docker compose by swapping image and contain name values 2021-01-05 21:42:01 -06:00
Roger Far
5affa9ebef Forgot to save the readme 2020-12-31 09:02:27 -07:00
Roger Far
02c154f094 Fixed few layout issues with spinners.
Add a new Test Folder button in settings to test.
Add Docker images.
2020-12-12 14:10:05 -07:00
Roger Far
b990a910be Update packages and update Runtime to .NET 5 2020-12-12 11:02:54 -07:00
Roger Far
9224c0549e Merge pull request #4 from alexmckenley/files
Implement api/v2/torrents/files endpoint
2020-12-01 07:12:32 -07:00
Alex Taylor
4d92a6f42d Implement torrents/files endpoint
It seems that recently sonarr and radarr have changed the qbittorrent inegration such at the torrents/files endpoint is required for file mapping to work. I've tested this against both sonarr and radarr, and things seem to be working.
2020-12-01 01:07:49 -08:00
Roger Far
a3c96809ef Add missing startup.bat 2020-10-15 07:37:14 -06:00
Roger Far
71697f993f Fixed small issues when adding torrents and not loading meta data. 2020-10-15 07:28:44 -06:00
Roger Versluis
22494a1cad Fixed deletion bug and extraction bug 2020-05-17 09:30:14 -06:00
Roger Far
7d9d64c97b no message 2020-05-16 20:21:33 -06:00
Roger Far
629babd9bd Fixed extracting when the path in the rar file has the torrent as a subdirectory. 2020-05-16 20:21:00 -06:00
Roger Far
0220b18017 Fixed issue with deleting torrents and a null reference error. 2020-05-16 20:20:37 -06:00
Roger Far
914272d6d7 Remove console.log 2020-05-16 20:19:53 -06:00
Roger Far
7c6458bbb0 Fixed an authentication quirk where theqBittorrent API returns a 403 instead of a 401 when its unauthorized. 2020-05-16 20:19:42 -06:00
Roger Far
5d73ae140e Fixed not selecting files sometimes. 2020-04-18 15:10:59 -06:00
Roger Far
5234f6cad3 Don't delete the torrent when it's finished when it comes from the QBittorrentAPI, let the caller handle deletion 2020-04-18 12:46:39 -06:00
Roger Far
731daddc99 Improved logging and fixed queued failure handling. 2020-04-18 08:07:06 -06:00
Roger Far
dc4992a360 Changed how the service gets installed, fixed few bugs 2020-04-17 07:30:29 -06:00
343 changed files with 50754 additions and 18416 deletions

14
.devcontainer/Dockerfile Normal file
View file

@ -0,0 +1,14 @@
FROM mcr.microsoft.com/devcontainers/dotnet:2-10.0-noble
ARG NODE_MAJOR=22
ARG ANGULAR_CLI_VERSION=21
RUN curl -fsSL "https://deb.nodesource.com/setup_${NODE_MAJOR}.x" | bash - \
&& apt-get update \
&& apt-get install -y --no-install-recommends nodejs sqlite3 \
&& npm install -g "@angular/cli@^${ANGULAR_CLI_VERSION}" \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /data/db /data/downloads \
&& chown -R vscode:vscode /data

View file

@ -0,0 +1,51 @@
{
"name": "rdt-client",
"build": {
"dockerfile": "Dockerfile",
"args": {
"NODE_MAJOR": "22"
}
},
"remoteUser": "vscode",
"runArgs": ["--init"],
"containerEnv": {
"ASPNETCORE_ENVIRONMENT": "Development",
"BASE_PATH": "",
"DOTNET_USE_POLLING_FILE_WATCHER": "1",
"CHOKIDAR_USEPOLLING": "true"
},
"forwardPorts": [4200, 6500],
"portsAttributes": {
"4200": {
"label": "Angular client",
"onAutoForward": "notify"
},
"6500": {
"label": "RdtClient backend",
"onAutoForward": "silent"
}
},
"mounts": [
"source=rdtclient-data-db,target=/data/db,type=volume",
"source=rdtclient-data-downloads,target=/data/downloads,type=volume",
"source=rdtclient-nuget,target=/home/vscode/.nuget,type=volume",
"source=rdtclient-npm-cache,target=/home/vscode/.npm,type=volume"
],
"postCreateCommand": "/bin/bash .devcontainer/post-create.sh",
"postStartCommand": "/bin/bash .devcontainer/post-start.sh",
"customizations": {
"vscode": {
"extensions": [
"ms-dotnettools.csharp",
"ms-dotnettools.csdevkit",
"Angular.ng-template",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
],
"settings": {
"dotnet.defaultSolution": "server/RdtClient.sln",
"terminal.integrated.defaultProfile.linux": "zsh"
}
}
}
}

16
.devcontainer/post-create.sh Executable file
View file

@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
sudo install -d -m 0775 -o vscode -g vscode \
/data/db \
/data/downloads \
/home/vscode/.nuget \
/home/vscode/.nuget/NuGet \
/home/vscode/.nuget/packages \
/home/vscode/.npm
sudo chown -R vscode:vscode /data /home/vscode/.nuget /home/vscode/.npm
dotnet restore server/RdtClient.sln
npm install --prefix client

13
.devcontainer/post-start.sh Executable file
View file

@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
sudo install -d -m 0775 -o vscode -g vscode \
/data/db \
/data/downloads \
/home/vscode/.nuget \
/home/vscode/.nuget/NuGet \
/home/vscode/.nuget/packages \
/home/vscode/.npm
sudo chown -R vscode:vscode /data /home/vscode/.nuget /home/vscode/.npm

40
.dockerignore Normal file
View file

@ -0,0 +1,40 @@
### Angular ###
# compiled output
client/dist/
client/tmp/
client/app/**/*.js
client/app/**/*.js.map
# dependencies
client/node_modules/
client/bower_components/
# IDEs and editors
client/.idea/
# misc
client/.sass-cache/
client/connect.lock/
client/coverage/
client/libpeerconnection.log/
client/npm-debug.log
client/testem.log
client/typings/
# e2e
client/e2e/*.js
client/e2e/*.map
#System Files
**/.DS_Store/
### DotnetCore ###
# .NET Core build folders
server/**/bin
server/**/obj
server/**/*.user
server/**/.vs
server/RdtClient.Web/wwwroot/
build/
publish/data

20
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,20 @@
---
name: Bug report
about: Use this template to report actual bugs. If you are looking for general help, please post a discussion.
title: ''
labels: ''
assignees: ''
---
**What version are you using?**
**What OS are you running?**
**Are you using Docker or as a service?**
**Which debrid provider are you using?**
**Which downloader are you using?**
**Please attach a log file here with the log setting set to debug**

141
.github/workflows/build-docker-image.yml vendored Normal file
View file

@ -0,0 +1,141 @@
name: Release Docker Image
on:
push:
tags:
- v*
permissions:
packages: write
env:
APP_NAME: rdtclient
DOCKER_FILE: ./Dockerfile
ENABLE_DOCKERHUB: 1
ENABLE_GHCR: 1
concurrency: ${{ github.sha }}
jobs:
build:
outputs:
digest-amd64: ${{ steps.digest.outputs.digest-amd64 }}
digest-arm64: ${{ steps.digest.outputs.digest-arm64 }}
strategy:
matrix:
config:
- arch: amd64
runs-on: ubuntu-latest
platform: linux/amd64
- arch: arm64
runs-on: ubuntu-24.04-arm
platform: linux/arm64
runs-on: ${{ matrix.config.runs-on }}
steps:
- name: Checkout Code
uses: actions/checkout@v6
- name: Extract version and patch .csproj
run: |
VERSION="${GITHUB_REF_NAME#v}"
sed -i "s|<Version>.*</Version>|<Version>$VERSION</Version>|" server/RdtClient.Web/RdtClient.Web.csproj
sed -i "s|<AssemblyVersion>.*</AssemblyVersion>|<AssemblyVersion>$VERSION</AssemblyVersion>|" server/RdtClient.Web/RdtClient.Web.csproj
- name: Login to DockerHub
if: ${{ env.ENABLE_DOCKERHUB == 1 }}
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR
if: ${{ env.ENABLE_GHCR == 1 }}
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker Metadata action
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ secrets.DOCKERHUB_USERNAME }}/${{ env.APP_NAME }}
ghcr.io/${{ github.repository_owner }}/${{ env.APP_NAME }}
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
context: .
labels: ${{ steps.meta.outputs.labels }}
tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.APP_NAME }}
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
- name: Export digest
id: digest
run: |
echo "digest-${{ matrix.config.arch }}=${{ steps.build.outputs.digest }}" >> "$GITHUB_OUTPUT"
push-images:
needs: build
runs-on: ubuntu-latest
steps:
- name: Login to DockerHub
if: ${{ env.ENABLE_DOCKERHUB == 1 }}
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR
if: ${{ env.ENABLE_GHCR == 1 }}
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker Metadata action
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ secrets.DOCKERHUB_USERNAME }}/${{ env.APP_NAME }}
ghcr.io/${{ github.repository_owner }}/${{ env.APP_NAME }}
tags: |
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Create manifest list and push
run: |
docker buildx imagetools create \
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< '${{ steps.meta.outputs.json }}') \
${{ secrets.DOCKERHUB_USERNAME }}/${{ env.APP_NAME }}@${{ needs.build.outputs.digest-amd64 }} \
${{ secrets.DOCKERHUB_USERNAME }}/${{ env.APP_NAME }}@${{ needs.build.outputs.digest-arm64 }}
- name: Checkout code
uses: actions/checkout@v6
- name: Docker Hub Registry Description
if: ${{ env.ENABLE_DOCKERHUB == 1 }}
uses: peter-evans/dockerhub-description@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
repository: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.APP_NAME }}
short-description: |
A web interface to manage your torrents on debrid providers.
readme-filepath: ./README-DOCKER.md

99
.github/workflows/build-release.yaml vendored Normal file
View file

@ -0,0 +1,99 @@
name: Create GitHub Release
on:
push:
tags:
- v*
permissions:
contents: write
packages: write
jobs:
build:
name: Test, Build, and Bundle
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: "lts/*"
- name: Set up .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: '10'
- name: Install Frontend Dependencies
working-directory: client
run: npm install
- name: Build Frontend
working-directory: client
run: npm run build
- name: Restore Backend Dependencies
working-directory: server
run: dotnet restore
- name: Build and Publish Backend
working-directory: server
shell: pwsh
run: |
$v = "${{ github.ref_name }}".TrimStart('v')
dotnet build -c Release --no-restore -p:Version=$v -p:AssemblyVersion=$v
dotnet publish RdtClient.Web/RdtClient.Web.csproj -c Release --no-build -p:Version=$v -p:AssemblyVersion=$v -o ../publish
- name: Create ZIP
shell: pwsh
run: Compress-Archive -Path publish\* -DestinationPath RealDebridClient.zip
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: RealDebridClient.zip
path: RealDebridClient.zip
release:
name: Create GitHub release
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Strip 'v' from tag
id: version
run: echo "version=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT
- name: Get latest release info
id: query-release-info
uses: release-flow/keep-a-changelog-action@v2
with:
command: query
version: ${{ steps.version.outputs.version }}
- name: Download Artifact
uses: actions/download-artifact@v4
with:
name: RealDebridClient.zip
- name: Create Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: ${{ github.ref_name }}
body: ${{ steps.query-release-info.outputs.release-notes }}
draft: false
prerelease: false
files: RealDebridClient.zip
token: ${{ secrets.GITHUB_TOKEN }}

24
.github/workflows/dotnet-test.yml vendored Normal file
View file

@ -0,0 +1,24 @@
name: dotnet test
on:
push:
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: 10.0.x
- name: Restore dependencies
run: dotnet restore server
- name: Build
run: dotnet build --no-restore server
- name: Test
run: dotnet test --no-build --verbosity normal server

46
.gitignore vendored
View file

@ -1,39 +1,11 @@
### Angular ###
# compiled output
client/dist/
client/tmp/
client/app/**/*.js
client/app/**/*.js.map
# dependencies
client/node_modules/
client/bower_components/
# IDEs and editors
client/.idea/
# misc
client/.sass-cache/
client/connect.lock/
client/coverage/
client/libpeerconnection.log/
client/npm-debug.log
client/testem.log
client/typings/
# e2e
client/e2e/*.js
client/e2e/*.map
#System Files
**/.DS_Store/
### DotnetCore ###
# .NET Core build folders
server/**/bin
server/**/obj
server/**/*.user
server/**/.vs
server/RdtClient.Web/wwwroot/
build/
publish/
node_modules/
RealDebridClient.zip
server/RdtClient.Web/appsettings.Development.json
data
Dockerfile.dev
test.bat
.DS_Store

983
CHANGELOG.md Normal file
View file

@ -0,0 +1,983 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased
## [2.0.138] - 2026-06-05
### Fixed
- SABnzbd fixes.
## [2.0.137] - 2026-06-05
### Fixed
- Fix torbox save path mapping for single file downloads.
## [2.0.136] - 2026-05-30
### Added
- Added devcontainer development workflow.
### Changed
- Upgraded torbox.net dependency.
## [2.0.135] - 2026-05-27
### Changed
- When adding a torrent through the qBittorrent endpoints, it will wait to see if the torrent gets added properly or errors out, resulting in a better experience for infringing files and Sonarr / Radarr.
- Upgraded torbox.net dependency.
- Support SABnzbd API key auth, thanks to @ALenfant!
## [2.0.134] - 2026-05-22
### Changed
- Upgraded torbox.net dependency.
## [2.0.133] - 2026-05-17
### Added
- Added NZB support for Premiumize, thanks to @ALenfant!
## [2.0.132] - 2026-05-17
### Changed
- Fixed for Cleanuparr
- Upgrade Torbox.NET dependency
## [2.0.131] - 2026-05-14
### Changed
- Fixes to the rate limiting.
## [2.0.130] - 2026-04-28
### Changed
- Fixes to unit tests
- Upgraded .NET and Angular dependencies
- Added better qBittorrent capabilities for Cleanuparr
- Performance fixes
## [2.0.129] - 2026-04-06
### Added
- Optimized mobile views, thanks to @sylvaindd!
## [2.0.128] - 2026-04-06
### Changed
- Fixed some qBittorrent endpoints.
## [2.0.127] - 2026-03-15
### Added
- Implement qBittorrent endpoint for counting torrents, thanks to @vinodmishra!
## [2.0.126] - 2026-03-14
### Added
- qBittorrent hash filtering on the /torrrents/info endpoint, thanks to @jfrconley!
- Category select as a dropdown when adding a new torrent, thanks to @sylvaindd!
### Changed
- Rate limiting fixes, thanks to @omgbeez!
- AllDebrid Symlink fixes, thanks to @AlexandreVassard!
## [2.0.125] - 2026-03-01
### Added
- Added extra qBittorrent endpoints for Cleanuparr compatibility.
## [2.0.124] - 2026-02-23
### Added
- Added /version/api for qBittorrent to support clients that still want to talk to the old API.
- Improved rate limiting handling.
## [2.0.123] - 2026-02-21
### Changed
- Reverted SharpCompress to 0.42.1 due to file locking issues.
## [2.0.122] - 2026-02-21
### Added
- Sort torrents by added date by default, remember sort order in the browser.
### Changed
- Fixed ETA calculations.
- Fixed Torrentbox statuses.
- Fixed download progress reports.
- Fixed NZB delete actions.
## [2.0.121] - 2026-02-18
### Added
- NZB/Usernet support thanks to @omgbeez!
### Changed
- Make some qBittorrent endpoints accessible without authentication.
- Improved ETA calculations.
- Few performance improvements when the download page has a lot of torrents.
- Performance improvements for the sqlite connection.
## [2.0.120] - 2026-02-11
### Changed
- Upgrade to .NET 10.
- Upgrade to Angular 21.
## [2.0.119] - 2025-10-13
### Removed
- Removed internal downloader from the GUI.
## [2.0.118] - 2025-10-06
### Added
- Added some fake qBittorrent API calls for decluttarr.
## [2.0.117] - 2025-10-06
### Removed
- Removed internal downloader and migrated to the Bezzad downloader.
## [2.0.116] - 2025-08-04
### Added
- Added setting to ban certain trackers from being added. Will filter by the torrent source or announcement urls.
### Changed
- Upgraded to Angular 20.
## [2.0.115] - 2025-07-28
### Added
- Added setting to delay the finish action.
### Fixed
- Make sure the Real-Debrid provider times out when trying to add a new torrent.
## [2.0.114] - 2025-06-21
### Added
- Add Select All functionality to the delete dialog in individual torrent screen, thanks @mentalblank
- Add setting to add a list of trackers (from a URL) to every torrent and magnet that's added to rdt-client, thanks @mentalblank
### Changed
- The `User-Agent` header is now set on all requests to debrid providers' APIs.
## [2.0.113] - 2025-05-22
### Fixed
- Revert Synolog.Api.Client because of breaking changes.
## [2.0.112] - 2025-05-18
### Added
- Add ability to disable the built in unpacking process by setting the "Maximum unpack processes" to 0.
### Changed
- Upgraded Angular to use control flow.
### Fixed
- Fixed dequeing issue.
- Fixed logging handler for ProviderUpdater.
## [2.0.111] - 2025-05-03
### Added
- Added button to register rdt-client as a handler for magnet links on [supported browsers](https://caniuse.com/mdn-api_navigator_registerprotocolhandler_scheme_parameter_magnet).
## [2.0.110] - 2025-04-24
### Fixed
- Fixed build number in the app.
## [2.0.109] - 2025-04-23
### Fixed
- Debrid Queue fixes (don't auto delete queued torrents, handle errors when dequeueing).
- Censor download station password when logging settings at startup.
- Set `HostDownloadAction` when auto-importing torrents.
- Build GitHub release .zip on windows not linux.
- Use arm GitHub Actions runner to build arm docker image.
## [2.0.108] - 2025-04-13
### Fixed
- Fixed websocket UI updating.
## [2.0.107] - 2025-04-13
### Fixed
- Fixed Docker release versioning.
## [2.0.106] - 2025-04-13
### Fixed
- Changed how the GitHub release is created and how the changelog is generated.
## [2.0.105] - 2025-04-13
### Added
- Add feature to limit the amount of torrents that get sent to the provider at the same time.
### Fixed
- Moved the websocket update process to its own background thread to improve UI update consistency.
## [2.0.104] - 2025-04-12
### Fixed
- Update the version number
## [2.0.103] - 2025-04-12
### Added
- Button to select all options when deleting a torrent, thanks @EugeneKallis
- Add setting to ignore update notifications. A notification will appear regardless of this setting if any GitHub Security Advisories are published in this repo.
### Changed
- Download .zip of torrent files from TorBox when possible, thanks @asylumexp
- Users of AllDebrid and RealDebrid will now have no files downloaded when all files are excluded by filters. Before, if all files were excluded, rdt-client would download all the files in the torrent.
- Reduce number of calls to debrid provider API when no torrents need updating
### Fixed
- The dropdown navigation menu on mobile will now close when you navigate to another page
- Long torrent names without spaces will now wrap across lines
### Security
- Require auth to change debrid api key
## [2.0.102] - 2025-03-07
### Changed
- Fixed Angular build for Docker.
## [2.0.101] - 2025-03-07
### Changed
- Fixed Angular build (again).
## [2.0.100] - 2025-03-07
### Changed
- Fixed Angular build.
## [2.0.99] - 2025-03-07
### Security fix
- The Api/Authentication/Update was not protected by authentication, meaning everyone could reset your password and gain access.
### Added
- Set the useragent for the Bezadd downloader to avoid getting blacklisted by Torbox.
### Changed
- Upgraded to Angular 19.
- Upgraded to Torbox 1.5.
## [2.0.98] - 2025-02-16
### Added
- Added unit tests, thanks @Cucumberrbob!
### Changed
- Fixed symlinks for AllDebrid.
- Upgraded DebridLink.fr to the latest version.
- Fixed nested files in the Premiumize Provider.
- Fixed deleting of torrents in the watch folders.
## [2.0.97] - 2025-02-16
### Added
- Added support for DebridLink.fr.
### Fixed
- Fixed for the internal downloader.
- Added a column for torrent add date.
- Upgraded AllDebrid API.
## [2.0.96] - 2025-01-29
### Added
- Added support for the synology download manager.
- Added a column for torrent add date.
### Changed
- Fixed for the Symlink downloader and AllDebrid.
- Fixed setting the downloader when adding a torrent through the GUI.
## [2.0.95] - 2025-01-19
### Added
- Added the /api/v2/transfer/info qBittorrent endpoint.
### Changed
- AllDebrid Symlink path fixes.
## [2.0.94] - 2025-01-05
### Changed
- AllDebrid path fixes.
## [2.0.93] - 2025-01-03
### Changed
- Torbox fixes.
## [2.0.92] - 2024-12-18
### Changed
- Torbox fixes.
## [2.0.91] - 2024-12-11
### Changed
- Torbox fixes.
## [2.0.90] - 2024-12-06
### Changed
- Download individual files from Torbox instead of a zip file.
### Removed
- Removed ability to select instant files from AllDebrid.
## [2.0.89] - 2024-11-24
### Changed
- Disabled selecting of files as Real-Debrid was the only provider that supported that.
## [2.0.88] - 2024-11-24
### Changed
- Catch disabled instant availability endpoint from Real Debrid.
## [2.0.87] - 2024-11-18
### Added
- Torbox support.
- qBittorrent API authentication when no authentication is used.
### Changed
- .NET version changed to .NET 9.
- Changed download limit to be split by active torrents.
## [2.0.86] - 2024-09-03
### Changed
- Add potential fix for BASE_PATH.
- Fixed creation of empty folders.
## [2.0.85] - 2024-09-03
### Changed
- Reverted: Prevent the creation of download folders when using symlink.
## [2.0.84] - 2024-09-02
### Changed
- Replace docker libssl1.1 with libssl3
## [2.0.83] - 2024-09-02
### Changed
- Fixed progress reporting to the qBittorrent API endpoint.
- Prevent the creation of download folders when using symlink.
## [2.0.82] - 2024-09-02
### Changed
- Update packages and docker alpine version.
## [2.0.81] - 2024-07-28
### Changed
- Improved handling of infringed torrents from real debrid.
- Force update of torrent data from real-debrid when no filename is found in the local DB.
- Fixed real-debrid deserialization issue when checking for instant available files.
## [2.0.80] - 2024-07-13
### Changed
- Add rate limiter to retry requests that are rate limited from Real-Debrid.
- Optimize calls to Real-Debrid API when torrents are finished and periodic updates.
- Update to .NET 8.0.7
## [2.0.79] - 2024-06-03
### Changed
- Fixed issue with qBittorrent progress sometimes throwing errors.
## [2.0.78] - 2024-05-04
### Changed
- Fixed Aria2c download path issue when a category is set.
## [2.0.77] - 2024-05-03
### Changed
- Fixed Aria2c download path issue when a category is set.
## [2.0.76] - 2024-05-02
### Changed
- Fixed issues with the qBittorrent endpoint.
- Fixed issue that could crash the torrent runner.
## [2.0.75] - 2024-04-24
### Changed
- Fixed broken recursive symlink searching.
## [2.0.74] - 2024-04-20
### Added
- Added support for symlink recursive searching.
## [2.0.73] - 2024-04-11
### Changed
- Fixed another issue with the symlinker and file resolver.
## [2.0.72] - 2024-04-10
### Changed
- Fixed issue with download speed test when the symlink downloader is selected.
## [2.0.71] - 2024-04-10
### Changed
- Fixed symlink path matching bug.
## [2.0.70] - 2024-04-10
### Added
- Added symlink logging.
## [2.0.69] - 2024-04-09
### Added
- Added sorting to the GUI columns.
### Changed
- Fixed reloading on the /settings and other pages.
## [2.0.68] - 2024-04-09
### Changed
- Base Href middleware fix that throws error when a response is not 200.
## [2.0.67] - 2024-04-09
### Changed
- Symlink fixes.
## [2.0.66] - 2024-04-08
### Changed
- Symlink fixes and improvements.
## [2.0.65] - 2024-04-07
### Added
- Added option to configure the buffersize for the internal downloader.
## [2.0.64] - 2024-04-06
### Added
- Add log level Verbose and add logging for the internal downloader, only works when both log levels are set to Verbose.
### Changed
- Add fixes for the symlink downloader
- Add better indication when a torrent is stalled
- Fixed download client selection on the torrents
## [2.0.63] - 2024-03-05
### Changed
- When Sonarr/Radarr requests a torrent to be deleted, and its files too, then delete those files instead of ingoring it.
## [2.0.62] - 2024-02-17
### Changed
- Fixed reporting a torrent as error when some downloads have failed but still need to be retried.
- Fixed issue where downloads could get started over and over.
## [2.0.61] - 2024-01-21
### Added
- Added setting to include or exclude files based on a given regex.
- Add logging.
## [2.0.60] - 2024-01-21
### Changed
- Fixed bug where downloads could get stuck in active state while deleted.
- Use the RD folder structure when downloading a file.
## [2.0.59] - 2024-01-21
### Changed
- Added the simple downloader back and moved the current internal downloader to a new setting.
## [2.0.58] - 2024-01-10
### Changed
- Don't automatically count the chunk count.
## [2.0.57] - 2024-01-09
### Changed
- Fixed symlink retry.
## [2.0.56] - 2024-01-07
### Changed
- Add retry mechanism for the downloaders.
## [2.0.55] - 2024-01-07
### Changed
- Tweaked the internal downloader to prevent memory issues.
- Add retry mechanism for the symlink downloader.
## [2.0.54] - 2024-01-07
### Changed
- Added some logging for the symlink downloader to troubleshoot.
- Added some logging when deleting torrents and the symlinker overrides the finish action.
## [2.0.53] - 2024-01-05
### Added
- Add setting to set the download path on the aria2 instance.
## [2.0.52] - 2024-01-05
### Added
- Add BASE_PATH environment variable for the base path setting.
- Expose the Post Torrent Download Action setting on the Provider settings.
## [2.0.51] - 2024-01-05
### Added
- Added setting to store magnets and torrents to a directory after adding.
- Added bulk settings change on the index pages.
### Changed
- Swapped the internal downloader back to the one that was before, this one is giving too many headaches.
- Prevent deleting torrents from the debrid provider when the symlink downloader is used.
- Fixed %F parameter on the external program.
- Run the external program before the deletion process is ran.
- Remove the 100 char limit on inputs.
## [2.0.50] - 2023-11-25
### Changed
- Fixed Docker Builds for arm64.
## [2.0.49] - 2023-11-24
### Changed
- Fixed memory issue in internal downloader.
- Changed unpack process to handle cancels.
## [2.0.48] - 2023-11-15
### Changed
- Reverted dockerfile again as the packagemanager still doesn't have .net 8.
## [2.0.47] - 2023-11-15
### Changed
- Changed docker to use the package manager again.
## [2.0.46] - 2023-11-15
### Changed
- Fix in internal downloader.
## [2.0.45] - 2023-11-15
### Changed
- Optimizations to the internal downloader.
## [2.0.44] - 2023-11-15
### Changed
- Revert broken upgrade.
## [2.0.43] - 2023-11-15
### Changed
- Improvements to the internal downloader.
## [2.0.42] - 2023-11-14
### Changed
- Fixed docker build as .NET8 isn't published yet.
## [2.0.41] - 2023-11-14
### Changed
- Upgraded to .NET8 to see if downloader perf improves.
## [2.0.40] - 2023-10-03
### Changed
- Symlink downloader fixes.
## [2.0.39] - 2023-09-21
### Changed
- Fixed docker build
## [2.0.38] - 2023-09-20
### Added
- Added Symlink downloader to allow the use of rclone.
- Added extra logging.
- Added bulk retry on the index.
### Changed
- Lower password requirements
## [2.0.37] - 2023-08-03
### Changed
- Fixed docker build process.
## [2.0.36] - 2023-08-02
### Changed
- Fixed docker build process and upgraded to Node18.
## [2.0.35] - 2023-08-02
### Removed
- Docker arm32/v7 images because the base image does not support it anymore: https://www.linuxserver.io/blog/a-farewell-to-arm-hf
## [2.0.34] - 2023-08-01
### Changed
- Update docker image to alpine 3.14.
## [2.0.33] - 2023-08-01
### Changed
- Fixed sub folders on Premiumize.
- Fixed serialization errors on Premiumize.
- Filter illegal path and filename characters when setting the download paths.
### Added
- Add the option "Post Download Action" to the Torrent settings popup.
- Add a 2nd "Add Torrent" button on the add torrent page.
- Add the Aria2c downloader to the Docker container and set it as the default downloader when running in docker.
## [2.0.32] - 2023-06-27
### Changed
- Fixed the BaseURL content-length setting.
## [2.0.31] - 2023-06-11
### Added
- Added setting to set the BaseURL.
### Changed
- Added some logging.
## [2.0.30] - 2023-04-09
### Changed
- Improved the internal downloader bandwidth limiter.
## [2.0.29] - 2023-04-09
### Changed
- Testing Github actions automatic build to Docker.
## [2.0.28] - 2023-04-09
### Changed
- Added link to repository from the version link.
## [2.0.27] - 2023-04-08
### Changed
- Fixed Premiumize selecting of files.
- Updated internal downloader to report download speeds better.
## [2.0.26] - 2023-03-30
### Changed
- Add some logging for Premiumize to better understand errors.
- Changed Premiumize to download each file individually instead of creating a zip first.
- Swapped the internal downloader for a new version.
- Fixed issue when switching providers and it not switching over properly.
## [2.0.25] - 2023-03-17
### Changed
- Fixed docker run issues.
## [2.0.24] - 2023-03-16
### Changed
- Fixed docker run issues.
## [2.0.23] - 2023-03-15
### Changed
- Fixed docker build issues.
## [2.0.22] - 2023-03-11
### Added
- Add support for Premiumize
## [2.0.21] - 2023-03-09
### Changed
- Fixed docker build by pinning the .NET version to LTS.
## [2.0.20] - 2023-03-08
### Added
- Add support for multi-level unpacking.
- Add settings to specify the Error and Processed paths for the watch folders.
- Option to disable authentication completely.
### Changed
- Removed the Simple Downloader and replaced it with https://github.com/bezzad/Downloader as the Internal Downloader.
- Fixed setting the Base Href for sub folder hosting.
## [2.0.19] - 2022-10-18
### Changed
- Changed the AllDebrid provider to use HTTPS instead of HTTP.
## [2.0.18] - 2022-10-18
### Added
- Added the option to bulk delete torrents, thanks kanazaca!
- Added option to remove the torrent only from the client after downloads are completed.
- Added option to change the category of an existing torrent.
- Added option to not download files to the host.
### Changed
- If a watched file gives an error when adding, move it to an error folder.
## [2.0.17] - 2022-05-24
### Changed
- Fixed issue with some settings not saving.
## [2.0.16] - 2022-05-24
### Changed
- Fixed MacOS pre-fill on the login screen.
## [2.0.15] - 2022-05-15
### Changed
- Remove settings for Finish Action and Category for the qBittorrent integration, these should always be set to None and the category comes from the integration.
## [2.0.14] - 2022-05-14
### Changed
- Fixed Windows Service issue
- For service users: the appsettings.json is slightly changed: HostUrl is now Port. Important if you used a non standard (6500) port.
## [2.0.13] - 2022-05-14
### Changed
- Rewrote the settings storage. Added more settings to set defaults for importing.
- Fixed filtering of torrents when a category is passed to the TorrentsInfo endpoint, fixing Radarr/Sonarr integrations.
### Added
- Added settings to control the timeout and polling interval to RealDebrid/AllDebrid.
## [2.0.12] - 2022-03-20
### Changed
- Fixed the AllDebrid client.
## [2.0.11] - 2022-03-19
### Changed
- Fixed the "Progress" for the AllDebrid client, thanks @23doors.
## [2.0.10] - 2022-03-19
### Added
- When changing the download speed setting for the simple downloader it will apply the setting to active downloads.
- Add running of external applications when the torrent is finished.
### Changed
- Fixed deserialization of the availability check for RealDebrid.
- Fixed the simple downloader download limiter.
## [2.0.9] - 2022-03-12
### Changed
- Updated packages, added logging to the RD and AD providers when serialization fails.
## [2.0.8] - 2022-02-28
### Changed
- Fixed issue with AllDebrid sometimes returning NULL links.
## [2.0.7] - 2022-02-06
### Changed
- Added setting to set the category when a torrent is imported from RealDebrid or other provider.
## [2.0.6] - 2022-02-06
### Added
- Added setting to automatically delete torrents in the state of error after a certain amount of time.
- Added lifetime setting to automatically expire torrents after a certain amount of time.
## [2.0.5] - 2022-01-11
### Changed
- Updated AllDebrid provider to fix issue with ID's not being a number.
## [2.0.4] - 2022-01-08
### Changed
- Fixed bug where the QBittorrent API didn't report the error state correctly when an error ocurred in the debrid provider.
## [2.0.3] - 2022-01-02
### Changed
- Fixed automatic adding of AllDebrid torrents.
### Added
- Added update notification.
## [2.0.2] - 2021-11-24
### Changed
- Fixed update timer for providers.
## [2.0.1] - 2021-11-24
### Changed
- Fixed potential issue with the Real-Debrid mapper.
- Fixed serialization issue for AllDebrid.
## [2.0.0] - 2021-11-21
### Changed
- Update projects to .NET6 and Angular 13.
### Added
- Added setting to automatically import torrents from RealDebrid / AllDebrid.
- Added setting to automatically delete torrents from RealDebridClient when they have been removed from RealDebrid / AllDebrid.
## [1.9.8] - 2021-10-30
### Added
- Add speed limit setting on the Simple downloader.
## [1.9.7] - 2021-10-30
### Added
- Add AllDebrid support.
## [1.9.6] - 2021-10-30
### Added
- Improved handling of errors on the torrent itself
- Added retry counts when adding torrents
- Added retry counts on the Sonarr/Radarr settings page
## [1.9.5] - 2021-10-28
### Changed
- Fixed issues with the simple downloader.
- Fixed issue with retrying downloads.
### Added
- Added torrent uploading status from Real Debrid.
- Restored removing of torrents when deleted from Real Debrid.
## [1.9.4] - 2021-10-28
### Changed
- Fixed issues retrying torrents after multiple failed downloads in large torrents.
## [1.9.3] - 2021-10-27
### Changed
- Fixed issue with the unpack queue.
## [1.9.2] - 2021-10-27
### Changed
- Fixed issue where not the correct torrent was used for a download.
## [1.9.1] - 2021-10-27
### Added
- Added automatic torrent retrying when RealDebrid reports an error on the torrent.
### Changed
- Changed how Aria2 is polled, this will result in less RPC calls for a large amount of torrents.
- Add more and better logging for the torrent runner in Debug.
- Add Aria2 checks to see if links get added properly.
- Update Aria2.NET and RD.NET, added automatic retrying in case of server failures.
## [1.9.0] - 2021-10-24
### Added
- Add priorty for torrents. You can set the priority when adding a new torrent. When added from Sonarr/Radarr it will assume no priority.
- Added support for the Sonarr/Radarr/qBittorrent setPrio command. It will set the priority of a torrent to 1.
- Added support for the Sonarr/Radarr/qBittorrent pause and resume commands. This only works with the Aria2 downloader.
### Changed
- Fixed issues when downloads get deleted when they are still being checked.
- Improved timings and retrying of adding of downloads to Aria2. Will now try 5 times before failing.
## [1.8.9] - 2021-10-23
### Changed
- Add delays between adding downloads to Aria2 to avoid Aria2 going down when adding a large amount of downloads.
## [1.8.8] - 2021-10-21
### Changed
- Fixed starting downloads when RealDebrid reports ghost links in torrents.
## [1.8.7] - 2021-10-11
### Added
- Add Aria2 test connection button.
- Add full torrent retry mechanism, by default it will now retry the torrent 2 times when a download fails for more than 3 times.
### Changed
- Improved Aria2 download handling.
## [1.8.6] - 2021-10-09
### Added
- Experimental support for a Aria2 download client. Check the readme for usage.
### Changed
- Fixed potential error when sonarr is querying and the torrent isn't added to RealDebrid yet.
- Fixed interface randomly stop updating.
- Upgrade dependencies.
## [1.8.5] - 2021-10-07
### Changed
- Fixed issue where deleting a torrent could error out.
## [1.8.4] - 2021-08-05
### Changed
- Changed the default timeout for Real-Debrid communication to 10 seconds instead of 100 seconds.
## [1.8.3] - 2021-08-05
### Changed
- Fixed potential issue with duplicates categories.
## [1.8.2] - 2021-08-02
### Changed
- Fixed issue with starting downloads.
## [1.8.1] - 2021-07-31
### Changed
- Fixed issue where downloads were hanging the full interface.
## [1.8.0] - 2021-07-18
### Added
- Fixed support for categories. They are now saved persistently in the database.
- Added new "settings" page.
- Added new "add new" page. Added more options when manually adding a torrent, including the ability to manually select files.
- Added dedicated torrent pages with more information when selecting a torrent on the main page.
- Add ability to retry individual downloads.
### Changed
- Fixed enter key on the login and setup screen.
- Fixed an issue with selecting files and getting the links. This process could take a long time and would hang the client while waiting for a response.
### Removed
- Removed the retry and delete button from the main page and moved them to the torrent page.
- Removed the ability to retry all downloads.
## [1.7.8] - 2021-06-17
### Changed
- Fixed issue for real this time with a broken response from RDT when the available files returns in a format different than normal.
## [1.7.7] - 2021-06-09
### Changed
- Fixed some issues with download error handling.
- Fixed issue where files aren't always properly selected.
## [1.7.6] - 2021-06-05
### Changed
- Fixed build for Raspberry PI.
## [1.7.5] - 2021-06-05
### Changed
- Reduced the frequency of database reads and writes by adding caches for settings and torrents.
- Updated dependency of RD.NET to improve serialization error reporting.
- Changed how the base href is determined to support path proxies.
- Fixed issue with handling of renamed torrents.
## [1.7.4] - 2021-04-22
### Changed
- Changed how the docker is built.
## [1.7.3] - 2021-04-19
### Changed
- Fixed issue (hopefully) where sometimes the name of the torrent would change.
## [1.7.2] - 2021-04-14
### Changed
- Fixed issue where sometimes the content path is not recognized by Sonarr.
- Fixed reporting errors when a RD torrent errors.
## [1.7.1] - 2021-04-01
### Added
- Add Update.ps1 to automatically download the latest version from Github for Windows users.
## [1.7.0] - 2021-04-01
### Added
- Add automatic retry functionality for downloads. It will retry downloading a file after an error 3 times.
### Changed
- Fixed an issue downloads sometimes getting added multiple times.
## [1.6.3] - 2021-03-15
### Changed
- Fixed a bug where it sometimes could add a download multiple times when the RD torrent was in a certain state.
## [1.6.2] - 2021-03-13
### Added
- Add a retry button in the interface. Giving the possibility to re-download the torrent in RealDebrid or re-download the files locally.
### Changed
- Fixed some issues with deleting files and the files being in use.
- Fixed an issue with some torrents not downloading when finished.
### Removed
- Removed the "Download" and "Unpack" buttons from the interface.
## [1.6.1] - 2021-03-12
### Changed
- Fixed a bug in the torrent runner where it could download the same file multiple times.
## [1.6.0] - 2021-03-10
### Added
- Add an option to see which files are available on Real-Debrid on the add torrent modal.
### Changed
- Update the unrestrict process to only unrestrict links when a download is started instead of when the torrent is added.
### Removed
- Removed the "Auto download" and "Auto unpack" options, they are now always enabled by default.
## [1.5.6] - 2021-02-14
### Changed
- Updated all packages
- Updated the multi downloader, this should fix the insufficient disk space issue.
## [1.5.5] - 2021-02-12
### Added
- You can now enable debug logging through the setting modal. Debug will give you a lot of information, but also the best picture of what's going on. Default log level is warning to avoid creating big log files.
### Changed
- Fixed the bug where the unpack process would halt if there were downloads pending.
## [1.5.4] - 2021-02-11
### Changed
- Fixed an issue where auto extracting wasn't working.
## [1.5.3] - 2021-02-10
### Changed
- Fixed issue with selecting files when all files were filtered out.
## [1.5.2] - 2021-02-09
### Added
- Add ProxyServer to the Multipart download settings.
### Changed
- Fixed issue with RealDebrid API not returning a proper list of available files.
## [1.5.1] - 2021-02-06
### Changed
- Fixed bug with the OnlyDownloadAvailable files setting not showing correctly enabled in the setting modal.
## [1.5.0] - 2021-02-05
### Added
- Added a new setting to only download files that have been downloaded on Real Debrid, this will make downloading relevant files faster.
- Re-added the service-install.bat and service-remove.bat files to whoever wants to run as a service.
## [1.4.0] - 2021-01-13
### Added
- This release will now support the Docker image. You can still run it as a service as before.
### Changed
- Lots of changes, but the most notable is that the database is stored an a persistent storage on your host, configured through the docker file. This does mean that any torrents you are currently running will be removed from RDT.
- Sonarr and Radarr support have been greatly improved, especially the version 3 releases.
- The download/unpack queue process has been rewritten to be more robust.
## [1.3.0] - 2020-12-01
### Changed
- Sonarr and Radarr compatibility broke due to the missing files endpint, thank you @alexmckenley for adding the endpoint!
## [1.2.0] - 2020-10-05
### Changed
- Fixed small issues when adding torrents and not loading meta data.
## [1.1.0] - 2020-05-16
### Changed
- Small bug fixes
### Changed
- test
### Removed
- nothing
## [1.0.0] - 2020-04-11
### Added
- First release
- Add unraring progress and default auto download / auto remove options.

100
Dockerfile Normal file
View file

@ -0,0 +1,100 @@
# Stage 1 - Build the frontend
FROM node:lts-alpine AS node-build-env
ARG TARGETPLATFORM
ENV TARGETPLATFORM=${TARGETPLATFORM:-linux/amd64}
ARG BUILDPLATFORM
ENV BUILDPLATFORM=${BUILDPLATFORM:-linux/amd64}
RUN mkdir /appclient
WORKDIR /appclient
RUN apk add --no-cache git python3 py3-pip make g++
COPY client ./client
COPY root ./root
RUN \
cd client && \
echo "**** Building Code ****" && \
npm install && \
npx ng build --output-path=out
RUN ls -FCla /appclient/root
# Stage 2 - Build the backend
FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS dotnet-build-env
ARG TARGETPLATFORM
ENV TARGETPLATFORM=${TARGETPLATFORM:-linux/amd64}
ARG BUILDPLATFORM
ENV BUILDPLATFORM=${BUILDPLATFORM:-linux/amd64}
RUN mkdir /appserver
WORKDIR /appserver
COPY server ./server
RUN \
echo "**** Building Source Code for $TARGETPLATFORM on $BUILDPLATFORM ****" && \
cd server && \
dotnet restore --no-cache RdtClient.sln && \
dotnet test && \
dotnet publish --no-restore -c Release -o out ;
# Stage 3 - Build runtime image
FROM ghcr.io/linuxserver/baseimage-alpine:3.20
ARG TARGETPLATFORM
ENV TARGETPLATFORM=${TARGETPLATFORM:-linux/amd64}
ARG BUILDPLATFORM
ENV BUILDPLATFORM=${BUILDPLATFORM:-linux/amd64}
# set version label
ARG BUILD_DATE
ARG VERSION
LABEL build_version="Linuxserver.io extended version:- ${VERSION} Build-date:- ${BUILD_DATE}"
LABEL maintainer="ravensorb"
# set environment variables
ARG DEBIAN_FRONTEND="noninteractive"
ENV XDG_CONFIG_HOME="/config/xdg"
ENV RDTCLIENT_BRANCH="main"
RUN \
mkdir -p /data/downloads /data/db || true && \
echo "**** Updating package information ****" && \
apk update && \
echo "**** Install pre-reqs ****" && \
apk add bash icu-libs krb5-libs libgcc libintl libssl3 libstdc++ zlib && \
echo "**** Installing dotnet ****" && \
mkdir -p /usr/share/dotnet
RUN \
if [ "$TARGETPLATFORM" = "linux/arm/v7" ] ; then \
wget https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/10.0.0/aspnetcore-runtime-10.0.0-linux-musl-arm.tar.gz && \
tar zxf aspnetcore-runtime-10.0.0-linux-musl-arm.tar.gz -C /usr/share/dotnet ; \
elif [ "$TARGETPLATFORM" = "linux/arm64" ] ; then \
wget https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/10.0.0/aspnetcore-runtime-10.0.0-linux-musl-arm64.tar.gz && \
tar zxf aspnetcore-runtime-10.0.0-linux-musl-arm64.tar.gz -C /usr/share/dotnet ; \
else \
wget https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/10.0.0/aspnetcore-runtime-10.0.0-linux-musl-x64.tar.gz && \
tar zxf aspnetcore-runtime-10.0.0-linux-musl-x64.tar.gz -C /usr/share/dotnet ; \
fi
RUN \
echo "**** Setting permissions ****" && \
chown -R abc:abc /data && \
rm -rf \
/tmp/* \
/var/cache/apk/* \
/var/tmp/* || true
ENV PATH "$PATH:/usr/share/dotnet"
# Copy files for app
WORKDIR /app
COPY --from=dotnet-build-env /appserver/server/out .
COPY --from=node-build-env /appclient/client/out/browser ./wwwroot
COPY --from=node-build-env /appclient/root/ /
# ports and volumes
EXPOSE 6500
# Check Status
HEALTHCHECK --interval=30s --timeout=30s --start-period=30s --retries=3 CMD curl --fail http://localhost:6500 || exit

190
README-DOCKER.md Normal file
View file

@ -0,0 +1,190 @@
# [rogerfar/rdt-client](https://github.com/rogerfar/rdt-client)
Thid docker file follows the [linuxserver.io](https://linuxserver.io) pattern that leverages the [s6-overlay](https://github.com/just-containers/s6-overlay) to run the application as a service within the container. This allows for scripts to be run prior to start of the application to handle initalization and setting of permissions.
rdt-client is a web a web interface to manage your torrents on Real-Debrid. It supports the following features:
* Add new torrents through magnets or files
* Download all files from Real Debrid to your local machine automatically
* Unpack all files when finished downloading
* Implements a fake qBittorrent API so you can hook up other applications like Sonarr or Couchpotato.
* Built with Angular 11 and .NET 5
## Supported Architectures
Our images support multiple architectures such as `x86-64`, `arm64` and `armhf`. We utilise the docker manifest for multi-platform awareness. More information is available from docker [here](https://github.com/docker/distribution/blob/master/docs/spec/manifest-v2-2.md#manifest-list) and our announcement [here](https://blog.linuxserver.io/2019/02/21/the-lsio-pipeline-project/).
Simply pulling `rogerfar/rdtclient` should retrieve the correct image for your arch, but you can also pull specific arch images via tags.
The architectures supported by this image are:
| Architecture | Tag |
| :----: | --- |
| x86-64 | amd64-latest |
| arm64 | arm64v8-latest |
| armhf | arm32v7-latest |
## Version Tags
This image provides various versions that are available via tags. `latest` tag usually provides the latest stable version. Others are considered under development and caution must be exercised when using them.
| Tag | Description |
| :----: | --- |
| latest | Stable releases |
## Usage
Here are some example snippets to help you get started creating a container.
### docker-compose ([recommended](https://docs.linuxserver.io/general/docker-compose))
Compatible with docker-compose v2 schemas.
```yaml
---
version: '3.3'
services:
rdtclient:
image: rogerfar/rdtclient
container_name: rdtclient
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/London
volumes:
- <path to data>:/data/db
- <path to downloads>:/data/downloads
logging:
driver: json-file
options:
max-size: 10m
ports:
- 6500:6500
restart: unless-stopped
```
### docker cli
```
docker run -d \
--name=rdtclient \
-e PUID=1000 \
-e PGID=1000 \
-e TZ=Europe/London \
-p 6500:6500 \
-v <path to data>:/data/db \
-v <path/to/downloads>:/data/downloads \
--restart unless-stopped \
rogerfar/rdtclient
```
## Parameters
Container images are configured using parameters passed at runtime (such as those above). These parameters are separated by a colon and indicate `<external>:<internal>` respectively. For example, `-p 8080:80` would expose port `80` from inside the container to be accessible from the host's IP on port `8080` outside the container.
| Parameter | Function |
| :----: | --- |
| `-p 6500` | WebUI |
| `-e PUID=1000` | for UserID - see below for explanation |
| `-e PGID=1000` | for GroupID - see below for explanation |
| `-e TZ=Europe/London` | Specify a timezone to use EG Europe/London. |
| `-v /data/db` | App data. |
| `-v /data/downloads` | Location of downloads on disk. |
## Environment variables from files (Docker secrets)
You can set any environment variable from a file by using a special prepend `FILE__`.
As an example:
```
-e FILE__PASSWORD=/run/secrets/mysecretpassword
```
Will set the environment variable `PASSWORD` based on the contents of the `/run/secrets/mysecretpassword` file.
## Umask for running applications
For all of our images we provide the ability to override the default umask settings for services started within the containers using the optional `-e UMASK=022` setting.
Keep in mind umask is not chmod it subtracts from permissions based on it's value it does not add. Please read up [here](https://en.wikipedia.org/wiki/Umask) before asking for support.
## User / Group Identifiers
When using volumes (`-v` flags) permissions issues can arise between the host OS and the container, we avoid this issue by allowing you to specify the user `PUID` and group `PGID`.
Ensure any volume directories on the host are owned by the same user you specify and any permissions issues will vanish like magic.
In this instance `PUID=1000` and `PGID=1000`, to find yours use `id user` as below:
```
$ id username
uid=1000(dockeruser) gid=1000(dockergroup) groups=1000(dockergroup)
```
&nbsp;
## Application Setup
Webui can be found at `<your-ip>:6500`
## Support Info
* Shell access whilst the container is running: `docker exec -it rtdclient /bin/bash`
* To monitor the logs of the container in realtime: `docker logs -f rdtclient`
* container version number
* `docker inspect -f '{{ index .Config.Labels "build_version" }}' rdtclient`
* image version number
* `docker inspect -f '{{ index .Config.Labels "build_version" }}' rogerfar/rdtclient`
## Updating Info
Most of our images are static, versioned, and require an image update and container recreation to update the app inside. Please consult the [Application Setup](#application-setup) section above to see if it is recommended for the image.
Below are the instructions for updating containers:
### Via Docker Compose
* Update all images: `docker-compose pull`
* or update a single image: `docker-compose pull rdtclient`
* Let compose update all containers as necessary: `docker-compose up -d`
* or update a single container: `docker-compose up -d rdtclient`
* You can also remove the old dangling images: `docker image prune`
### Via Docker Run
* Update the image: `docker pull rogerfar/rdtclient`
* Stop the running container: `docker stop rdtclient`
* Delete the container: `docker rm rdtclient`
* Recreate a new container with the same docker run parameters as instructed above (if mapped correctly to a host folder, your `/data` folder and settings will be preserved)
* You can also remove the old dangling images: `docker image prune`
### Via Watchtower auto-updater (only use if you don't remember the original parameters)
* Pull the latest image at its tag and replace it with the same env variables in one run:
```
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower \
--run-once rtdclient
```
* You can also remove the old dangling images: `docker image prune`
**Note:** We do not endorse the use of Watchtower as a solution to automated updates of existing Docker containers. In fact we generally discourage automated updates. However, this is a useful tool for one-time manual updates of containers where you have forgotten the original parameters. In the long term, we highly recommend using [Docker Compose](https://docs.linuxserver.io/general/docker-compose).
### Image Update Notifications - Diun (Docker Image Update Notifier)
* We recommend [Diun](https://crazymax.dev/diun/) for update notifications. Other tools that automatically update containers unattended are not recommended or supported.
## Building locally
If you want to make local modifications to these images for development purposes or just to customize the logic:
```
git clone https://github.com/ravensorb/docker-rdtclient.git
cd docker-rdtclient
docker build \
--no-cache \
--pull \
-t rogerfar/rtd-client:latest .
```
The ARM variants can be built on x86_64 hardware using `multiarch/qemu-user-static`
```
docker run --rm --privileged multiarch/qemu-user-static:register --reset
```
Once registered you can define the dockerfile to use with `-f Dockerfile.aarch64`.

274
README.md
View file

@ -1,45 +1,255 @@
# Real-Debrid Torrent Client
# Real-Debrid Torrent & Usenet Client
This is a web interface to manage your torrents on Real-Debrid. It supports the following features:
This is a web interface to manage your torrents on Real-Debrid, AllDebrid, Premiumize, TorBox or DebridLink. It supports the following features:
- Add new torrents through magnet or files
- Download all files from Real Debrid to your local machine automatically
- Unpack all files when finished downloading
- Implements a fake qBittorrent API so you can hook up other applications like Sonarr or Couchpotato.
- Build with Angular 9 and .NET Core 3.1
- Add new torrents through magnets or files
- Add usenet downloads through NZB files (TorBox and Premiumize only)
- Download all files from Real-Debrid, AllDebrid, Premiumize or TorBox to your local machine automatically
- Unpack all files when finished downloading
- Implements a fake qBittorrent API so you can hook up other applications like Sonarr, Radarr or Couchpotato.
- Built with Angular 21 and .NET 10
**You will need a Premium service at Real-Debrid!**
**You will need a Premium service at Real-Debrid, AllDebrid, Premiumize, Torbox or DebridLink!**
## Installation
[Click here to sign up for Real-Debrid.](https://real-debrid.com/?id=1348683)
1. Unpack the latest release from the releases folder and run `startup.bat`. This will start the application on port 6500.
2. Browse to http://127.0.0.1:6500
3. The very first credentials you type in will be remembered for future logins.
4. Click on Settings on the top and enter your Real-Debrid API key.
5. Change your download path if needed.
6. To install as service on Windows, exit the console and run `serviceinstall.bat` as administrator.
[Click here to sign up for AllDebrid.](https://alldebrid.com/?uid=2v91l)
## Troubleshooting
[Click here to sign up for Premiumize.](https://www.premiumize.me/)
- If you forgot your logins simply delete the `database.db` and restart the service.
[Click here to sign up for TorBox.](https://torbox.app/subscription?referral=3d25018e-f30d-4c4b-a714-48c04bc76765)
## Connecting Sonarr
[Click here to sign up for DebridLink.](https://debrid-link.fr/id/6duif)
1. Login to Sonarr and click Settings
2. Go to the Download Client tab and click the plus to add
3. Click "qBittorrent" in the list
4. Enter the IP or hostname of the RealDebridClient in the Host field
5. Enter the port 6500 in the Port field
6. Enter your Username/Password you setup in step 3 above in the Username/Password field.
7. Leave the other settings as is.
8. Hit Test and then Save if all is well.
9. Sonarr will now think you have a regular Torrent client hooked up.
<sub>(referal links so I can get a few free premium days)</sub>
Notice: the progress and ETA reported in Sonarr's Activity tab will not be very accurate, but it will report the torrent as completed so it can be processed by Sonarr when done downloading.
## Docker Setup
Please see our separate Docker setup Read Me.
[Readme for Docker](README-DOCKER.md)
## Run as a Service
Instead of running in Docker you can install it as a service in Windows or Linux.
## Windows Service
1. Make sure you have the **ASP.NET Core Runtime 10.0.0** and the **SDK** installed: [https://dotnet.microsoft.com/download/dotnet/10.0](https://dotnet.microsoft.com/download/dotnet/10.0)
2. Get the latest zip file from the Releases page and extract it to your host.
3. Open the `appsettings.json` file and replace the `LogLevel` `Path` to a path on your host.
4. In `appsettings.json` replace the `Database` `Path` to a path on your host.
5. When using Windows paths, make sure to escape the slashes. For example: `D:\\RdtClient\\db\\rdtclient.db`
6. Do one of these:
* Run `RdtClient.Web.exe` to start the client.
* Run `service-install.bat` to install the client as a service. This will install `RdtClient.Web.exe` as a service which make the client start in the backgorund when the computer starts. (You probably want to do this if you are going to use this with Sonarr etc...)
## Linux Service
Instead of running in Docker you can install it as a service in Linux.
1. Install .NET: [https://docs.microsoft.com/en-us/dotnet/core/install/linux](https://docs.microsoft.com/en-us/dotnet/core/install/linux)
Ubuntu 20.04 example:
```wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb```
```sudo dpkg -i packages-microsoft-prod.deb```
```rm packages-microsoft-prod.deb```
```sudo apt-get update && sudo apt-get install -y dotnet-sdk-10.0```
2. Get latest archive from [releases](https://github.com/rogerfar/rdt-client/releases):
```wget <zip_url>```
3. Extract to path of your choice (~/rtdc in this example):
```unzip RealDebridClient.zip -d ~/rdtc && cd ~/rdtc```
4. In appsettings.json replace the Database Path to a path on your host. Any directories in path must already exist. Or just remove "/data/db/" for ease.
5. Test rdt client runs ok:
```dotnet RdtClient.Web.dll```
navigate to http://<ipaddress>:6500, if all is good then we'll create a service
6. Create a service (systemd in this example):
```sudo nano /etc/systemd/system/rdtc.service```
paste in this service file content and edit path of your directory:
```
[Unit]
Description=RdtClient Service
[Service]
WorkingDirectory=/home/<username>/rdtc
ExecStart=/usr/bin/dotnet RdtClient.Web.dll
SyslogIdentifier=RdtClient
User=<username>
[Install]
WantedBy=multi-user.target
```
7. enable and start the service:
```sudo systemctl daemon-reload```
```sudo systemctl enable rdtc```
```sudo systemctl start rdtc```
## Proxmox LXC
If you use Proxmox for your homelab, you can run rdt-client in a linux container (LXC), check it here:
[https://tteck.github.io/Proxmox/](https://tteck.github.io/Proxmox/)
## Setup
### First Login
1. Browse to [http://127.0.0.1:6500](http://127.0.0.1:6500) (or the path of your server).
1. The very first credentials you enter in will be remembered for future logins.
1. Click on `Settings` on the top and enter your Real-Debrid API key (found here: [https://real-debrid.com/apitoken](https://real-debrid.com/apitoken).
1. If you are using docker then the `Download path` setting needs to be the same as in your docker file mapping. By default this is `/data/downloads`. If you are using Windows, this is a path on your host.
1. Same goes for `Mapped path`, but this is the destination path from your docker mapping. This is a path on your host. For Windows, this will most likely be the same as the `Download path`.
1. Save your settings.
### Download Clients
Currently there 4 available download clients:
#### Bezzad Downloader
This [downloader](https://github.com/bezzad/Downloader) can be used to download files in parallel and with multiple chunks.
It has the following options:
- Download speed (in MB/s): This number indicates the speed in MB/s per download over all parallel downloads and chunks.
- Parallel connections per download: This number indicates how many parallel it will use per download. This can increase speed, recommended is no more than 8.
- Parallel chunks per download: This number indicates in how many chunks each download is split, recommended is no more than 8.
- Connection Timeout: This number indicates the timeout in milliseconds before a download chunk times out. It will retry each chunk 5 times before completely failing.
#### Aria2c downloader
This will use an external Aria2c downloader client. You will need to install this client yourself on your host, it is not included in the docker image.
It has the following options:
- Url: The full URL to your Aria2c service. This must end in /jsonrpc. A standard path is `http://192.168.10.2:6800/jsonrpc`.
- Secret: Optional secret to connecto to your Aria2c service.
If Aria2c is selected, none of the above options for `Internal Downloader` are used, you have to configure Aria2c manually.
#### Symlink downloader
Symlink downloader requires a rclone mount to be mounted into your filesystem. Be sure to keep the exact path to mounted files in other apps exactly
the same as used by rdt-client. Otherwise the symlinks wont resolve the file its trying to point to.
If the mount path folder cant be found the client wont start downloading anything.
Required configuration:
- Post Download Action = DO NOT SELECT REMOVE FROM PROVIDER
- Rclone mount path = /PATH_TO_YOUR_RCLONE_MOUNT/torrents/
Suggested configuration:
- Automatic retry downloads > 3
#### Synology Download Station
The Synology Download Station downloader uses an external Download Station server. You will need to set this up yourself.
It has the following options:
- Url: The URL to the Synology DownloadStation. A common URL is `http://127.0.0.1:5000`
- Username: The username to use when connecting to the Synology DownloadStation.
- Password: The password to use when connecting to the Synology DownloadStation.
- Download Path: The root path to download the file on the Synology DownloadStation host. If left empty, the default path configured on your Download Station server will be used.
### Troubleshooting
- If you forgot your logins simply delete the `rdtclient.db` and restart the service.
- A log file is written to your persistent path as `rdtclient.log`. When you run into issues please change the loglevel in your docker script to `Debug`.
### Connecting Sonarr/Radarr
RdtClient emulates the qBittorrent web protocol and allows applications to use those APIs. This way you can use Sonarr and Radarr to download directly from RealDebrid.
#### Torrents
1. Login to Sonarr or Radarr and click `Settings`.
1. Go to the `Download Client` tab and click the plus to add.
1. Click `qBittorrent` in the list.
1. Enter the IP or hostname of the RealDebridClient in the `Host` field.
1. Enter the 6500 in the `Port` field.
1. Enter your Username/Password you setup above in the Username/Password field.
1. Set the category to `sonarr` for Sonarr or `radarr` for Radarr.
1. Leave the other settings as is.
1. Hit `Test` and then `Save` if all is well.
1. Sonarr will now think you have a regular Torrent client hooked up.
When downloading files it will append the `category` setting in the Sonarr/Radarr Download Client setting. For example if your Remote Path setting is set to `C:\Downloads` and your Sonarr Download Client setting `category` is set to `sonarr` files will be downloaded to `C:\Downloads\sonarr`.
Notice: the progress and ETA reported in Sonarr's Activity tab will not be accurate, but it will report the torrent as completed so it can be processed after it is done downloading.
#### Usenet/NZB
RdtClient also emulates part of the SABnzbd API so Sonarr and Radarr can add NZB downloads. This requires a provider that supports Usenet/NZB downloads, currently TorBox or Premiumize.
1. Login to Sonarr or Radarr and click `Settings`.
1. Go to the `Download Client` tab and click the plus to add.
1. Click `SABnzbd` in the list.
1. Enter the IP or hostname of RdtClient in the `Host` field.
1. Enter `6500` in the `Port` field.
1. Enable `Use SSL` only if you access RdtClient through HTTPS.
1. Leave `URL Base` empty unless RdtClient is configured with a `BasePath`, for example `/rdt`.
1. If RdtClient authentication is enabled, leave `API Key` empty and enter your RdtClient username and password. If your client only supports an API key, enter `{username}:{password}` in `API Key`.
1. If RdtClient authentication is disabled, enter any value in `API Key`, for example `rdtclient`, and leave username/password empty.
1. Set the category to `sonarr` for Sonarr or `radarr` for Radarr.
1. Hit `Test` and then `Save` if all is well.
When importing completed NZB downloads, Sonarr/Radarr must be able to access the path reported by RdtClient. In Docker setups this may require a Remote Path Mapping from the RdtClient download path to the path mounted inside Sonarr/Radarr.
### Running within a folder
By default the application runs in the root of your hosted address (i.e. https://rdt.myserver.com/), but if you want to run it as a relative folder (i.e. https://myserver.com/rdt) you will have to change the `BasePath` setting in the `appsettings.json` file. You can set the `BASE_PATH` environment variable for docker enviroments.
## Build instructions
1. Open the client folder project in VS Code and run `npm install`
2. To debug run `ng serve`, to build run `ng build --prod`
3. Open the Visual Studio 2019 project `RdtClient.sln` and `Publish`
4. The result is found in `\rdt-client\server\RdtClient.Web\bin\Release\netcoreapp3.1\publish`
### Prerequisites
- NodeJS
- NPM
- Angular CLI
- .NET 10
- Visual Studio 2025
- (optional) Resharper
### Dev Container
The repository includes a dev container under `.devcontainer/` for the split development workflow used by this project.
It installs .NET 10 and Node 22, forwards ports `4200` and `6500`, and persists `/data/db` and `/data/downloads` in named volumes so the local SQLite database, logs, and downloads survive container rebuilds.
1. Open the repository in the dev container.
1. In one terminal run `dotnet watch run --project server/RdtClient.Web`.
1. In another terminal run `cd client && npm start`.
1. Open `http://localhost:4200`. The Angular dev server proxies `/Api` and `/hub` to the backend running on `6500`.
1. Open the client folder project in VS Code and run `npm install`.
1. To debug run `ng serve`, to build run `ng build -c production`.
1. Open the Visual Studio 2025 project `RdtClient.sln` and `Publish` the `RdtClient.Web` to the given `PublishFolder` target.
1. When debugging, make sure to run `RdtClient.Web.dll` and not `IISExpress`.
1. The result is found in `Publish`.
## Build docker container
1. In the root of the project run `docker build --tag rdtclient .`
1. To create the docker container run `docker run --publish 6500:6500 --detach --name rdtclientdev rdtclient:latest`
1. To stop: `docker stop rdtclient`
1. To remove: `docker rm rdtclient`
1. Or use `docker-build.bat`
## Misc Install Notes
### Rootless Podman, Linux Host, and CIFS Connections
RDT Client read and write permission tests fail if the CIFS connection is not setup properly, despite permissions working inspection. In the Web GUI, it will report access denied, and in the log file you will see exceptions like this ([dotnet issue](https://github.com/dotnet/runtime/issues/42790)):
```
System.IO.IOException: Permission denied
```
The **nobrl** has to be specified in your CIFS connection - [man page](https://linux.die.net/man/8/mount.cifs).
Example: ```Options=_netdev,credentials=/etc/samba/credentials/600file,rw,uid=SUBUID,gid=SBUGID,nobrl,file_mode=0770,dir_mode=0770,noperm```

View file

@ -7,6 +7,7 @@ indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
max_line_length = 120
[*.ts]
quote_type = single

37
client/.eslintrc.json Normal file
View file

@ -0,0 +1,37 @@
{
"root": true,
"ignorePatterns": ["projects/**/*"],
"overrides": [
{
"files": ["*.ts"],
"parserOptions": {
"project": ["tsconfig.json"],
"createDefaultProgram": true
},
"extends": ["plugin:@angular-eslint/recommended", "plugin:@angular-eslint/template/process-inline-templates"],
"rules": {
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "app",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "app",
"style": "kebab-case"
}
]
}
},
{
"files": ["*.html"],
"extends": ["plugin:@angular-eslint/template/recommended"],
"rules": {}
}
]
}

46
client/.gitignore vendored
View file

@ -0,0 +1,46 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out
# dependencies
/node_modules
# profiling files
chrome-profiler-events*.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.angular/cache
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db

14
client/.ncurc.js Normal file
View file

@ -0,0 +1,14 @@
const minorOnly = ["eslint", "typescript"];
const patchOnly = [];
module.exports = {
target: (name) => {
if (minorOnly.indexOf(name) > -1) {
return "minor";
}
if (patchOnly.indexOf(name) > -1) {
return "patch";
}
return "latest";
},
};

2
client/.prettierignore Normal file
View file

@ -0,0 +1,2 @@
.angular
.vscode

View file

@ -2,5 +2,10 @@
"/Api": {
"target": "http://localhost:6500/",
"secure": false
},
"/hub": {
"target": "http://localhost:6500/",
"secure": false,
"ws": true
}
}

View file

@ -3,11 +3,33 @@
"version": 1,
"newProjectRoot": "projects",
"projects": {
"rdt-client": {
"client": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
"style": "scss",
"skipTests": true
},
"@schematics/angular:class": {
"skipTests": true
},
"@schematics/angular:directive": {
"skipTests": true
},
"@schematics/angular:guard": {
"skipTests": true
},
"@schematics/angular:interceptor": {
"skipTests": true
},
"@schematics/angular:pipe": {
"skipTests": true
},
"@schematics/angular:service": {
"skipTests": true
},
"@schematics/angular:application": {
"strict": true
}
},
"root": "",
@ -15,104 +37,115 @@
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"builder": "@angular/build:application",
"options": {
"outputPath": "../server/RdtClient.Web/wwwroot",
"outputPath": {
"base": "../server/RdtClient.Web/wwwroot",
"browser": ""
},
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"polyfills": ["src/polyfills.ts"],
"tsConfig": "tsconfig.app.json",
"aot": true,
"inlineStyleLanguage": "scss",
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.scss"],
"scripts": []
"scripts": [],
"browser": "src/main.ts",
"stylePreprocessorOptions": {
"sass": {
"silenceDeprecations": ["if-function"]
}
}
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "2mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"builder": "@angular/build:dev-server",
"options": {
"browserTarget": "rdt-client:build",
"proxyConfig": "angular-proxy.json"
},
"configurations": {
"production": {
"browserTarget": "rdt-client:build:production"
"buildTarget": "client:build:production"
},
"development": {
"buildTarget": "client:build:development"
}
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"builder": "@angular/build:extract-i18n",
"options": {
"browserTarget": "rdt-client:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.scss"],
"scripts": []
"buildTarget": "client:build"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"builder": "@angular-eslint/builder:lint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"e2e/tsconfig.json"
],
"exclude": ["**/node_modules/**"]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "rdt-client:serve"
},
"configurations": {
"production": {
"devServerTarget": "rdt-client:serve:production"
}
"lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
}
}
}
}
},
"defaultProject": "rdt-client"
"cli": {
"analytics": false,
"schematicCollections": ["@angular-eslint/schematics"]
},
"schematics": {
"@schematics/angular:component": {
"type": "component"
},
"@schematics/angular:directive": {
"type": "directive"
},
"@schematics/angular:service": {
"type": "service"
},
"@schematics/angular:guard": {
"typeSeparator": "."
},
"@schematics/angular:interceptor": {
"typeSeparator": "."
},
"@schematics/angular:module": {
"typeSeparator": "."
},
"@schematics/angular:pipe": {
"typeSeparator": "."
},
"@schematics/angular:resolver": {
"typeSeparator": "."
}
}
}

View file

@ -1,12 +0,0 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# You can see what browsers were selected by your queries by running:
# npx browserslist
> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 9-11 # For IE 9-11 support, remove 'not'.

View file

@ -1,32 +0,0 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, './coverage/rdt-client'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

22705
client/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,51 +1,54 @@
{
"name": "rdt-client",
"name": "client",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"watch": "ng build --watch --configuration development",
"update": "ng update --force --allow-dirty @angular/cli @angular/core @angular/cdk",
"lint": "ng lint",
"e2e": "ng e2e"
"prettier": "prettier --write .",
"precommit": "npm run prettier && npm run lint"
},
"private": true,
"dependencies": {
"@angular/animations": "~9.1.1",
"@angular/common": "~9.1.1",
"@angular/compiler": "~9.1.1",
"@angular/core": "~9.1.1",
"@angular/forms": "~9.1.1",
"@angular/platform-browser": "~9.1.1",
"@angular/platform-browser-dynamic": "~9.1.1",
"@angular/router": "~9.1.1",
"@fortawesome/fontawesome-free": "^5.13.0",
"bulma": "^0.8.1",
"curray": "^1.0.7",
"ngx-filesize": "^2.0.13",
"rxjs": "~6.5.5",
"tslib": "^1.11.1",
"zone.js": "~0.10.3"
"@angular/animations": "^21.2.10",
"@angular/cdk": "^21.2.8",
"@angular/common": "^21.2.10",
"@angular/compiler": "^21.2.10",
"@angular/core": "^21.2.10",
"@angular/forms": "^21.2.10",
"@angular/platform-browser": "^21.2.10",
"@angular/platform-browser-dynamic": "^21.2.10",
"@angular/router": "^21.2.10",
"@fortawesome/fontawesome-free": "^7.2.0",
"@microsoft/signalr": "^10.0.0",
"bulma": "^1.0.4",
"curray": "^1.0.12",
"file-saver-es": "^2.0.5",
"filesize": "^11.0.17",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "^0.16.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.901.1",
"@angular/cli": "~9.1.1",
"@angular/compiler-cli": "~9.1.1",
"@angular/language-service": "~9.1.1",
"@types/node": "^13.11.1",
"@types/jasmine": "~3.5.10",
"@types/jasminewd2": "~2.0.8",
"codelyzer": "^5.2.2",
"jasmine-core": "~3.5.0",
"jasmine-spec-reporter": "~5.0.1",
"karma": "~5.0.1",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~2.1.1",
"karma-jasmine": "~3.1.1",
"karma-jasmine-html-reporter": "^1.5.3",
"protractor": "~5.4.3",
"ts-node": "~8.8.2",
"tslint": "~6.1.1",
"typescript": "~3.8.3"
"@angular-eslint/builder": "21.3.1",
"@angular-eslint/eslint-plugin": "21.3.1",
"@angular-eslint/eslint-plugin-template": "21.3.1",
"@angular-eslint/schematics": "21.3.1",
"@angular-eslint/template-parser": "21.3.1",
"@angular/build": "^21.2.8",
"@angular/cli": "^21.2.8",
"@angular/compiler-cli": "^21.2.10",
"@angular/language-service": "^21.2.10",
"@types/file-saver": "^2.0.7",
"@types/file-saver-es": "^2.0.3",
"@types/node": "^25.6.0",
"@typescript-eslint/eslint-plugin": "^8.59.1",
"@typescript-eslint/parser": "^8.59.1",
"eslint": "^9.39.4",
"prettier": "^3.8.3",
"typescript": "5.9.3"
}
}

View file

@ -0,0 +1,301 @@
<div class="field">
<div class="tabs is-toggle is-fullwidth">
<ul>
<li [class.is-active]="type === 'torrent'">
<a (click)="changeType('torrent')">
<span class="icon is-small"><i class="fas fa-magnet" aria-hidden="true"></i></span>
<span>Torrent</span>
</a>
</li>
<li [class.is-active]="type === 'nzb'">
<a (click)="changeType('nzb')">
<span class="icon is-small"><i class="fas fa-file-archive" aria-hidden="true"></i></span>
<span>NZB</span>
</a>
</li>
</ul>
</div>
</div>
<div class="field">
<div [hidden]="type !== 'torrent'">
<div class="field">
<label class="label">Torrent file</label>
<div class="file has-name">
<label class="file-label">
<input class="file-input" type="file" name="resume" (change)="pickFile($event)" [disabled]="saving" />
<span class="file-cta">
<span class="file-icon">
<i class="fas fa-upload"></i>
</span>
<span class="file-label"> Pick a torrent file... </span>
</span>
<span class="file-name">
{{ fileName }}
</span>
</label>
</div>
</div>
<div class="field">
<label class="label">Magnet Link</label>
<div class="control">
<textarea
class="textarea"
placeholder="Paste your magnet link here"
[(ngModel)]="magnetLink"
[disabled]="saving"
(blur)="checkFiles()"
(paste)="onPaste()"
></textarea>
</div>
</div>
</div>
<div [hidden]="type !== 'nzb'">
<div class="field">
<label class="label">NZB file</label>
<div class="file has-name">
<label class="file-label">
<input class="file-input" type="file" name="resume" (change)="pickFile($event)" [disabled]="saving" />
<span class="file-cta">
<span class="file-icon">
<i class="fas fa-upload"></i>
</span>
<span class="file-label"> Pick an NZB file... </span>
</span>
<span class="file-name">
{{ fileName }}
</span>
</label>
</div>
</div>
<div class="field">
<label class="label">NZB Link</label>
<div class="control">
<textarea
class="textarea"
placeholder="Paste your NZB link here"
[(ngModel)]="nzbLink"
[disabled]="saving"
></textarea>
</div>
</div>
</div>
<div class="field" style="margin-top: 10px">
<div class="control">
<button class="button is-success" [disabled]="saving" [ngClass]="{ 'is-loading': saving }" (click)="ok()">
@if (type === "torrent") {
<span>Add Torrent</span>
} @else {
<span>Add NZB</span>
}
</button>
</div>
</div>
<div>
<div class="separator">Advanced settings</div>
<div class="field">
<label class="label">Downloader</label>
<div class="control select is-fullwidth">
<select [(ngModel)]="downloadClient" (ngModelChange)="setFinishAction()">
<option [ngValue]="0">Bezzad</option>
<option [ngValue]="1">Aria2c</option>
<option [ngValue]="2">Symlink Downloader</option>
<option [ngValue]="3">Synology DownloadStation</option>
</select>
</div>
<p class="help">Select which downloader is used to download this from the debrid provider to your host.</p>
</div>
<div class="field">
<label class="label">Post Download Action</label>
<div class="control select is-fullwidth">
<select [(ngModel)]="hostDownloadAction">
<option [ngValue]="0">Download all files to host</option>
<option [ngValue]="1">Don't download any files to host</option>
</select>
</div>
<p class="help">
When a download is finished on the provider, perform this action. Use this setting if you only want to add files
to your debrid provider but not download them to the host.
</p>
</div>
<div class="field">
<label class="label">Minimum file size to download</label>
<div class="control">
<div class="field has-addons" style="margin-bottom: 0">
<div class="control is-expanded">
<input class="input" type="number" max="1000" min="0" step="1" [(ngModel)]="downloadMinSize" />
</div>
<div class="control">
<a class="button is-static">MB</a>
</div>
</div>
</div>
@if (provider === "AllDebrid") {
<p class="help">
When downloading with AllDebrid it cannot be guaranteed that only files above this limit will be download as
some files are grouped together in 1 large archive.
</p>
}
@if (downloadAction === 2) {
<p class="help">This setting does not apply to manually selected files.</p>
}
</div>
<div class="field">
<label class="label">Include files</label>
<div class="control">
<div class="field" style="margin-bottom: 0">
<div class="control is-expanded">
<input class="input" type="text" [(ngModel)]="includeRegex" (blur)="verifyRegex()" />
</div>
</div>
</div>
<p class="help">
Select only the files that are matching this regular expression. Only use this setting OR the Exclude files
setting, not both.
</p>
@if (downloadAction === 2) {
<p class="help">This setting does not apply to manually selected files.</p>
}
@if (includeRegexError) {
<p class="help is-danger">{{ includeRegexError }}</p>
}
</div>
<div class="field">
<label class="label">Exclude files</label>
<div class="control">
<div class="field" style="margin-bottom: 0">
<div class="control is-expanded">
<input class="input" type="text" [(ngModel)]="excludeRegex" (blur)="verifyRegex()" />
</div>
</div>
</div>
<p class="help">
Ignore files that are matching this regular expression. Only use this setting OR the Include files setting, not
both.
</p>
@if (downloadAction === 2) {
<p class="help">This setting does not apply to manually selected files.</p>
}
@if (excludeRegexError) {
<p class="help is-danger">{{ excludeRegexError }}</p>
}
</div>
<div class="field">
<label class="label">Finished action</label>
<div class="control select is-fullwidth">
<select [(ngModel)]="finishedAction">
<option [ngValue]="0">Do nothing</option>
@if (downloadClient !== 2) {
<option [ngValue]="1">Remove from provider and client</option>
<option [ngValue]="2">Remove from provider</option>
}
<option [ngValue]="3">Remove from client</option>
</select>
</div>
</div>
<div class="field">
<label class="label">Finished Action Delay (minutes)</label>
<div class="control is-fullwidth">
<input class="input" type="number" [(ngModel)]="finishedActionDelay" />
</div>
<p class="help">
When a download is finished on the provider, perform this action. Use this setting if you only want to add files
to your debrid provider but not download them to the host.
</p>
</div>
<div class="field">
<label class="label">Category</label>
<div class="control category-combo">
<input
class="input"
type="text"
[(ngModel)]="category"
(focus)="categoryDropdownOpen = true"
(input)="categoryDropdownOpen = true"
(blur)="onCategoryBlur()"
placeholder="Type or select a category"
autocomplete="off"
/>
@if (categoryDropdownOpen && filteredCategories.length > 0) {
<div class="category-dropdown">
@for (cat of filteredCategories; track $index) {
<a class="category-option" (mousedown)="selectCategory(cat)">{{ cat }}</a>
}
</div>
}
</div>
<p class="help">The category becomes a sub-folder in your main download path.</p>
</div>
<div class="field">
<label class="label">Priority</label>
<div class="control">
<input class="input" type="number" step="1" [(ngModel)]="priority" />
</div>
<p class="help">Set the priority where 1 is the highest. When empty it will be assigned the lowest priority.</p>
</div>
</div>
</div>
<div class="field">
<div>
<div class="field">
<label class="label">Automatic retry downloads</label>
<div class="control">
<input class="input" type="number" max="1000" min="0" step="1" [(ngModel)]="downloadRetryAttempts" />
</div>
<p class="help">When a single download fails it will retry it this many times before marking it as failed.</p>
</div>
<div class="field">
<label class="label">Automatic retry download</label>
<div class="control">
<input class="input" type="number" max="1000" min="0" step="1" [(ngModel)]="torrentRetryAttempts" />
</div>
<p class="help">
When a single download has failed multiple times (see setting above) or when the download itself received an
error it will retry the full download this many times before marking it failed.
</p>
</div>
<div class="field">
<label class="label">Delete download when in error</label>
<div class="control">
<input class="input" type="number" max="100000" min="0" step="1" [(ngModel)]="torrentDeleteOnError" />
</div>
<p class="help">
When a download has been in error for this many minutes, delete it from the provider and the client. 0 to
disable.
</p>
</div>
<div class="field">
<label class="label">Maximum lifetime</label>
<div class="control">
<input class="input" type="number" max="100000" min="0" step="1" [(ngModel)]="torrentLifetime" />
</div>
<p class="help">
The maximum lifetime of a download in minutes. When this time has passed, mark it as error. If the download is
completed, the lifetime setting will not apply. 0 to disable.
</p>
</div>
</div>
</div>
<div class="field">
<div class="control">
@if (error) {
<div class="notification is-danger is-light">{{ error }}</div>
}
</div>
</div>

View file

@ -0,0 +1,55 @@
.file-name {
max-width: 24em;
width: 24em;
}
.separator {
display: flex;
align-items: center;
text-align: center;
color: darkgray;
font-size: 10px;
margin-top: 12px;
margin-bottom: 12px;
}
.separator::before,
.separator::after {
content: "";
flex: 1;
border-bottom: 1px solid darkgray;
}
.separator:not(:empty)::before {
margin-right: 0.25em;
}
.separator:not(:empty)::after {
margin-left: 0.25em;
}
.category-combo {
position: relative;
}
.category-dropdown {
position: absolute;
z-index: 10;
width: 100%;
background: white;
border: 1px solid #dbdbdb;
border-top: none;
border-radius: 0 0 4px 4px;
max-height: 200px;
overflow-y: auto;
}
.category-option {
display: block;
padding: 0.375rem 0.75rem;
color: #363636;
&:hover {
background: #f5f5f5;
}
}

View file

@ -0,0 +1,333 @@
import { Component, DestroyRef, OnInit, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { Router } from '@angular/router';
import { DownloadType, Torrent, TorrentFileAvailability } from '../models/torrent.model';
import { SettingsService } from '../settings.service';
import { ActivatedRoute } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { NgClass } from '@angular/common';
import { TorrentService } from '../torrent.service';
@Component({
selector: 'app-add-new-torrent',
templateUrl: './add-new-torrent.component.html',
styleUrls: ['./add-new-torrent.component.scss'],
imports: [FormsModule, NgClass],
standalone: true,
})
export class AddNewTorrentComponent implements OnInit {
private destroyRef = inject(DestroyRef);
private router = inject(Router);
private torrentService = inject(TorrentService);
private settingsService = inject(SettingsService);
private activatedRoute = inject(ActivatedRoute);
public type: 'torrent' | 'nzb' = 'torrent';
public fileName: string;
public magnetLink: string;
public nzbLink: string;
private currentTorrentFile: string;
public provider: string;
public downloadClient: number;
private _category = '';
public categories: string[] = [];
public filteredCategories: string[] = [];
public categoryDropdownOpen = false;
public hostDownloadAction: number = 0;
public downloadAction: number = 0;
public finishedAction: number = 0;
public finishedActionDelay: number = 0;
public downloadMinSize: number = 0;
public includeRegex: string = '';
public excludeRegex: string = '';
public torrentRetryAttempts: number = 1;
public downloadRetryAttempts: number = 3;
public torrentDeleteOnError: number = 0;
public torrentLifetime: number = 0;
public priority: number;
public availableFiles: TorrentFileAvailability[];
public downloadFiles: { [key: string]: boolean } = {};
public allSelected: boolean;
public saving = false;
public error: string;
public includeRegexError: string;
public excludeRegexError: string;
public regexSelected: TorrentFileAvailability[];
private selectedFile: File | null = null;
public get category(): string {
return this._category;
}
public set category(value: string) {
this._category = value;
this.updateFilteredCategories();
}
ngOnInit(): void {
this.activatedRoute.queryParams.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((params) => {
if (params['type'] === 'nzb') {
this.type = 'nzb';
} else if (params['type'] === 'torrent') {
this.type = 'torrent';
}
if (params['magnet']) {
this.magnetLink = decodeURIComponent(params['magnet']);
this.type = 'torrent';
}
if (params['nzb']) {
this.nzbLink = decodeURIComponent(params['nzb']);
this.type = 'nzb';
}
});
this.settingsService
.get()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((settings) => {
const providerSetting = settings.find((m) => m.key === 'Provider:Provider');
this.provider = providerSetting.enumValues[providerSetting.value as number];
this.downloadClient = settings.find((m) => m.key === 'DownloadClient:Client')?.value as number;
this.category = settings.find((m) => m.key === 'Gui:Default:Category')?.value as string;
const categoriesSetting = settings.find((m) => m.key === 'General:Categories')?.value as string;
this.categories = (categoriesSetting ?? '')
.split(',')
.map((c) => c.trim())
.filter((c) => c.length > 0)
.filter((c, i, arr) => arr.findIndex((a) => a.toLowerCase() === c.toLowerCase()) === i);
const matchedCategory = this.categories.find((c) => c.toLowerCase() === (this.category ?? '').toLowerCase());
if (matchedCategory) {
this.category = matchedCategory;
} else {
this.updateFilteredCategories();
}
this.hostDownloadAction = this.downloadAction = settings.find((m) => m.key === 'Gui:Default:HostDownloadAction')
?.value as number;
this.downloadAction =
settings.find((m) => m.key === 'Gui:Default:OnlyDownloadAvailableFiles')?.value === true ? 1 : 0;
this.finishedAction = settings.find((m) => m.key === 'Gui:Default:FinishedAction')?.value as number;
this.finishedActionDelay = settings.find((m) => m.key == 'Gui:Default:FinishedActionDelay')?.value as number;
this.downloadMinSize = settings.find((m) => m.key === 'Gui:Default:MinFileSize')?.value as number;
this.includeRegex = settings.find((m) => m.key === 'Gui:Default:IncludeRegex')?.value as string;
this.excludeRegex = settings.find((m) => m.key === 'Gui:Default:ExcludeRegex')?.value as string;
this.torrentRetryAttempts = settings.find((m) => m.key === 'Gui:Default:TorrentRetryAttempts')?.value as number;
this.downloadRetryAttempts = settings.find((m) => m.key === 'Gui:Default:DownloadRetryAttempts')
?.value as number;
this.torrentDeleteOnError = settings.find((m) => m.key === 'Gui:Default:DeleteOnError')?.value as number;
this.torrentLifetime = settings.find((m) => m.key === 'Gui:Default:TorrentLifetime')?.value as number;
this.priority = settings.find((m) => m.key === 'Gui:Default:Priority')?.value as number;
this.setFinishAction();
});
}
private updateFilteredCategories(): void {
if (!this.category) {
this.filteredCategories = this.categories;
return;
}
const search = this.category.toLowerCase();
this.filteredCategories = this.categories.filter((value) => value.toLowerCase().includes(search));
}
public selectCategory(cat: string): void {
this.category = cat;
this.categoryDropdownOpen = false;
}
public onCategoryBlur(): void {
setTimeout(() => (this.categoryDropdownOpen = false), 150);
}
public setFinishAction() {
if (this.downloadClient === 2) {
if (this.finishedAction === 1) {
this.finishedAction = 3;
} else if (this.finishedAction === 2) {
this.finishedAction = 0;
}
}
}
public changeType(type: 'torrent' | 'nzb'): void {
this.type = type;
this.fileName = null;
this.selectedFile = null;
}
public pickFile(evt: Event): void {
const files = (evt.target as HTMLInputElement).files;
if (files == null || files.length === 0) {
return;
}
const file = files[0];
this.fileName = file.name;
this.selectedFile = file;
this.checkFiles();
}
public ok(): void {
this.saving = true;
this.error = null;
let downloadManualFiles: string = null;
if (this.downloadAction === 2) {
const selectedFiles = [];
for (const filePath in this.downloadFiles) {
if (this.downloadFiles[filePath] === true) {
selectedFiles.push(filePath);
}
}
if (selectedFiles.length === 0) {
this.error = 'No files have been selected to download';
return;
}
downloadManualFiles = selectedFiles.join(',');
}
const torrent = new Torrent();
torrent.category = this.category;
torrent.hostDownloadAction = this.hostDownloadAction;
torrent.downloadAction = this.downloadAction;
torrent.finishedAction = this.finishedAction;
torrent.finishedActionDelay = this.finishedActionDelay;
torrent.downloadMinSize = this.downloadMinSize;
torrent.includeRegex = this.includeRegex;
torrent.excludeRegex = this.excludeRegex;
torrent.downloadManualFiles = downloadManualFiles;
torrent.priority = this.priority;
torrent.torrentRetryAttempts = this.torrentRetryAttempts;
torrent.downloadRetryAttempts = this.downloadRetryAttempts;
torrent.deleteOnError = this.torrentDeleteOnError;
torrent.lifetime = this.torrentLifetime;
torrent.downloadClient = this.downloadClient;
if (this.type === 'torrent') {
torrent.type = DownloadType.Torrent;
if (this.magnetLink) {
this.torrentService.uploadMagnet(this.magnetLink, torrent).subscribe({
next: () => this.router.navigate(['/']),
error: (err) => {
this.error = err.error;
this.saving = false;
},
});
} else if (this.selectedFile) {
this.torrentService.uploadFile(this.selectedFile, torrent).subscribe({
next: () => this.router.navigate(['/']),
error: (err) => {
this.error = err.error;
this.saving = false;
},
});
} else {
this.error = 'No magnet or file uploaded';
this.saving = false;
}
} else {
if (this.nzbLink) {
this.torrentService.uploadNzbLink(this.nzbLink, torrent).subscribe({
next: () => this.router.navigate(['/']),
error: (err) => {
this.error = err.error;
this.saving = false;
},
});
} else if (this.selectedFile) {
this.torrentService.uploadNzbFile(this.selectedFile, torrent).subscribe({
next: () => this.router.navigate(['/']),
error: (err) => {
this.error = err.error;
this.saving = false;
},
});
} else {
this.error = 'No NZB link or file uploaded';
this.saving = false;
}
}
}
public onPaste(): void {
setTimeout(() => {
this.checkFiles();
}, 100);
}
public checkFiles(): void {
if (this.type === 'nzb') {
return;
}
if (this.magnetLink && this.magnetLink === this.currentTorrentFile) {
return;
}
this.saving = true;
this.error = null;
this.availableFiles = null;
this.downloadFiles = {};
this.allSelected = true;
if (this.magnetLink) {
this.torrentService.checkFilesMagnet(this.magnetLink).subscribe({
next: (result) => {
this.saving = false;
this.availableFiles = result;
this.currentTorrentFile = this.magnetLink;
result.forEach((file) => {
this.downloadFiles[file.filename] = true;
});
},
error: (err) => {
this.error = err.error;
this.saving = false;
},
});
} else if (this.selectedFile) {
this.torrentService.checkFiles(this.selectedFile).subscribe({
next: (result) => {
this.saving = false;
this.availableFiles = result;
result.forEach((file) => {
this.downloadFiles[file.filename] = true;
});
},
error: (err) => {
this.error = err.error;
this.saving = false;
},
});
} else {
this.saving = false;
}
}
public verifyRegex(): void {
this.includeRegexError = null;
this.excludeRegexError = null;
this.regexSelected = null;
this.torrentService.verifyRegex(this.includeRegex, this.excludeRegex, this.magnetLink).subscribe((result) => {
this.includeRegexError = result.includeError;
this.excludeRegexError = result.excludeError;
this.regexSelected = result.selectedFiles;
});
}
}

View file

@ -1,17 +1,52 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { MainLayoutComponent } from './main-layout/main-layout.component';
import { RouterModule, Routes } from '@angular/router';
import { AddNewTorrentComponent } from './add-new-torrent/add-new-torrent.component';
import { authResolver } from './auth-resolver.service';
import { LoginComponent } from './login/login.component';
import { MainLayoutComponent } from './main-layout/main-layout.component';
import { ProfileComponent } from './profile/profile.component';
import { SettingsComponent } from './settings/settings.component';
import { SetupComponent } from './setup/setup.component';
import { TorrentTableComponent } from './torrent-table/torrent-table.component';
import { TorrentComponent } from './torrent/torrent.component';
const routes: Routes = [
{
path: 'login',
component: LoginComponent,
},
{
path: 'setup',
component: SetupComponent,
},
{
path: '',
component: MainLayoutComponent,
canActivate: [],
resolve: {
isLoggedIn: authResolver,
},
children: [
{
path: '',
component: TorrentTableComponent,
},
{
path: 'torrent/:id',
component: TorrentComponent,
},
{
path: 'add',
component: AddNewTorrentComponent,
},
{
path: 'settings',
component: SettingsComponent,
},
{
path: 'profile',
component: ProfileComponent,
},
],
},
];

View file

@ -1,8 +1,10 @@
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
template: '<router-outlet></router-outlet>',
styles: []
styles: [],
imports: [RouterOutlet],
})
export class AppComponent {}

View file

@ -1,51 +0,0 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { NgxFilesizeModule, FileSizePipe } from 'ngx-filesize';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { MainLayoutComponent } from './main-layout/main-layout.component';
import { NavbarComponent } from './navbar/navbar.component';
import { AddNewTorrentComponent } from './navbar/add-new-torrent/add-new-torrent.component';
import { TorrentTableComponent } from './torrent-table/torrent-table.component';
import { TorrentRowComponent } from './torrent-row/torrent-row.component';
import { TorrentFileComponent } from './torrent-file/torrent-file.component';
import { SettingsComponent } from './navbar/settings/settings.component';
import { TorrentStatusPipe } from './torrent-status.pipe';
import { FileStatusPipe } from './file-status.pipe';
import { LoginComponent } from './login/login.component';
import { AuthInterceptor } from './auth.interceptor';
import { curray } from 'curray';
curray();
@NgModule({
declarations: [
AppComponent,
MainLayoutComponent,
NavbarComponent,
AddNewTorrentComponent,
TorrentTableComponent,
TorrentRowComponent,
TorrentFileComponent,
SettingsComponent,
TorrentStatusPipe,
FileStatusPipe,
LoginComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule,
HttpClientModule,
NgxFilesizeModule,
],
providers: [
FileSizePipe,
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
],
bootstrap: [AppComponent],
})
export class AppModule {}

View file

@ -0,0 +1,7 @@
import { inject } from '@angular/core';
import { AuthService } from './auth.service';
import { ResolveFn } from '@angular/router';
export const authResolver: ResolveFn<boolean> = () => {
return inject(AuthService).isLoggedIn();
};

View file

@ -1,30 +1,23 @@
import { Injectable } from '@angular/core';
import {
HttpEvent,
HttpInterceptor,
HttpHandler,
HttpRequest,
HttpErrorResponse,
} from '@angular/common/http';
import { throwError, Observable } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { Router } from '@angular/router';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private router: Router) {}
private router = inject(Router);
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
catchError((error: HttpErrorResponse) => {
if (error && error.status === 401) {
if (error && error.status === 402) {
this.router.navigate(['/setup']);
} else if (error && (error.status === 401 || error.status === 403)) {
this.router.navigate(['/login']);
}
return throwError(error);
})
return throwError(() => error);
}),
);
}
}

View file

@ -1,21 +1,48 @@
import { Injectable } from '@angular/core';
import { APP_BASE_HREF } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs/internal/Observable';
@Injectable({
providedIn: 'root',
})
export class AuthService {
constructor(private http: HttpClient) {}
private http = inject(HttpClient);
private baseHref = inject(APP_BASE_HREF);
public isLoggedIn(): Observable<boolean> {
return this.http.get<boolean>(`${this.baseHref}Api/Authentication/IsLoggedIn`);
}
public create(userName: string, password: string): Observable<void> {
return this.http.post<void>(`${this.baseHref}Api/Authentication/Create`, {
userName,
password,
});
}
public setupProvider(provider: number, token: string): Observable<void> {
return this.http.post<void>(`${this.baseHref}Api/Authentication/SetupProvider`, {
provider,
token,
});
}
public login(userName: string, password: string): Observable<void> {
return this.http.post<void>(`/Api/Authentication/Login`, {
return this.http.post<void>(`${this.baseHref}Api/Authentication/Login`, {
userName,
password,
});
}
public logout() {
return this.http.post<void>(`/Api/Authentication/Logout`, {});
return this.http.post<void>(`${this.baseHref}Api/Authentication/Logout`, {});
}
public update(userName: string, password: string): Observable<void> {
return this.http.post<void>(`${this.baseHref}Api/Authentication/Update`, {
userName,
password,
});
}
}

View file

@ -0,0 +1,8 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'decodeURI' })
export class DecodeURIPipe implements PipeTransform {
transform(input: any) {
return decodeURI(input);
}
}

View file

@ -0,0 +1,54 @@
import { Pipe, PipeTransform, inject } from '@angular/core';
import { Download } from './models/download.model';
import { FileSizePipe } from './filesize.pipe';
@Pipe({ name: 'downloadStatus' })
export class DownloadStatusPipe implements PipeTransform {
private pipe = inject(FileSizePipe);
transform(value: Download): string {
if (!value) {
return 'Pending';
}
if (value.error) {
return value.error;
}
if (value.completed != null) {
return 'Finished';
}
if (value.unpackingFinished) {
return 'Unpacking finished';
}
if (value.unpackingStarted) {
const progress = (value.bytesDone / value.bytesTotal || 0) * 100;
return `Unpacking ${progress.toFixed(2)}%`;
}
if (value.unpackingQueued) {
return 'Unpacking queued';
}
if (value.downloadFinished) {
return 'Download finished';
}
if (value.downloadStarted) {
const progress = (value.bytesDone / value.bytesTotal || 0) * 100;
const speed = this.pipe.transform(value.speed, 'filesize');
return `Downloading ${progress.toFixed(2)}% (${speed}/s)`;
}
if (value.downloadQueued) {
return 'Download queued';
}
return 'Pending';
}
}

View file

@ -1,29 +0,0 @@
import { Pipe, PipeTransform } from '@angular/core';
import { TorrentFile } from './models/torrent.model';
import { DownloadStatus } from './models/download.model';
@Pipe({
name: 'fileStatus',
})
export class FileStatusPipe implements PipeTransform {
transform(value: TorrentFile): string {
if (
!value.download ||
value.download.status === DownloadStatus.PendingDownload
) {
return 'Pending';
}
if (value.download.status === DownloadStatus.Downloading) {
const progress = (
(value.download.bytesDownloaded / value.download.bytesSize) *
100
).toFixed(2);
return `${progress || 0}%`;
}
if (value.download.status === DownloadStatus.Finished) {
return `Finished`;
}
}
}

View file

@ -0,0 +1,17 @@
import { Pipe, PipeTransform } from '@angular/core';
import { filesize } from 'filesize';
@Pipe({ name: 'filesize' })
export class FileSizePipe implements PipeTransform {
private static transformOne(value: number, options?: any): string {
return filesize(value, options).toString();
}
transform(value: number | number[], options?: any) {
if (Array.isArray(value)) {
return value.map((val) => FileSizePipe.transformOne(val, options));
}
return FileSizePipe.transformOne(value, options);
}
}

View file

@ -2,65 +2,59 @@
<div class="hero-body">
<div class="container">
<div class="columns is-centered">
<div class="column is-5-tablet is-4-desktop is-3-widescreen">
<img src="../../assets/logo.png" />
<div class="column is-5-tablet is-5-desktop is-5-widescreen">
<img src="assets/logo.png" />
<div class="box">
<div class="field">
<label for="" class="label">Username</label>
<div class="control has-icons-left">
<input
type="text"
placeholder=""
class="input"
name="userName"
[(ngModel)]="userName"
required
/>
<span class="icon is-small is-left">
<i class="fa fa-envelope"></i>
</span>
<form (ngSubmit)="login()">
<div class="field">
<label for="userName" class="label">Username</label>
<div class="control has-icons-left">
<input
type="text"
placeholder=""
class="input"
name="userName"
id="userName"
(change)="setUserName($event)"
required
/>
<span class="icon is-small is-left">
<i class="fa fa-envelope"></i>
</span>
</div>
</div>
</div>
<div class="field">
<label for="" class="label" name="password">Password</label>
<div class="control has-icons-left">
<input
type="password"
class="input"
required
name="password"
[(ngModel)]="password"
/>
<span class="icon is-small is-left">
<i class="fa fa-lock"></i>
</span>
<div class="field">
<label for="password" class="label">Password</label>
<div class="control has-icons-left">
<input
type="password"
class="input"
required
name="password"
id="password"
(change)="setPassword($event)"
/>
<span class="icon is-small is-left">
<i class="fa fa-lock"></i>
</span>
</div>
</div>
</div>
<div class="field">
<button
class="button is-success"
(click)="login()"
*ngIf="loggingIn"
>
<span class="icon">
<i class="fas fa-spinner fa-pulse"></i>
</span>
Login
</button>
<button
class="button is-success"
(click)="login()"
*ngIf="!loggingIn"
>
Login
</button>
</div>
<div
class="notification is-danger is-light"
*ngIf="error?.length > 0"
>
{{ error }}
</div>
<div class="field">
<button
class="button is-success"
type="submit"
[ngClass]="{ 'is-loading': loggingIn }"
[disabled]="loggingIn"
>
Login
</button>
</div>
@if (error?.length > 0) {
<div class="notification is-danger is-light">
{{ error }}
</div>
}
</form>
</div>
</div>
</div>

View file

@ -1,33 +1,42 @@
import { Component, OnInit } from '@angular/core';
import { AuthService } from '../auth.service';
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from '../auth.service';
import { FormsModule } from '@angular/forms';
import { NgClass } from '@angular/common';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss'],
imports: [FormsModule, NgClass],
standalone: true,
})
export class LoginComponent implements OnInit {
export class LoginComponent {
private authService = inject(AuthService);
private router = inject(Router);
public userName: string;
public password: string;
public error: string;
public loggingIn: boolean;
constructor(private authService: AuthService, private router: Router) {}
public setUserName(event: Event): void {
this.userName = (event.target as any).value;
}
ngOnInit(): void {}
public setPassword(event: Event): void {
this.password = (event.target as any).value;
}
public login(): void {
this.error = null;
this.loggingIn = true;
this.authService.login(this.userName, this.password).subscribe(
() => {
this.router.navigate(['/']);
},
(err) => {
this.authService.login(this.userName, this.password).subscribe({
next: () => this.router.navigate(['/']),
error: (err) => {
this.loggingIn = false;
this.error = err.error;
}
);
},
});
}
}

View file

@ -1,6 +1,6 @@
<app-navbar></app-navbar>
<section class="section main-list">
<div class="container">
<app-torrent-table></app-torrent-table>
<router-outlet></router-outlet>
</div>
</section>

View file

@ -1,12 +1,14 @@
import { Component, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import { NavbarComponent } from '../navbar/navbar.component';
import { RouterOutlet } from '@angular/router';
@Component({
selector: 'app-main-layout',
templateUrl: './main-layout.component.html',
styleUrls: ['./main-layout.component.scss']
styleUrls: ['./main-layout.component.scss'],
imports: [NavbarComponent, RouterOutlet],
standalone: true,
})
export class MainLayoutComponent implements OnInit {
export class MainLayoutComponent {
constructor() {}
ngOnInit(): void {}
}

View file

@ -0,0 +1,6 @@
export interface DiskSpaceStatus {
isPaused: boolean;
availableSpaceGB: number;
thresholdGB: number;
lastCheckTime: Date;
}

View file

@ -1,24 +1,19 @@
export class Download {
public downloadId: string;
public torrentId: string;
public path: string;
public link: string;
public added: Date;
public status: DownloadStatus;
public bytesDownloaded: number;
public bytesSize: number;
public downloadQueued: Date;
public downloadStarted: Date;
public downloadFinished: Date;
public unpackingQueued: Date;
public unpackingStarted: Date;
public unpackingFinished: Date;
public completed: Date;
public error: string;
public bytesTotal: number;
public bytesDone: number;
public speed: number;
}
export enum DownloadStatus {
PendingDownload = 0,
Downloading = 1,
Unpacking = 2,
Finished = 3,
public retryCount: number;
}

View file

@ -1,4 +1,9 @@
export class Profile {
userName: string;
expiration: Date;
public provider: string;
public userName: string;
public expiration: Date;
public currentVersion: string;
public latestVersion: string;
public isInsecure: boolean;
public disableUpdateNotification: boolean;
}

View file

@ -0,0 +1,4 @@
export interface RateLimitStatus {
nextDequeueTime: Date | null;
secondsRemaining: number;
}

View file

@ -1,4 +1,9 @@
export class Setting {
public settingId: string;
public value: string;
key: string;
value: boolean | number | null | string;
displayName: null | string;
description: null | string;
type: string;
settings: Setting[];
enumValues: { [key: string]: string };
}

View file

@ -1,41 +1,84 @@
import { Download } from './download.model';
export class Torrent {
torrentId: string;
hash: string;
status: TorrentStatus;
rdId: string;
rdName: string;
rdSize: number;
rdHost: string;
rdSplit: number;
rdProgress: number;
rdStatus: string;
rdAdded: Date;
rdEnded: Date;
rdSpeed: number;
rdSeeders: number;
rdFiles: string;
public torrentId: string;
public hash: string;
public category: string;
public downloadClient: number;
public hostDownloadAction: number;
public downloadAction: number;
public finishedAction: number;
public finishedActionDelay: number;
public downloadMinSize: number;
public includeRegex: string;
public excludeRegex: string;
public downloadManualFiles: string;
files: TorrentFile[];
downloads: Download[];
public added: Date;
public filesSelected: Date;
public completed: Date;
public fileOrMagnet: string;
public isFile: boolean;
public retryCount: number;
public downloadRetryAttempts: number;
public torrentRetryAttempts: number;
public deleteOnError: number;
public lifetime: number;
public priority: number;
public type: DownloadType;
public error: string;
public rdId: string;
public rdName: string;
public rdSize: number;
public rdHost: string;
public rdSplit: number;
public rdProgress: number;
public rdStatus: RealDebridStatus;
public rdStatusRaw: string;
public rdAdded: Date;
public rdEnded: Date;
public rdSpeed: number;
public rdSeeders: number;
public rdFiles: string;
public statusText?: string;
public filesCount?: number;
public downloadsCount?: number;
public files?: TorrentFile[];
public downloads?: Download[];
}
export class TorrentFile {
id: string;
path: string;
bytes: number;
selected: boolean;
public id: string;
public path: string;
public bytes: number;
public selected: boolean;
download: Download;
public download: Download;
}
export enum TorrentStatus {
RealDebrid = 0,
WaitingForDownload = 1,
DownloadQueued = 2,
export class TorrentFileAvailability {
public filename: string;
public filesize: number;
}
export enum RealDebridStatus {
Queued = 0,
Processing = 1,
WaitingForFileSelection = 2,
Downloading = 3,
Finished = 4,
Uploading = 5,
Error = 99,
}
export enum DownloadType {
Torrent = 0,
Nzb = 1,
}

View file

@ -0,0 +1,3 @@
export class Version {
public version: string;
}

View file

@ -1,77 +0,0 @@
<div class="modal" [class.is-active]="isActive">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Add New Torrent</p>
<button class="delete" aria-label="close" (click)="cancel()"></button>
</header>
<section class="modal-card-body">
<div class="field">
<label class="label">Magnet Link</label>
<div class="control">
<textarea
class="textarea"
placeholder="Paste your magnet link here"
[(ngModel)]="magnetLink"
[disabled]="saving"
></textarea>
</div>
</div>
<hr />
<div class="field">
<label class="label">Torrent file</label>
<div class="file has-name">
<label class="file-label">
<input
class="file-input"
type="file"
name="resume"
(change)="pickFile($event)"
[disabled]="saving"
/>
<span class="file-cta">
<span class="file-icon">
<i class="fas fa-upload"></i>
</span>
<span class="file-label">
Pick a torrent file...
</span>
</span>
<span class="file-name">
{{ fileName }}
</span>
</label>
</div>
</div>
<hr />
<div class="field">
<label class="checkbox">
<input type="checkbox" [(ngModel)]="autoDownload" />
Download torrent to host when finished downloading on Real-Debrid
</label>
<label class="checkbox">
<input type="checkbox" [(ngModel)]="autoDelete" />
Remove torrent when finished downloading on host
</label>
</div>
<div class="notification is-danger is-light" *ngIf="error?.length > 0">
Cannot add torrent: {{ error }}
</div>
</section>
<footer class="modal-card-foot">
<button class="button is-success" *ngIf="saving">
<span class="icon">
<i class="fas fa-spinner fa-pulse"></i>
</span>
<span>Add Torrent</span>
</button>
<button class="button is-success" (click)="ok()" *ngIf="!saving">
Add Torrent
</button>
<button class="button" (click)="cancel()" [disabled]="saving">
Cancel
</button>
</footer>
</div>
</div>

View file

@ -1,4 +0,0 @@
.file-name {
max-width: 24em;
width: 24em;
}

View file

@ -1,101 +0,0 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { TorrentService } from 'src/app/torrent.service';
@Component({
selector: 'app-add-new-torrent',
templateUrl: './add-new-torrent.component.html',
styleUrls: ['./add-new-torrent.component.scss'],
})
export class AddNewTorrentComponent implements OnInit {
@Input()
public get open(): boolean {
return this.isActive;
}
public set open(val: boolean) {
this.reset();
this.isActive = val;
}
@Output()
public openChange = new EventEmitter<boolean>();
public isActive = false;
public fileName: string;
public magnetLink: string;
public autoDownload: boolean;
public autoDelete: boolean;
public saving = false;
public error: string;
private selectedFile: File;
constructor(private torrentService: TorrentService) {}
ngOnInit(): void {}
public reset(): void {
this.fileName = '';
this.magnetLink = '';
this.autoDelete = false;
this.autoDownload = true;
this.saving = false;
this.selectedFile = null;
this.error = null;
}
public pickFile(evt: Event): void {
const files = (evt.target as HTMLInputElement).files;
if (files.length === 0) {
return;
}
const file = files[0];
this.fileName = file.name;
this.selectedFile = file;
}
public ok(): void {
this.saving = true;
this.error = null;
if (this.magnetLink) {
this.torrentService
.uploadMagnet(this.magnetLink, this.autoDownload, this.autoDelete)
.subscribe(
() => {
this.cancel();
},
(err) => {
this.error = err.error;
this.saving = false;
}
);
} else if (this.selectedFile) {
this.torrentService
.uploadFile(this.selectedFile, this.autoDownload, this.autoDelete)
.subscribe(
() => {
this.cancel();
},
(err) => {
this.error = err.error;
this.saving = false;
}
);
} else {
this.cancel();
}
}
public cancel(): void {
this.isActive = false;
this.openChange.emit(this.open);
}
}

View file

@ -1,11 +1,7 @@
<nav
class="navbar is-dark is-fixed-top"
role="navigation"
aria-label="main navigation"
>
<nav class="navbar is-dark is-fixed-top" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a class="navbar-item">
<img src="../../assets/logo.png" />
<a class="navbar-item" routerLink="/">
<img src="assets/logo.png" />
</a>
<a
@ -24,54 +20,66 @@
<div class="navbar-menu" [class.is-active]="showMobileMenu">
<div class="navbar-start">
<a class="navbar-item" (click)="this.showNewTorrent = true">
<a class="navbar-item" routerLink="/">
<span class="icon">
<i class="fas fa-table" aria-hidden="true"></i>
</span>
<span>Torrents/NZBs</span>
</a>
<a class="navbar-item" routerLink="/add" [queryParams]="{ type: 'torrent' }">
<span class="icon">
<i class="fas fa-plus" aria-hidden="true"></i>
</span>
<span>Add New Torrent</span>
<span>Add Torrent/NZB</span>
</a>
<a class="navbar-item" (click)="this.showSettings = true">
<a class="navbar-item" routerLink="/settings">
<span class="icon">
<i class="fas fa-cog" aria-hidden="true"></i>
</span>
<span>Settings</span>
</a>
<a
class="navbar-item"
*ngIf="profile"
href="https://real-debrid.com/?id=1348683"
target="_blank"
rel="noopener"
>
<span class="icon">
<i class="fas fa-euro-sign" aria-hidden="true"></i>
</span>
<span>Premium Status: {{ profile.expiration | date }}</span>
</a>
@if (profile) {
<a class="navbar-item" href="{{ providerLink }}" target="_blank" rel="noopener">
<span class="icon">
<i class="fas fa-euro-sign" aria-hidden="true"></i>
</span>
@if (profile.expiration) {
<span>Premium Status: {{ profile.expiration | date }}</span>
} @else {
<span class="no-premium">Not premium</span>
}
</a>
}
</div>
<div class="navbar-end">
<div class="navbar-item has-dropdown is-hoverable">
<span class="navbar-link">
Account
</span>
<span class="navbar-link"> Account </span>
<div class="navbar-dropdown is-right">
<a class="navbar-item">
Profile
</a>
<a class="navbar-item" (click)="logout()">
Logout
</a>
<a class="navbar-item" routerLink="profile"> Profile </a>
<a class="navbar-item" (click)="logout()"> Logout </a>
<hr class="navbar-divider" />
<div class="navbar-item">
Version 0.8.0
</div>
<a href="https://github.com/rogerfar/rdt-client" target="_blank" class="navbar-item">Version {{ version }}</a>
</div>
</div>
</div>
</div>
</nav>
<app-add-new-torrent [(open)]="showNewTorrent"></app-add-new-torrent>
<app-settings [(open)]="showSettings"></app-settings>
@if (
profile &&
(!profile.disableUpdateNotification || profile.isInsecure) &&
profile.latestVersion &&
profile.currentVersion !== profile.latestVersion
) {
<div class="notification" [ngClass]="profile.isInsecure ? 'is-danger' : 'is-warning'">
@if (profile.isInsecure) {
<span> Your current version is insecure. </span>
}
<span>
Version {{ profile.latestVersion }} of RealDebrid Client was found. You are currently on version
{{ profile.currentVersion }}.
</span>
</div>
}

View file

@ -0,0 +1,7 @@
.no-premium {
color: red;
}
.notification {
margin-top: 52px;
}

View file

@ -1,41 +1,77 @@
import { Component, OnInit } from '@angular/core';
import { TorrentService } from '../torrent.service';
import { SettingsService } from '../settings.service';
import { Profile } from '../models/profile.model';
import { Component, DestroyRef, OnInit, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { NavigationEnd, Router, RouterLink } from '@angular/router';
import { AuthService } from '../auth.service';
import { Router } from '@angular/router';
import { Profile } from '../models/profile.model';
import { SettingsService } from '../settings.service';
import { NgClass, DatePipe } from '@angular/common';
import { filter } from 'rxjs';
@Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.scss'],
imports: [RouterLink, NgClass, DatePipe],
standalone: true,
})
export class NavbarComponent implements OnInit {
private destroyRef = inject(DestroyRef);
private settingsService = inject(SettingsService);
private authService = inject(AuthService);
private router = inject(Router);
public showMobileMenu = false;
public showNewTorrent = false;
public showSettings = false;
public profile: Profile;
public providerLink: string;
public version: string;
constructor(
private settingsService: SettingsService,
private authService: AuthService,
private router: Router
) {}
constructor() {
this.router.events
.pipe(
filter((event): event is NavigationEnd => event instanceof NavigationEnd),
takeUntilDestroyed(this.destroyRef),
)
.subscribe(() => {
this.showMobileMenu = false;
});
}
ngOnInit(): void {
this.settingsService.getProfile().subscribe((result) => {
this.profile = result;
});
this.settingsService
.getProfile()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((result) => {
this.profile = result;
switch (result.provider) {
case 'RealDebrid':
this.providerLink = 'https://real-debrid.com/?id=1348683';
break;
case 'AllDebrid':
this.providerLink = 'https://alldebrid.com/?uid=2v91l&lang=en';
break;
case 'Premiumize':
this.providerLink = 'https://www.premiumize.me/';
break;
case 'TorBox':
this.providerLink = 'https://torbox.app/';
break;
case 'DebridLink':
this.providerLink = 'https://debrid-link.com/';
break;
}
});
this.settingsService
.getVersion()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((result) => {
this.version = result.version;
});
}
public logout(): void {
this.authService.logout().subscribe(
() => {
this.router.navigate(['/login']);
},
(err) => {}
);
this.authService.logout().subscribe({ next: () => this.router.navigate(['/login']), error: console.error });
}
}

View file

@ -1,78 +0,0 @@
<div class="modal" [class.is-active]="isActive">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Settings</p>
<button class="delete" aria-label="close" (click)="cancel()"></button>
</header>
<section class="modal-card-body">
<div class="field">
<label class="label">Real-Debrid API Key</label>
<div class="control">
<input
class="input"
type="text"
maxlength="100"
[(ngModel)]="settingRealDebridApiKey"
/>
</div>
<p class="help">
You can find your API key here:
<a
href="https://real-debrid.com/apitoken"
target="_blank"
rel="noopener"
>https://real-debrid.com/apitoken</a
>.
</p>
</div>
<div class="field">
<label class="label">Download folder</label>
<div class="control">
<input
class="input"
type="text"
maxlength="1000"
[(ngModel)]="settingDownloadFolder"
/>
</div>
<p class="help">
Path on the host where Real Debrid Client is hosted.
</p>
</div>
<div class="field">
<label class="label">Maximum parallel downloads</label>
<div class="control">
<input
class="input"
type="number"
max="100"
min="1"
[(ngModel)]="settingDownloadLimit"
/>
</div>
<p class="help">
Maximum amount of torrents that get downloaded to your host at the
same time.
</p>
</div>
<div class="notification is-danger is-light" *ngIf="error?.length > 0">
Error saving settings: {{ error }}
</div>
</section>
<footer class="modal-card-foot">
<button class="button is-success" *ngIf="saving">
<span class="icon">
<i class="fas fa-spinner fa-pulse"></i>
</span>
<span>Save Settings</span>
</button>
<button class="button is-success" (click)="ok()" *ngIf="!saving">
Save Settings
</button>
<button class="button" (click)="cancel()" [disabled]="saving">
Cancel
</button>
</footer>
</div>
</div>

View file

@ -1,103 +0,0 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { SettingsService } from 'src/app/settings.service';
import { Setting } from 'src/app/models/setting.model';
@Component({
selector: 'app-settings',
templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss'],
})
export class SettingsComponent implements OnInit {
@Input()
public get open(): boolean {
return this.isActive;
}
public set open(val: boolean) {
this.reset();
this.isActive = val;
}
@Output()
public openChange = new EventEmitter<boolean>();
public isActive = false;
public saving = false;
public error: string;
public settingRealDebridApiKey: string;
public settingDownloadFolder: string;
public settingDownloadLimit: number;
constructor(private settingsService: SettingsService) {}
ngOnInit(): void {}
public reset(): void {
this.saving = false;
this.error = null;
this.settingsService.get().subscribe(
(results) => {
this.settingRealDebridApiKey = this.getSetting(
results,
'RealDebridApiKey'
);
this.settingDownloadFolder = this.getSetting(results, 'DownloadFolder');
this.settingDownloadLimit = parseInt(
this.getSetting(results, 'DownloadLimit'),
10
);
},
(err) => {
this.error = err.error;
this.saving = true;
}
);
}
public ok(): void {
this.saving = true;
const settings: Setting[] = [
{
settingId: 'RealDebridApiKey',
value: this.settingRealDebridApiKey,
},
{
settingId: 'DownloadFolder',
value: this.settingDownloadFolder,
},
{
settingId: 'DownloadLimit',
value: (this.settingDownloadLimit ?? 10).toString(),
},
];
this.settingsService.update(settings).subscribe(
() => {
this.isActive = false;
this.openChange.emit(this.open);
},
(err) => {
this.error = err;
}
);
}
public cancel(): void {
this.isActive = false;
this.openChange.emit(this.open);
}
private getSetting(settings: Setting[], key: string): string {
const setting = settings.filter((m) => m.settingId === key);
if (setting.length !== 1) {
throw new Error(`Unable to find setting with key ${key}`);
}
return setting[0].value;
}
}

View file

@ -0,0 +1,25 @@
import { Pipe, PipeTransform, SecurityContext, VERSION, inject } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
@Pipe({ name: 'nl2br' })
export class Nl2BrPipe implements PipeTransform {
private sanitizer = inject(DomSanitizer);
transform(value: string, sanitizeBeforehand?: boolean): string {
if (typeof value !== 'string') {
return value;
}
let result: any;
const textParsed = value.replace(/(?:\r\n|\r|\n)/g, '<br />');
if (!VERSION || VERSION.major === '2') {
result = this.sanitizer.bypassSecurityTrustHtml(textParsed);
} else if (sanitizeBeforehand) {
result = this.sanitizer.sanitize(SecurityContext.HTML, textParsed);
} else {
result = textParsed;
}
return result;
}
}

View file

@ -0,0 +1,42 @@
<div class="field">
<label class="label">Login username</label>
<div class="control">
<input class="input" type="text" minlength="1" [(ngModel)]="username" />
</div>
<p class="help">
This is the username you use to login Real-Debrid Client. Only change this if you want to change the username.
</p>
</div>
<div class="field">
<label class="label">Login password</label>
<div class="control">
<input class="input" type="password" minlength="1" [(ngModel)]="password" />
</div>
<p class="help">
This is the password you use to login Real-Debrid Client. Only change this if you want to change the password.
</p>
</div>
<div class="field">
<div class="control">
@if (error !== null) {
<div class="notification is-danger is-light">Error saving: {{ error }}</div>
}
</div>
</div>
<div class="field">
<div class="control">
@if (success) {
<div class="notification is-success is-light">Your profile has been updated</div>
}
</div>
</div>
<div class="field">
<div class="control">
<button class="button is-success" (click)="save()" [disabled]="saving" [ngClass]="{ 'is-loading': saving }">
Save Profile
</button>
</div>
</div>

View file

@ -0,0 +1,40 @@
import { Component, inject } from '@angular/core';
import { AuthService } from '../auth.service';
import { FormsModule } from '@angular/forms';
import { NgClass } from '@angular/common';
@Component({
selector: 'app-profile',
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.scss'],
imports: [FormsModule, NgClass],
standalone: true,
})
export class ProfileComponent {
private authService = inject(AuthService);
public username: string;
public password: string;
public saving = false;
public success: boolean;
public error: string;
public save(): void {
this.success = false;
this.error = null;
this.saving = true;
this.authService.update(this.username, this.password).subscribe({
next: () => {
this.success = true;
this.saving = false;
},
error: (err) => {
this.error = err.error;
this.success = false;
this.saving = false;
},
});
}
}

View file

@ -1,24 +1,50 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { Setting } from './models/setting.model';
import { Profile } from './models/profile.model';
import { Setting } from './models/setting.model';
import { APP_BASE_HREF } from '@angular/common';
import { Version } from './models/version.model';
@Injectable({
providedIn: 'root',
})
export class SettingsService {
constructor(private http: HttpClient) {}
private http = inject(HttpClient);
private baseHref = inject(APP_BASE_HREF);
public get(): Observable<Setting[]> {
return this.http.get<Setting[]>(`/Api/Settings`);
return this.http.get<Setting[]>(`${this.baseHref}Api/Settings`);
}
public update(settings: Setting[]): Observable<void> {
return this.http.put<void>(`/Api/Settings`, { settings });
return this.http.put<void>(`${this.baseHref}Api/Settings`, settings);
}
public getProfile(): Observable<Profile> {
return this.http.get<Profile>(`/Api/Settings/Profile`);
return this.http.get<Profile>(`${this.baseHref}Api/Settings/Profile`);
}
public getVersion(): Observable<Version> {
return this.http.get<Version>(`${this.baseHref}Api/Settings/Version`);
}
public testPath(path: string): Observable<void> {
return this.http.post<void>(`${this.baseHref}Api/Settings/TestPath`, { path });
}
public testDownloadSpeed(): Observable<number> {
return this.http.get<number>(`${this.baseHref}Api/Settings/TestDownloadSpeed`);
}
public testWriteSpeed(): Observable<number> {
return this.http.get<number>(`${this.baseHref}Api/Settings/TestWriteSpeed`);
}
public testAria2cConnection(url: string, secret: string): Observable<{ version: string }> {
return this.http.post<{ version: string }>(`${this.baseHref}Api/Settings/TestAria2cConnection`, {
url,
secret,
});
}
}

View file

@ -0,0 +1,219 @@
<div class="tabs">
<ul>
@for (tab of tabs; track tab.key; let i = $index) {
<li [ngClass]="{ 'is-active': activeTab === i }" (click)="activeTab = i">
<a>{{ tab.displayName }}</a>
</li>
}
<li [ngClass]="{ 'is-active': activeTab === 99 }" (click)="activeTab = 99">
<a>Speed Tests</a>
</li>
</ul>
</div>
@for (tab of tabs; track tab.key; let i = $index) {
<div [hidden]="activeTab !== i">
<h3>{{ tab.description }}</h3>
@for (setting of tab.settings; track setting.key) {
@if (setting.type === "Object") {
<h3 class="title is-3" style="margin-top: 1.2rem">{{ setting.displayName }}</h3>
}
<div class="field">
@if (setting.type !== "Boolean" && setting.type !== "Object") {
<label class="label">{{ setting.displayName }}</label>
}
@switch (setting.type) {
@case ("String") {
<div class="control">
<input class="input" type="text" [(ngModel)]="setting.value" />
</div>
}
@case ("Int32") {
<div class="control">
<input class="input" type="number" [(ngModel)]="setting.value" />
</div>
}
@case ("Boolean") {
<label class="checkbox">
<input type="checkbox" [(ngModel)]="setting.value" />
{{ setting.displayName }}
</label>
}
@case ("Enum") {
<div class="control select is-fullwidth">
<select [(ngModel)]="setting.value">
@for (kvp of setting.enumValues | keyvalue; track kvp) {
<option [value]="kvp.key">{{ kvp.value }}</option>
}
</select>
</div>
}
@case ("Object") {}
@default {
<div class="control">Invalid setting type {{ setting.type }}</div>
}
}
<p class="help" [innerHtml]="setting.description | nl2br"></p>
@if (setting.key === "DownloadClient:Aria2cSecret") {
<button
class="button is-warning"
(click)="testAria2cConnection()"
[disabled]="saving"
[ngClass]="{ 'is-loading': saving }"
>
Test aria2 connection
</button>
@if (testAria2cConnectionError) {
<div class="notification is-danger is-light" style="margin-top: 1rem">
Could connect to Aria2 client<br />
{{ testAria2cConnectionError }}
</div>
}
@if (testAria2cConnectionSuccess) {
<div class="notification is-success is-light" style="margin-top: 1rem">
Found Aria2 client version {{ testAria2cConnectionSuccess }}
</div>
}
}
</div>
}
</div>
}
@if (activeTab === 99) {
<div>
<div class="field">
<label class="label">Test download path permissions</label>
<div class="control">
@if (!testPathError && !testPathSuccess) {
<button
class="button is-warning"
(click)="testDownloadPath()"
[disabled]="saving"
[ngClass]="{ 'is-loading': saving }"
>
Test permissions
</button>
}
@if (testPathError) {
<div class="notification is-danger is-light">
Could not test your download path<br />
{{ testPathError }}
</div>
}
@if (testPathSuccess) {
<div class="notification is-success is-light">Your download path looks good!</div>
}
</div>
<div class="help">This will check if the download folder has write permissions.</div>
</div>
<div class="field">
<label class="label">Test Real-Debrid download speed</label>
<div class="control">
@if (testDownloadSpeedError) {
<div class="notification is-danger is-light">
Could not test your download speed<br />
{{ testDownloadSpeedError }}
</div>
} @else if (testDownloadSpeedSuccess) {
<div class="notification is-success is-light">Download speed {{ testDownloadSpeedSuccess | filesize }}/s</div>
} @else {
<button
class="button is-warning"
(click)="testDownloadSpeed()"
[disabled]="saving"
[ngClass]="{ 'is-loading': saving }"
>
Test download speed
</button>
}
</div>
<div class="help">
This will attempt to download a 10GB file from Real-Debrid. When 50MB has been downloaded the test will stop.
</div>
</div>
<div class="field">
<label class="label">Test download folder write speed</label>
<div class="control">
@if (testWriteSpeedError) {
<div class="notification is-danger is-light">
Could not test your download speed<br />
{{ testWriteSpeedError }}
</div>
} @else if (testWriteSpeedSuccess) {
<div class="notification is-success is-light">Write speed {{ testWriteSpeedSuccess | filesize }}/s</div>
} @else {
<button
class="button is-warning"
(click)="testWriteSpeed()"
[disabled]="saving"
[ngClass]="{ 'is-loading': saving }"
>
Test write speed
</button>
}
</div>
<div class="help">This will write a small file to your download folder to see how fast it can write to it.</div>
</div>
</div>
}
<div class="field">
<div class="control">
@if (error?.length > 0) {
<div class="notification is-danger is-light">Error saving settings: {{ error }}</div>
}
</div>
</div>
@if (activeTab === 0) {
<div class="field">
<label class="label">Register client as magnet link handler</label>
<div class="control">
<button
class="button is-info"
type="button"
(click)="registerMagnetHandler()"
[disabled]="!canRegisterMagnetHandler"
>
<span>
{{ canRegisterMagnetHandler ? "Register Handler" : "Unavailable in This Browser" }}
</span>
</button>
</div>
<p class="help">
@if (canRegisterMagnetHandler) {
This will attempt to register the client as your browser's default handler for magnet links and automatically
open them in the new torrent screen for downloading.
} @else {
Magnet link registration is unavailable. Your
<a
href="https://caniuse.com/mdn-api_navigator_registerprotocolhandler"
target="_blank"
rel="noopener noreferrer"
>
browser may not support this feature
</a>
, or the client is not being served in a
<a
href="https://developer.mozilla.org/en-US/docs/Glossary/Secure_Context/"
target="_blank"
rel="noopener noreferrer"
>
secure context
</a>
.
}
</p>
</div>
}
@if (activeTab < 99) {
<div class="field">
<div class="control">
<button class="button is-success" (click)="ok()" [disabled]="saving" [ngClass]="{ 'is-loading': saving }">
Save Settings
</button>
</div>
</div>
}

View file

@ -0,0 +1,160 @@
import { Component, OnInit, inject } from '@angular/core';
import { Setting } from '../models/setting.model';
import { NgClass, KeyValuePipe } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Nl2BrPipe } from '../nl2br.pipe';
import { FileSizePipe } from '../filesize.pipe';
import { SettingsService } from '../settings.service';
@Component({
selector: 'app-settings',
templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss'],
imports: [NgClass, FormsModule, KeyValuePipe, Nl2BrPipe, FileSizePipe],
standalone: true,
})
export class SettingsComponent implements OnInit {
private settingsService = inject(SettingsService);
public activeTab = 0;
public tabs: Setting[] = [];
public saving = false;
public error: string;
public testPathError: string;
public testPathSuccess: boolean;
public testDownloadSpeedError: string;
public testDownloadSpeedSuccess: number;
public testWriteSpeedError: string;
public testWriteSpeedSuccess: number;
public testAria2cConnectionError: string = null;
public testAria2cConnectionSuccess: string = null;
public canRegisterMagnetHandler = false;
ngOnInit(): void {
this.reset();
this.canRegisterMagnetHandler = !!(window.isSecureContext && 'registerProtocolHandler' in navigator);
}
public reset(): void {
this.settingsService.get().subscribe((settings) => {
this.tabs = settings.filter((m) => m.key.indexOf(':') === -1);
for (let tab of this.tabs) {
tab.settings = settings.filter((m) => m.key.indexOf(`${tab.key}:`) > -1);
}
});
}
public ok(): void {
this.saving = true;
const settingsToSave = this.tabs.flatMap((m) => m.settings).filter((m) => m.type !== 'Object');
this.settingsService.update(settingsToSave).subscribe({
next: () =>
setTimeout(() => {
this.saving = false;
}, 1000),
error: (err) => {
this.saving = false;
this.error = err;
},
});
}
public testDownloadPath(): void {
const settingDownloadPath = this.tabs
.find((m) => m.key === 'DownloadClient')
.settings.find((m) => m.key === 'DownloadClient:DownloadPath').value as string;
this.saving = true;
this.testPathError = null;
this.testPathSuccess = false;
this.settingsService.testPath(settingDownloadPath).subscribe({
next: () => {
this.saving = false;
this.testPathSuccess = true;
},
error: (err) => {
this.testPathError = err.error;
this.saving = false;
},
});
}
public testDownloadSpeed(): void {
this.saving = true;
this.testDownloadSpeedError = null;
this.testDownloadSpeedSuccess = 0;
this.settingsService.testDownloadSpeed().subscribe({
next: (result) => {
this.saving = false;
this.testDownloadSpeedSuccess = result;
},
error: (err) => {
this.testDownloadSpeedError = err.error;
this.saving = false;
},
});
}
public testWriteSpeed(): void {
this.saving = true;
this.testWriteSpeedError = null;
this.testWriteSpeedSuccess = 0;
this.settingsService.testWriteSpeed().subscribe({
next: (result) => {
this.saving = false;
this.testWriteSpeedSuccess = result;
},
error: (err) => {
this.testWriteSpeedError = err.error;
this.saving = false;
},
});
}
public testAria2cConnection(): void {
const settingAria2cUrl = this.tabs
.find((m) => m.key === 'DownloadClient')
.settings.find((m) => m.key === 'DownloadClient:Aria2cUrl').value as string;
const settingAria2cSecret = this.tabs
.find((m) => m.key === 'DownloadClient')
.settings.find((m) => m.key === 'DownloadClient:Aria2cSecret').value as string;
this.saving = true;
this.testAria2cConnectionError = null;
this.testAria2cConnectionSuccess = null;
this.settingsService.testAria2cConnection(settingAria2cUrl, settingAria2cSecret).subscribe({
next: (result) => {
this.saving = false;
this.testAria2cConnectionSuccess = result.version;
},
error: (err) => {
this.testAria2cConnectionError = err.error;
this.saving = false;
},
});
}
public registerMagnetHandler(): void {
try {
navigator.registerProtocolHandler('magnet', `${window.location.origin}/add?magnet=%s`);
alert(
'Success! Your browser will now prompt you to confirm and add the client as the default handler for magnet links.',
);
} catch (error) {
alert('Magnet link registration failed.');
}
}
}

View file

@ -0,0 +1,193 @@
<section class="hero is-dark is-fullheight">
<div class="hero-body">
<div class="container">
<div class="columns is-centered">
<div class="column is-5-tablet is-5-desktop is-5-widescreen">
<img src="assets/logo.png" />
@if (step === 1) {
<div class="box">
<div class="notification is-primary">
Welcome to Real-Debrid Client. Please create your account by entering a username and password.
</div>
<form (ngSubmit)="setup()">
<div class="field">
<label for="userName" class="label">Username</label>
<div class="control has-icons-left">
<input
type="text"
placeholder=""
class="input"
name="userName"
id="userName"
[(ngModel)]="userName"
required
/>
<span class="icon is-small is-left">
<i class="fa fa-envelope"></i>
</span>
</div>
</div>
<div class="field">
<label for="password" class="label">Password</label>
<div class="control has-icons-left">
<input
type="password"
class="input"
required
name="password"
id="password"
[(ngModel)]="password"
/>
<span class="icon is-small is-left">
<i class="fa fa-lock"></i>
</span>
</div>
</div>
<div class="field">
<button class="button is-success" [ngClass]="{ 'is-loading': working }" [disabled]="working">
Setup
</button>
</div>
@if (error?.length > 0) {
<div class="notification is-danger is-light">
{{ error }}
</div>
}
</form>
</div>
}
@if (step === 2) {
<div class="box">
<div class="notification is-primary">
To be able to use the Real-Debrid Client you need a premium subscription to download torrents.
<br /><br />
Not premium yet? You have the choice of 5 providers:
<br />
<a href="https://real-debrid.com/?id=1348683" target="_blank" rel="noopener"
>Use this link to sign up to Real-Debrid.</a
>
<br />
<a href="https://alldebrid.com/?uid=2v91l&lang=en" target="_blank" rel="noopener"
>Use this link to sign up to AllDebrid.</a
>
<br />
<a href="https://www.premiumize.me/" target="_blank" rel="noopener"
>Use this link to sign up to Premiumize.</a
>
<br />
<a href="https://torbox.app/" target="_blank" rel="noopener">Use this link to sign up to TorBox.</a>
<br />
<a href="https://debrid-link.com/" target="_blank" rel="noopener"
>Use this link to sign up to DebridLink.</a
>
<small>(Referal links)</small>
</div>
<div class="field">
<label class="label">Provider</label>
<div class="control select is-fullwidth">
<select [(ngModel)]="provider">
<option [ngValue]="0">Real-Debrid</option>
<option [ngValue]="1">AllDebrid</option>
<option [ngValue]="2">Premiumize</option>
<option [ngValue]="3">TorBox</option>
<option [ngValue]="4">DebridLink</option>
</select>
</div>
</div>
<div class="field">
<label for="debrid-api-token" class="label">API Private Token</label>
<div class="control">
<input
type="text"
placeholder=""
class="input"
name="token"
id="debrid-api-token"
[(ngModel)]="token"
required
/>
</div>
@if (provider === 0) {
<p class="help">
You can find your API key here:
<a href="https://real-debrid.com/apitoken" target="_blank" rel="noopener"
>https://real-debrid.com/apitoken</a
>.
</p>
}
@if (provider === 1) {
<p class="help">
You can find your API key here:
<a href="https://alldebrid.com/apikeys/" target="_blank" rel="noopener"
>https://alldebrid.com/apikeys/</a
>.
</p>
}
@if (provider === 2) {
<p class="help">
You can find your API key here:
<a href="https://www.premiumize.me/account" target="_blank" rel="noopener"
>https://www.premiumize.me/account</a
>.
</p>
}
@if (provider === 3) {
<p class="help">
You can find your API key here:
<a href="https://torbox.app/settings" target="_blank" rel="noopener">https://torbox.app/settings</a
>.
</p>
}
@if (provider === 4) {
<p class="help">
You can find your API key here:
<a href="https://debrid-link.com/webapp/apikey" target="_blank" rel="noopener"
>https://debrid-link.com/webapp/apikey</a
>.
</p>
}
</div>
<div class="field">
<button
class="button is-success"
(click)="setToken()"
[ngClass]="{ 'is-loading': working }"
[disabled]="working"
>
Setup
</button>
</div>
@if (error?.length > 0) {
<div class="notification is-danger is-light">
{{ error }}
</div>
}
</div>
}
@if (step === 3) {
<div class="box">
<div class="notification is-primary">
You are now connected to Real-Debrid! To setup Radarr or Sonarr, please following the instructions here:
<a href="https://github.com/rogerfar/rdt-client/" target="_blank" rel="noopener"
>https://github.com/rogerfar/rdt-client/</a
>.
</div>
<div class="field">
<button
class="button is-success"
(click)="close()"
[ngClass]="{ 'is-loading': working }"
[disabled]="working"
>
OK
</button>
</div>
</div>
}
</div>
</div>
</div>
</div>
</section>

View file

@ -0,0 +1,60 @@
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from '../auth.service';
import { NgClass } from '@angular/common';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-setup',
templateUrl: './setup.component.html',
styleUrls: ['./setup.component.scss'],
imports: [FormsModule, NgClass],
standalone: true,
})
export class SetupComponent {
private authService = inject(AuthService);
private router = inject(Router);
public userName: string;
public password: string;
public provider = 0;
public token: string;
public error: string;
public working: boolean;
public step: number = 1;
public setup(): void {
this.error = null;
this.working = true;
this.authService.create(this.userName, this.password).subscribe({
next: () => {
this.step = 2;
this.working = false;
},
error: (err) => {
this.working = false;
this.error = err.error;
},
});
}
public setToken(): void {
this.authService.setupProvider(this.provider, this.token).subscribe({
next: () => {
this.step = 3;
this.working = false;
},
error: (err: any) => {
this.working = false;
this.error = err.error;
},
});
}
public close(): void {
this.router.navigate(['/']);
}
}

View file

@ -0,0 +1,78 @@
import { Pipe, PipeTransform } from '@angular/core';
export type SortDirection = 'asc' | 'desc';
export function getSortFieldValue(item: unknown, field: string): unknown {
if (item == null || !field) {
return undefined;
}
return field.split('.').reduce<unknown>((value, key) => {
if (value == null || typeof value !== 'object') {
return undefined;
}
return (value as Record<string, unknown>)[key];
}, item);
}
function compareSortValues(left: unknown, right: unknown): number {
if (left === right) {
return 0;
}
if (left == null) {
return 1;
}
if (right == null) {
return -1;
}
if (typeof left === 'string' && typeof right === 'string') {
return left.localeCompare(right, undefined, { sensitivity: 'base' });
}
if (left instanceof Date && right instanceof Date) {
return left.getTime() - right.getTime();
}
if (typeof left === 'boolean' && typeof right === 'boolean') {
return Number(left) - Number(right);
}
const comparableLeft = left as string | number | bigint;
const comparableRight = right as string | number | bigint;
if (comparableLeft < comparableRight) {
return -1;
}
if (comparableLeft > comparableRight) {
return 1;
}
return 0;
}
export function sortItems<T>(
array: readonly T[],
field: string,
order: SortDirection = 'asc',
accessor: (item: T, field: string) => unknown = getSortFieldValue,
): T[] {
if (!Array.isArray(array)) {
return [];
}
const direction = order === 'asc' ? 1 : -1;
return [...array].sort((left, right) => compareSortValues(accessor(left, field), accessor(right, field)) * direction);
}
@Pipe({ name: 'sort' })
export class SortPipe implements PipeTransform {
transform(array: unknown[], field: string, order: SortDirection = 'asc'): unknown[] {
return sortItems(array, field, order);
}
}

View file

@ -1,10 +0,0 @@
<td>
<i class="fas fa-file" aria-hidden="true"></i>
{{ file.path }}
</td>
<td>{{ file.bytes | filesize }}</td>
<td>
{{ file | fileStatus }}
</td>
<td></td>
<td></td>

View file

@ -1,3 +0,0 @@
td {
font-size: smaller;
}

View file

@ -1,17 +0,0 @@
import { Component, OnInit, Input } from '@angular/core';
import { Torrent, TorrentFile } from '../models/torrent.model';
import { TorrentService } from '../torrent.service';
@Component({
selector: '[app-torrent-file]',
templateUrl: './torrent-file.component.html',
styleUrls: ['./torrent-file.component.scss'],
})
export class TorrentFileComponent implements OnInit {
@Input()
public file: TorrentFile;
constructor(private torrentService: TorrentService) {}
ngOnInit(): void {}
}

View file

@ -1,30 +0,0 @@
<td>{{ torrent.rdName }}</td>
<td>
{{ torrent.rdSize | filesize }}
</td>
<td>
{{ torrent | status }}
</td>
<td>
<span
class="icon download-icon"
(click)="download($event)"
title="Download to disk"
*ngIf="!loading && torrent.status === 1"
>
<i class="fas fa-download"></i>
</span>
</td>
<td>
<span
class="icon delete-icon"
(click)="delete($event)"
title="Delete torrent"
*ngIf="!loading"
>
<i class="fas fa-times"></i>
</span>
<span class="icon loading-icon" *ngIf="loading">
<i class="fas fa-spinner fa-pulse"></i>
</span>
</td>

View file

@ -1,4 +0,0 @@
.delete-icon {
color: red;
cursor: pointer;
}

View file

@ -1,36 +0,0 @@
import { Component, OnInit, Input } from '@angular/core';
import { Torrent, TorrentStatus } from 'src/app/models/torrent.model';
import { TorrentService } from 'src/app/torrent.service';
@Component({
selector: '[app-torrent-row]',
templateUrl: './torrent-row.component.html',
styleUrls: ['./torrent-row.component.scss'],
})
export class TorrentRowComponent implements OnInit {
@Input()
public torrent: Torrent;
public loading = false;
constructor(private torrentService: TorrentService) {}
ngOnInit(): void {}
public download(event: Event): void {
event.stopPropagation();
this.loading = true;
this.torrentService.download(this.torrent.torrentId).subscribe(() => {
this.loading = false;
this.torrent.status = TorrentStatus.Downloading;
});
}
public delete(event: Event): void {
event.stopPropagation();
this.loading = true;
this.torrentService.delete(this.torrent.torrentId).subscribe(() => {});
}
}

View file

@ -1,81 +1,132 @@
import { Pipe, PipeTransform } from '@angular/core';
import { Torrent, TorrentStatus } from './models/torrent.model';
import { FileSizePipe } from 'ngx-filesize';
import { DownloadStatus } from './models/download.model';
import { RealDebridStatus, Torrent } from './models/torrent.model';
import { FileSizePipe } from './filesize.pipe';
@Pipe({
name: 'status',
})
export class TorrentStatusPipe implements PipeTransform {
constructor(private pipe: FileSizePipe) {}
const fileSizePipe = new FileSizePipe();
transform(torrent: Torrent): string {
if (torrent.downloads && torrent.downloads.length > 0) {
const allFinished = torrent.downloads.all(
(m) => m.status === DownloadStatus.Finished
);
if (allFinished) {
return 'Finished';
export function getTorrentStatus(torrent: Torrent): string {
if (torrent.error) {
return torrent.error;
}
const downloads = torrent.downloads ?? [];
if (downloads.length > 0) {
let allFinished = true;
let downloadingCount = 0;
let downloadedCount = 0;
let downloadingBytesDone = 0;
let downloadingBytesTotal = 0;
let downloadingSpeed = 0;
let unpackingCount = 0;
let unpackedCount = 0;
let unpackingBytesDone = 0;
let unpackingBytesTotal = 0;
let queuedForUnpackingCount = 0;
let queuedForDownloadingCount = 0;
for (const download of downloads) {
if (download.completed == null) {
allFinished = false;
}
const downloading = torrent.downloads.where(
(m) => m.status === DownloadStatus.Downloading
);
const unpacking = torrent.downloads.where(
(m) => m.status === DownloadStatus.Unpacking
);
const allBytesDownloaded = torrent.downloads.sum(
(m) => m.bytesDownloaded
);
const allBytesSize = torrent.downloads.sum((m) => m.bytesSize);
let progress = 0;
let allSpeeds = 0;
if (allBytesSize > 0) {
progress = (allBytesDownloaded / allBytesSize) * 100;
allSpeeds = downloading.sum((m) => m.speed) / downloading.length;
if (download.downloadFinished != null) {
downloadedCount += 1;
}
console.log(allBytesDownloaded, allBytesSize, progress, allSpeeds);
let speed: string | string[] = '0';
if (allSpeeds > 0) {
speed = this.pipe.transform(allSpeeds, 'filesize');
if (download.downloadStarted && !download.downloadFinished && download.bytesDone > 0) {
downloadingCount += 1;
downloadingBytesDone += download.bytesDone;
downloadingBytesTotal += download.bytesTotal;
downloadingSpeed += download.speed;
}
if (downloading.length > 0) {
if (allBytesSize > 0) {
return `Downloading (${progress.toFixed(2)}% - ${speed}/s)`;
}
return `Preparing download`;
if (download.unpackingFinished != null) {
unpackedCount += 1;
}
if (unpacking.length > 0) {
return `Unpacking (${progress.toFixed(2)}% - ${speed}/s)`;
if (download.unpackingStarted && !download.unpackingFinished && download.bytesDone > 0) {
unpackingCount += 1;
unpackingBytesDone += download.bytesDone;
unpackingBytesTotal += download.bytesTotal;
}
return 'Pending download';
if (download.unpackingQueued && !download.unpackingStarted) {
queuedForUnpackingCount += 1;
}
if (!download.downloadStarted && !download.downloadFinished) {
queuedForDownloadingCount += 1;
}
}
switch (torrent.status) {
case TorrentStatus.RealDebrid:
const speed = this.pipe.transform(torrent.rdSpeed, 'filesize');
return `Torrent downloading (${torrent.rdProgress}% - ${speed}/s)`;
case TorrentStatus.WaitingForDownload:
return `Ready to download, press the download icon to start`;
case TorrentStatus.DownloadQueued:
return `Download queued`;
case TorrentStatus.Downloading:
return `Downloading`;
case TorrentStatus.Finished:
return `Finished`;
case TorrentStatus.Error:
return `Error`;
default:
return 'Unknown status';
if (allFinished) {
return 'Finished';
}
if (downloadingCount > 0) {
const progress = ((downloadingBytesDone / downloadingBytesTotal) || 0) * 100;
const speed = fileSizePipe.transform(downloadingSpeed, 'filesize') as string;
return `Downloading file ${downloadingCount + downloadedCount}/${downloads.length} (${progress.toFixed(2)}% - ${speed}/s)`;
}
if (unpackingCount > 0) {
const progress = ((unpackingBytesDone / unpackingBytesTotal) || 0) * 100;
return `Extracting file ${unpackingCount + unpackedCount}/${downloads.length} (${progress.toFixed(2)}%)`;
}
if (queuedForUnpackingCount > 0) {
return 'Queued for unpacking';
}
if (queuedForDownloadingCount > 0) {
return 'Queued for downloading';
}
if (unpackedCount > 0) {
return 'Files unpacked';
}
if (downloadedCount > 0) {
return 'Files downloaded to host';
}
}
if (torrent.completed) {
return 'Finished';
}
const prefix = torrent.type === 1 ? 'NZB' : 'Torrent';
switch (torrent.rdStatus) {
case RealDebridStatus.Queued:
return 'Not Yet Added to Provider';
case RealDebridStatus.Downloading:
if (torrent.rdSeeders < 1 && torrent.type !== 1) {
return 'Torrent stalled';
}
return `${prefix} downloading (${torrent.rdProgress}% - ${fileSizePipe.transform(torrent.rdSpeed, 'filesize') as string}/s)`;
case RealDebridStatus.Processing:
return `${prefix} processing`;
case RealDebridStatus.WaitingForFileSelection:
return `${prefix} waiting for file selection`;
case RealDebridStatus.Error:
return `${prefix} error: ${torrent.rdStatusRaw}`;
case RealDebridStatus.Finished:
return `${prefix} finished, waiting for download links`;
case RealDebridStatus.Uploading:
return `${prefix} uploading`;
default:
return 'Unknown status';
}
}
@Pipe({ name: 'status' })
export class TorrentStatusPipe implements PipeTransform {
transform(torrent: Torrent): string {
return getTorrentStatus(torrent);
}
}

View file

@ -1,52 +1,362 @@
<div class="notification is-danger is-light" *ngIf="error && error.length > 0">
An error has occured: {{ error }}<br />
Please refresh the screen after fixing this error.
</div>
@if (error && error.length > 0) {
<div class="notification is-danger is-light">
An error has occurred: {{ error }}<br />
Please refresh the screen after fixing this error.
</div>
}
@if (diskSpaceStatus?.isPaused) {
<div class="notification is-warning">
<strong>Bezzad downloads paused due to low disk space</strong>
<br />
Available: {{ diskSpaceStatus.availableSpaceGB }} GB | Resume at: {{ diskSpaceStatus.thresholdGB * 2 }} GB
<br />
<small>Last check: {{ diskSpaceStatus.lastCheckTime | date: "short" }}</small>
</div>
}
@if (rateLimitStatus?.nextDequeueTime) {
<div class="notification is-warning">
<strong>Debrid provider rate limit reached</strong>
<br />
Processing paused until {{ rateLimitStatus.nextDequeueTime | date: "medium" }}
@if (rateLimitStatus.secondsRemaining > 0) {
({{ rateLimitStatus.secondsRemaining | number: "1.0-0" }} seconds remaining)
}
</div>
}
<div class="table-container">
<table class="table is-fullwidth is-hoverable">
<colgroup>
<col style="width: 50%;" />
<col style="width: 15%;" />
<col style="width: 35%;" />
<col style="width: 50px;" />
<col style="width: 50px;" />
</colgroup>
<thead>
<tr>
<th>Name</th>
<th>Size</th>
<th>Status</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<ng-container *ngFor="let torrent of torrents; trackBy: trackByMethod">
<tr
app-torrent-row
[torrent]="torrent"
(click)="selectTorrent(torrent)"
></tr>
<ng-container
*ngIf="showFiles[torrent.torrentId] && torrent?.files.length === 0"
>
@if (!isMobile) {
<table class="table is-fullwidth is-hoverable">
<thead>
<tr>
<th>
<input
type="checkbox"
(click)="toggleDeleteSelectAll($event)"
[checked]="selectedTorrents.length > 0 && selectedTorrents.length === torrents.length"
/>
</th>
<th (click)="sort('rdName')">Name</th>
<th (click)="sort('category')">Category</th>
<th (click)="sort('priority')">Priority</th>
<th (click)="sort('rdSeeders')">Seeders</th>
<th (click)="sort('files.length')">Files</th>
<th (click)="sort('downloads.length')">Downloads</th>
<th (click)="sort('rdSize')">Size</th>
<th (click)="sort('added')">Requested</th>
<th (click)="sort('status')">Status</th>
</tr>
</thead>
<tbody>
@for (torrent of sortedTorrents; track torrent.torrentId) {
<tr>
<td colspan="20">
<i class="fas fa-spinner fa-pulse"></i>
<td>
<input type="checkbox" (click)="toggleSelect(torrent.torrentId)" [checked]="isSelected(torrent.torrentId)" />
</td>
<td (click)="openTorrent(torrent.torrentId)" class="break-all">
{{ torrent.rdName }}
</td>
<td>
{{ torrent.category }}
</td>
<td>
{{ torrent.priority }}
</td>
<td>
{{ torrent.rdSeeders }}
</td>
<td>
{{ (torrent.filesCount ?? torrent.files?.length ?? 0) | number }}
</td>
<td>
{{ (torrent.downloadsCount ?? torrent.downloads?.length ?? 0) | number }}
</td>
<td>
{{ torrent.rdSize | filesize }}
</td>
<td>
{{ torrent.added | date: "medium" }}
</td>
<td>
{{ torrent.statusText ?? (torrent | status) }}
</td>
</tr>
</ng-container>
<ng-container
*ngIf="showFiles[torrent.torrentId] && torrent?.files.length > 0"
>
<tr
app-torrent-file
[file]="file"
*ngFor="let file of torrent.files"
></tr>
</ng-container>
</ng-container>
</tbody>
</table>
}
</tbody>
</table>
} @else {
<div class="mobile-cards">
@for (torrent of sortedTorrents; track torrent.torrentId) {
<div class="box mobile-card" [class.is-selected]="isSelected(torrent.torrentId)">
<div class="mobile-card-checkbox">
<input
type="checkbox"
(click)="toggleSelect(torrent.torrentId)"
[checked]="isSelected(torrent.torrentId)"
/>
</div>
<div class="mobile-card-body" (click)="openTorrent(torrent.torrentId)">
<p class="mobile-card-name">{{ torrent.rdName }}</p>
<div class="tags mb-2">
<span class="tag is-info is-light">{{ torrent.statusText ?? (torrent | status) }}</span>
@if (torrent.category) {
<span class="tag is-light">{{ torrent.category }}</span>
}
</div>
<div class="mobile-card-details">
<span class="mobile-card-detail"
><span class="detail-label">Size</span> {{ torrent.rdSize | filesize }}</span
>
<span class="mobile-card-detail"><span class="detail-label">Seeders</span> {{ torrent.rdSeeders }}</span>
<span class="mobile-card-detail"
><span class="detail-label">Files</span> {{ (torrent.filesCount ?? torrent.files?.length ?? 0) | number }}</span
>
<span class="mobile-card-detail"
><span class="detail-label">Downloads</span> {{ (torrent.downloadsCount ?? torrent.downloads?.length ?? 0) | number }}</span
>
</div>
</div>
</div>
}
</div>
}
<div class="flex-container">
@if (torrents.length > 0) {
<button class="button is-danger" (click)="showDeleteModal()" [disabled]="selectedTorrents.length === 0">
Delete Selected
</button>
<button class="button is-primary" (click)="showRetryModal()" [disabled]="selectedTorrents.length === 0">
Retry Selected
</button>
<button class="button is-primary" (click)="changeSettingsModal()" [disabled]="selectedTorrents.length === 0">
Change Settings
</button>
}
</div>
</div>
<div class="modal" [class.is-active]="isDeleteModalActive">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Delete selected torrents</p>
<button class="delete" aria-label="close" (click)="deleteCancel()"></button>
</header>
<section class="modal-card-body">
<p>Are you sure you want to delete these torrent?</p>
<div class="field">
<label class="label"></label>
<div class="control">
<label class="checkbox">
<input type="checkbox" [(ngModel)]="deleteData" (change)="updateDeleteSelectAll()" />
Delete Torrents from client
</label>
<br />
<label class="checkbox">
<input type="checkbox" [(ngModel)]="deleteRdTorrent" (change)="updateDeleteSelectAll()" />
Delete Torrents from provider
</label>
<br />
<label class="checkbox">
<input type="checkbox" [(ngModel)]="deleteLocalFiles" (change)="updateDeleteSelectAll()" />
Delete local files
</label>
<br />
<br />
<label class="checkbox">
<input type="checkbox" [(ngModel)]="deleteSelectAll" (change)="toggleDeleteSelectAllOptions()" />
Select All
</label>
</div>
</div>
<div class="notification is-primary">
Deleting a torrent from Real-Debrid will automatically delete it here too.
</div>
@if (deleteError?.length > 0) {
<div class="notification is-danger is-light">Error deleting torrent: {{ deleteError }}</div>
}
</section>
<footer class="modal-card-foot">
<button
class="button is-success"
(click)="deleteOk()"
[disabled]="deleting"
[ngClass]="{ 'is-loading': deleting }"
>
Delete selected
</button>
<button class="button" (click)="deleteCancel()" [disabled]="deleting" [ngClass]="{ 'is-loading': deleting }">
Cancel
</button>
</footer>
</div>
</div>
<div class="modal" [class.is-active]="isRetryModalActive">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Retry selected torrents</p>
<button class="delete" aria-label="close" (click)="retryCancel()"></button>
</header>
<section class="modal-card-body">
<p>Are you sure you want to retry these torrent?</p>
<p>
This action will delete all the torrent data + all local downloads. Then it will re-add the original magnet link
or torrent file to the debrid provider.
</p>
@if (retryError?.length > 0) {
<div class="notification is-danger is-light">Error retrying torrent: {{ retryError }}</div>
}
</section>
<footer class="modal-card-foot">
<button
class="button is-success"
(click)="retryOk()"
[disabled]="retrying"
[ngClass]="{ 'is-loading': retrying }"
>
Retry selected
</button>
<button class="button" (click)="retryCancel()" [disabled]="retrying" [ngClass]="{ 'is-loading': retrying }">
Cancel
</button>
</footer>
</div>
</div>
<div class="modal" [class.is-active]="isChangeSettingsModalActive">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Change settings for selected torrents</p>
<button class="delete" aria-label="close" (click)="changeSettingsCancel()"></button>
</header>
<section class="modal-card-body">
<div class="field">
<label class="label">Downloader</label>
<div class="control select is-fullwidth">
<select [(ngModel)]="updateSettingsDownloadClient">
<option [ngValue]="null"></option>
<option [ngValue]="0">Bezzad</option>
<option [ngValue]="1">Aria2c</option>
<option [ngValue]="2">Symlink Downloader</option>
<option [ngValue]="3">Synology DownloadStation</option>
</select>
</div>
<p class="help">
Select which downloader is used to download this torrent from the debrid provider to your host.
</p>
</div>
<div class="field">
<label class="label">Post Download Action</label>
<div class="control select is-fullwidth">
<select [(ngModel)]="updateSettingsHostDownloadAction">
<option [ngValue]="0">Download all files to host</option>
<option [ngValue]="1">Don't download any files to host</option>
</select>
</div>
<p class="help">
When a torrent is finished downloading on the provider, perform this action. Use this setting if you only want
to add files to Real-Debrid but not download them to the host.
</p>
</div>
<div class="field">
<label class="label">Category</label>
<div class="control">
<input class="input" type="text" [(ngModel)]="updateSettingsCategory" />
</div>
<p class="help">The category becomes a sub-folder in your main download path.</p>
</div>
<div class="field">
<label class="label">Priority</label>
<div class="control">
<input class="input" type="number" step="1" [(ngModel)]="updateSettingsPriority" />
</div>
<p class="help">
Set the priority for this torrent where 1 is the highest. When empty it will be assigned the lowest priority.
</p>
</div>
<div class="field">
<label class="label">Automatic retry downloads</label>
<div class="control">
<input
class="input"
type="number"
max="1000"
min="0"
step="1"
[(ngModel)]="updateSettingsDownloadRetryAttempts"
/>
</div>
<p class="help">When a single download fails it will retry it this many times before marking it as failed.</p>
</div>
<div class="field">
<label class="label">Automatic retry torrent</label>
<div class="control">
<input
class="input"
type="number"
max="1000"
min="0"
step="1"
[(ngModel)]="updateSettingsTorrentRetryAttempts"
/>
</div>
<p class="help">
When a single download has failed multiple times (see setting above) or when the torrent itself received an
error it will retry the full torrent this many times before marking it failed.
</p>
</div>
<div class="field">
<label class="label">Delete download when in error</label>
<div class="control">
<input class="input" type="number" max="1000" min="0" step="1" [(ngModel)]="updateSettingsDeleteOnError" />
</div>
<p class="help">
When a download has been in error for this many minutes, delete it from the provider and the client. 0 to
disable.
</p>
</div>
<div class="field">
<label class="label">Torrent maximum lifetime</label>
<div class="control">
<input
class="input"
type="number"
max="100000"
min="0"
step="1"
[(ngModel)]="updateSettingsTorrentLifetime"
/>
</div>
<p class="help">
The maximum lifetime of a torrent in minutes. When this time has passed, mark the torrent as error. If the
torrent is completed and has downloads, the lifetime setting will not apply. 0 to disable.
</p>
</div>
@if (changeSettingsError?.length > 0) {
<div class="notification is-danger is-light">Error changing settings: {{ changeSettingsError }}</div>
}
</section>
<footer class="modal-card-foot">
<button
class="button is-success"
(click)="changeSettingsOk()"
[disabled]="changingSettings"
[ngClass]="{ 'is-loading': changingSettings }"
>
Save
</button>
<button
class="button"
(click)="changeSettingsCancel()"
[disabled]="changingSettings"
[ngClass]="{ 'is-loading': changingSettings }"
>
Cancel
</button>
</footer>
</div>
</div>

View file

@ -1,5 +1,96 @@
table {
tr {
cursor: pointer;
td.break-all {
word-break: break-all;
}
}
}
.mobile-cards {
display: block;
}
.mobile-card {
display: flex;
align-items: flex-start;
gap: 0.75rem;
padding: 0.875rem 0.75rem !important;
margin-bottom: 0.5rem !important;
cursor: pointer;
&.is-selected {
border-left: 3px solid hsl(217, 71%, 53%);
}
&:active {
background-color: hsl(0, 0%, 96%);
}
}
.mobile-card-checkbox {
flex-shrink: 0;
padding-top: 0.125rem;
input[type="checkbox"] {
width: 1.1rem;
height: 1.1rem;
cursor: pointer;
}
}
.mobile-card-body {
flex: 1;
min-width: 0;
}
.mobile-card-name {
font-weight: 600;
font-size: 0.9rem;
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-bottom: 0.375rem;
color: hsl(0, 0%, 21%);
}
.mobile-card-details {
display: flex;
flex-wrap: wrap;
gap: 0.25rem 1rem;
}
.mobile-card-detail {
font-size: 0.8rem;
color: hsl(0, 0%, 29%);
white-space: nowrap;
.detail-label {
color: hsl(0, 0%, 48%);
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.03em;
margin-right: 0.2em;
}
}
.flex-container {
display: flex;
flex: 1 1 0;
gap: 20px;
flex-direction: row;
margin-top: 1rem;
@media screen and (max-width: 1279px) {
flex-direction: column;
}
@media screen and (max-width: 768px) {
gap: 0.5rem;
.button {
width: 100%;
}
}
}

View file

@ -1,68 +1,400 @@
import {
Component,
OnInit,
OnDestroy,
HostListener,
ElementRef,
} from '@angular/core';
import { Component, DestroyRef, OnDestroy, OnInit, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { Router } from '@angular/router';
import { Torrent } from '../models/torrent.model';
import { DiskSpaceStatus } from '../models/disk-space-status.model';
import { RateLimitStatus } from '../models/rate-limit-status.model';
import { TorrentService } from '../torrent.service';
import { DownloadStatus } from '../models/download.model';
import { forkJoin, Observable } from 'rxjs';
import { FormsModule } from '@angular/forms';
import { NgClass, DecimalPipe, DatePipe } from '@angular/common';
import { getTorrentStatus, TorrentStatusPipe } from '../torrent-status.pipe';
import { SortDirection, getSortFieldValue, sortItems } from '../sort.pipe';
import { FileSizePipe } from '../filesize.pipe';
@Component({
selector: 'app-torrent-table',
templateUrl: './torrent-table.component.html',
styleUrls: ['./torrent-table.component.scss'],
imports: [FormsModule, NgClass, DecimalPipe, DatePipe, TorrentStatusPipe, FileSizePipe],
standalone: true,
})
export class TorrentTableComponent implements OnInit, OnDestroy {
private destroyRef = inject(DestroyRef);
private router = inject(Router);
private torrentService = inject(TorrentService);
private selectedTorrentIds = new Set<string>();
public torrents: Torrent[] = [];
public sortedTorrents: Torrent[] = [];
public selectedTorrents: string[] = [];
public error: string;
public showFiles: { [key: string]: boolean } = {};
public sortProperty = 'added';
public sortDirection: SortDirection = 'desc';
private timer: any;
public isDeleteModalActive: boolean;
public deleteError: string;
public deleting: boolean;
public deleteSelectAll: boolean;
public deleteData: boolean;
public deleteRdTorrent: boolean;
public deleteLocalFiles: boolean;
constructor(private torrentService: TorrentService) {}
public isRetryModalActive: boolean;
public retryError: string;
public retrying: boolean;
public isChangeSettingsModalActive: boolean;
public changeSettingsError: string;
public changingSettings: boolean;
public updateSettingsDownloadClient: number;
public updateSettingsHostDownloadAction: number;
public updateSettingsCategory: string;
public updateSettingsPriority: number;
public updateSettingsDownloadRetryAttempts: number;
public updateSettingsTorrentRetryAttempts: number;
public updateSettingsDeleteOnError: number;
public updateSettingsTorrentLifetime: number;
public diskSpaceStatus: DiskSpaceStatus | null = null;
public rateLimitStatus: RateLimitStatus | null = null;
public isMobile = false;
private mobileQuery: MediaQueryList;
private mobileQueryListener: (e: MediaQueryListEvent) => void;
ngOnInit(): void {
this.timer = setInterval(() => {
this.torrentService.getList().subscribe(
(result) => {
this.torrents = result;
this.mobileQuery = window.matchMedia('(max-width: 768px)');
this.isMobile = this.mobileQuery.matches;
this.mobileQueryListener = (e: MediaQueryListEvent) => {
this.isMobile = e.matches;
};
this.mobileQuery.addEventListener('change', this.mobileQueryListener);
// Load persisted sort settings (if any)
try {
const sp = localStorage.getItem('torrentTable.sortProperty');
const sd = localStorage.getItem('torrentTable.sortDirection');
if (sp) {
this.sortProperty = sp;
}
if (sd === 'asc' || sd === 'desc') {
this.sortDirection = sd as 'asc' | 'desc';
}
} catch (_) {
// Ignore storage errors (e.g., disabled storage)
}
this.torrentService
.getDiskSpaceStatus()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({
next: (status) => {
this.diskSpaceStatus = status;
},
(err) => {
});
this.torrentService.diskSpaceStatus$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((status) => {
this.diskSpaceStatus = status;
});
this.torrentService
.getRateLimitStatus()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({
next: (status) => {
this.rateLimitStatus = status;
},
});
this.torrentService.rateLimitStatus$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((status) => {
this.rateLimitStatus = status;
});
this.torrentService.update$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((result) => {
this.setTorrents(result);
});
this.torrentService
.getList()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({
next: (result) => {
this.setTorrents(result);
},
error: (err) => {
this.error = err.error;
clearInterval(this.timer);
}
);
}, 1000);
},
});
}
public sort(property: string): void {
if (this.sortProperty === property) {
this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
} else {
this.sortProperty = property;
this.sortDirection = 'desc';
}
// Persist sort settings
try {
localStorage.setItem('torrentTable.sortProperty', this.sortProperty);
localStorage.setItem('torrentTable.sortDirection', this.sortDirection);
} catch (_) {
// Ignore storage errors
}
this.applySorting();
}
public openTorrent(torrentId: string): void {
this.router.navigate([`/torrent/${torrentId}`]);
}
public toggleDeleteSelectAll(event: Event) {
const checked = (event.target as HTMLInputElement).checked;
this.selectedTorrentIds = checked ? new Set(this.torrents.map((torrent) => torrent.torrentId)) : new Set<string>();
this.syncSelectedTorrents();
}
public toggleSelect(torrentId: string) {
if (this.selectedTorrentIds.has(torrentId)) {
this.selectedTorrentIds.delete(torrentId);
} else {
this.selectedTorrentIds.add(torrentId);
}
this.syncSelectedTorrents();
}
public isSelected(torrentId: string): boolean {
return this.selectedTorrentIds.has(torrentId);
}
public showDeleteModal(): void {
this.deleteData = false;
this.deleteRdTorrent = false;
this.deleteLocalFiles = false;
this.deleteError = null;
this.isDeleteModalActive = true;
}
public deleteCancel(): void {
this.isDeleteModalActive = false;
}
public deleteOk(): void {
this.deleting = true;
const calls: Observable<void>[] = [];
this.selectedTorrents.forEach((torrentId) => {
calls.push(this.torrentService.delete(torrentId, this.deleteData, this.deleteRdTorrent, this.deleteLocalFiles));
});
forkJoin(calls).subscribe({
complete: () => {
this.isDeleteModalActive = false;
this.deleting = false;
this.clearSelectedTorrents();
},
error: (err) => {
this.deleteError = err.error;
this.deleting = false;
},
});
}
public showRetryModal(): void {
this.retryError = null;
this.isRetryModalActive = true;
}
public retryCancel(): void {
this.isRetryModalActive = false;
}
public retryOk(): void {
this.retrying = true;
const calls: Observable<void>[] = [];
this.selectedTorrents.forEach((torrentId) => {
calls.push(this.torrentService.retry(torrentId));
});
forkJoin(calls).subscribe({
complete: () => {
this.isRetryModalActive = false;
this.retrying = false;
this.clearSelectedTorrents();
},
error: (err) => {
this.retryError = err.error;
this.retrying = false;
},
});
}
public changeSettingsModal(): void {
this.changeSettingsError = null;
const selectedTorrents = this.getSelectedTorrentModels();
this.updateSettingsDownloadClient = selectedTorrents.every(
(m, _, arr) => m.downloadClient === arr[0].downloadClient,
)
? selectedTorrents[0].downloadClient
: null;
this.updateSettingsHostDownloadAction = selectedTorrents.every(
(m, _, arr) => m.hostDownloadAction === arr[0].hostDownloadAction,
)
? selectedTorrents[0].hostDownloadAction
: null;
this.updateSettingsCategory = selectedTorrents.every((m, _, arr) => m.category === arr[0].category)
? selectedTorrents[0].category
: null;
this.updateSettingsPriority = selectedTorrents.every((m, _, arr) => m.priority === arr[0].priority)
? selectedTorrents[0].priority
: null;
this.updateSettingsDownloadRetryAttempts = selectedTorrents.every(
(m, _, arr) => m.downloadRetryAttempts === arr[0].downloadRetryAttempts,
)
? selectedTorrents[0].downloadRetryAttempts
: null;
this.updateSettingsTorrentRetryAttempts = selectedTorrents.every(
(m, _, arr) => m.torrentRetryAttempts === arr[0].torrentRetryAttempts,
)
? selectedTorrents[0].torrentRetryAttempts
: null;
this.updateSettingsDeleteOnError = selectedTorrents.every((m, _, arr) => m.deleteOnError === arr[0].deleteOnError)
? selectedTorrents[0].deleteOnError
: null;
this.updateSettingsTorrentLifetime = selectedTorrents.every((m, _, arr) => m.lifetime === arr[0].lifetime)
? selectedTorrents[0].lifetime
: null;
this.isChangeSettingsModalActive = true;
}
public changeSettingsCancel(): void {
this.isChangeSettingsModalActive = false;
}
public changeSettingsOk(): void {
this.changingSettings = true;
const calls: Observable<void>[] = [];
const selectedTorrents = this.getSelectedTorrentModels();
selectedTorrents.forEach((torrent) => {
if (this.updateSettingsDownloadClient != null) {
torrent.downloadClient = this.updateSettingsDownloadClient;
}
if (this.updateSettingsHostDownloadAction != null) {
torrent.hostDownloadAction = this.updateSettingsHostDownloadAction;
}
if (this.updateSettingsCategory != null) {
torrent.category = this.updateSettingsCategory;
}
if (this.updateSettingsPriority != null) {
torrent.priority = this.updateSettingsPriority;
}
if (this.updateSettingsDownloadRetryAttempts != null) {
torrent.downloadRetryAttempts = this.updateSettingsDownloadRetryAttempts;
}
if (this.updateSettingsTorrentRetryAttempts != null) {
torrent.torrentRetryAttempts = this.updateSettingsTorrentRetryAttempts;
}
if (this.updateSettingsDeleteOnError != null) {
torrent.deleteOnError = this.updateSettingsDeleteOnError;
}
if (this.updateSettingsTorrentLifetime != null) {
torrent.lifetime = this.updateSettingsTorrentLifetime;
}
calls.push(this.torrentService.update(torrent));
});
forkJoin(calls).subscribe({
complete: () => {
this.isChangeSettingsModalActive = false;
this.changingSettings = false;
this.clearSelectedTorrents();
},
error: (err) => {
this.changeSettingsError = err.error;
this.changingSettings = false;
},
});
}
toggleDeleteSelectAllOptions() {
this.deleteData = this.deleteSelectAll;
this.deleteRdTorrent = this.deleteSelectAll;
this.deleteLocalFiles = this.deleteSelectAll;
}
updateDeleteSelectAll() {
this.deleteSelectAll = this.deleteData && this.deleteRdTorrent && this.deleteLocalFiles;
}
ngOnDestroy(): void {
clearInterval(this.timer);
this.mobileQuery?.removeEventListener('change', this.mobileQueryListener);
}
public selectTorrent(torrent: Torrent): void {
this.showFiles[torrent.torrentId] = !this.showFiles[torrent.torrentId];
private setTorrents(torrents: Torrent[]): void {
this.torrents = torrents;
this.pruneSelectedTorrents();
this.applySorting();
}
if (this.showFiles[torrent.torrentId]) {
this.torrentService.getDetails(torrent.torrentId).subscribe((result) => {
torrent.files = result.files;
torrent.downloads = result.downloads;
private applySorting(): void {
this.sortedTorrents = sortItems(this.torrents, this.sortProperty, this.sortDirection, (torrent, field) => {
switch (field) {
case 'files.length':
return torrent.filesCount ?? torrent.files?.length ?? 0;
case 'downloads.length':
return torrent.downloadsCount ?? torrent.downloads?.length ?? 0;
case 'status':
return torrent.statusText ?? getTorrentStatus(torrent);
default:
return getSortFieldValue(torrent, field);
}
});
}
torrent.files.forEach((file) => {
const downloads = torrent.downloads.filter(
(m) => m.link === file.path
);
private getSelectedTorrentModels(): Torrent[] {
return this.torrents.filter((torrent) => this.selectedTorrentIds.has(torrent.torrentId));
}
if (downloads.length > 0) {
file.download = downloads[0];
}
});
});
private pruneSelectedTorrents(): void {
const torrentIds = new Set(this.torrents.map((torrent) => torrent.torrentId));
for (const torrentId of this.selectedTorrentIds) {
if (!torrentIds.has(torrentId)) {
this.selectedTorrentIds.delete(torrentId);
}
}
this.syncSelectedTorrents();
}
public trackByMethod(index: number, el: Torrent): string {
return el.torrentId;
private clearSelectedTorrents(): void {
this.selectedTorrentIds.clear();
this.syncSelectedTorrents();
}
private syncSelectedTorrents(): void {
this.selectedTorrents = this.torrents
.filter((torrent) => this.selectedTorrentIds.has(torrent.torrentId))
.map((torrent) => torrent.torrentId);
}
}

View file

@ -1,50 +1,157 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import * as signalR from '@microsoft/signalr';
import { Observable, Subject } from 'rxjs';
import { Torrent } from './models/torrent.model';
import { Torrent, TorrentFileAvailability } from './models/torrent.model';
import { DiskSpaceStatus } from './models/disk-space-status.model';
import { RateLimitStatus } from './models/rate-limit-status.model';
import { APP_BASE_HREF } from '@angular/common';
@Injectable({
providedIn: 'root',
})
export class TorrentService {
constructor(private http: HttpClient) {}
private http = inject(HttpClient);
private baseHref = inject(APP_BASE_HREF);
public update$: Subject<Torrent[]> = new Subject();
public diskSpaceStatus$: Subject<DiskSpaceStatus> = new Subject();
public rateLimitStatus$: Subject<RateLimitStatus> = new Subject();
private connection: signalR.HubConnection;
constructor() {
this.connect();
}
public connect(): void {
if (this.connection != null) {
return;
}
this.connection = new signalR.HubConnectionBuilder()
.withUrl(`${this.baseHref}hub`)
.withAutomaticReconnect()
.build();
this.connection.on('update', (torrents: Torrent[]) => {
this.update$.next(torrents);
});
this.connection.on('diskSpaceStatus', (status: any) => {
this.diskSpaceStatus$.next(status);
});
this.connection.on('rateLimitStatus', (status: any) => {
this.rateLimitStatus$.next(status);
});
this.connection.onreconnected(() => {
this.getDiskSpaceStatus().subscribe({
next: (status) => {
if (status) {
this.diskSpaceStatus$.next(status);
}
},
});
});
this.connection.start().catch((err) => console.error(err));
}
public getList(): Observable<Torrent[]> {
return this.http.get<Torrent[]>(`/Api/Torrents`);
return this.http.get<Torrent[]>(`${this.baseHref}Api/Torrents`);
}
public getDetails(torrentId: string): Observable<Torrent> {
return this.http.get<Torrent>(`/Api/Torrents/${torrentId}`);
public get(torrentId: string): Observable<Torrent> {
return this.http.get<Torrent>(`${this.baseHref}Api/Torrents/Get/${torrentId}`);
}
public uploadMagnet(
magnetLink: string,
autoDownload: boolean,
autoDelete: boolean
): Observable<void> {
return this.http.post<void>(`/Api/Torrents/UploadMagnet`, {
public getDiskSpaceStatus(): Observable<DiskSpaceStatus | null> {
return this.http.get<DiskSpaceStatus | null>(`${this.baseHref}Api/Torrents/DiskSpaceStatus`);
}
public getRateLimitStatus(): Observable<RateLimitStatus | null> {
return this.http.get<RateLimitStatus | null>(`${this.baseHref}Api/Torrents/RateLimitStatus`);
}
public uploadMagnet(magnetLink: string, torrent: Torrent): Observable<void> {
return this.http.post<void>(`${this.baseHref}Api/Torrents/UploadMagnet`, {
magnetLink,
autoDownload,
autoDelete,
torrent,
});
}
public uploadFile(
file: File,
autoDownload: boolean,
autoDelete: boolean
): Observable<void> {
public uploadFile(file: File, torrent: Torrent): Observable<void> {
const formData: FormData = new FormData();
formData.append('file', file);
formData.append('formData', JSON.stringify({ autoDownload, autoDelete }));
return this.http.post<void>(`/Api/Torrents/UploadFile`, formData);
formData.append('formData', JSON.stringify({ torrent }));
return this.http.post<void>(`${this.baseHref}Api/Torrents/UploadFile`, formData);
}
public download(torrentId: string): Observable<void> {
return this.http.get<void>(`/Api/Torrents/Download/${torrentId}`);
public uploadNzbLink(nzbLink: string, torrent: Torrent): Observable<void> {
return this.http.post<void>(`${this.baseHref}Api/Torrents/UploadNzbLink`, {
nzbLink,
torrent,
});
}
public delete(torrentId: string): Observable<void> {
return this.http.delete<void>(`/Api/Torrents/${torrentId}`);
public uploadNzbFile(file: File, torrent: Torrent): Observable<void> {
const formData: FormData = new FormData();
formData.append('file', file);
formData.append('formData', JSON.stringify({ torrent }));
return this.http.post<void>(`${this.baseHref}Api/Torrents/UploadNzbFile`, formData);
}
public checkFilesMagnet(magnetLink: string): Observable<TorrentFileAvailability[]> {
return this.http.post<TorrentFileAvailability[]>(`${this.baseHref}Api/Torrents/CheckFilesMagnet`, {
magnetLink,
});
}
public checkFiles(file: File): Observable<TorrentFileAvailability[]> {
const formData: FormData = new FormData();
formData.append('file', file);
return this.http.post<TorrentFileAvailability[]>(`${this.baseHref}Api/Torrents/CheckFiles`, formData);
}
public delete(
torrentId: string,
deleteData: boolean,
deleteRdTorrent: boolean,
deleteLocalFiles: boolean,
): Observable<void> {
return this.http.post<void>(`${this.baseHref}Api/Torrents/Delete/${torrentId}`, {
deleteData,
deleteRdTorrent,
deleteLocalFiles,
});
}
public retry(torrentId: string): Observable<void> {
return this.http.post<void>(`${this.baseHref}Api/Torrents/Retry/${torrentId}`, {});
}
public retryDownload(downloadId: string): Observable<void> {
return this.http.post<void>(`${this.baseHref}Api/Torrents/RetryDownload/${downloadId}`, {});
}
public update(torrent: Torrent): Observable<void> {
return this.http.put<void>(`${this.baseHref}Api/Torrents/Update`, torrent);
}
public verifyRegex(
includeRegex: string,
excludeRegex: string,
magnetLink: string,
): Observable<{ includeError: string; excludeError: string; selectedFiles: TorrentFileAvailability[] }> {
return this.http.post<{ includeError: string; excludeError: string; selectedFiles: TorrentFileAvailability[] }>(
`${this.baseHref}Api/Torrents/VerifyRegex`,
{
includeRegex,
excludeRegex,
magnetLink,
},
);
}
}

View file

@ -0,0 +1,705 @@
<div class="tabs">
<ul>
<li [ngClass]="{ 'is-active': activeTab === 0 }" (click)="activeTab = 0">
<a>General</a>
</li>
<li [ngClass]="{ 'is-active': activeTab === 1 }" (click)="activeTab = 1">
<a>Torrent Files</a>
</li>
<li [ngClass]="{ 'is-active': activeTab === 2 }" (click)="activeTab = 2">
<a>Downloads</a>
</li>
</ul>
</div>
@if (torrent === null || torrent === undefined) {
<div>
<div class="fa-3x">
<i class="fas fa-spinner fa-spin"></i>
</div>
</div>
} @else {
<div>
@if (activeTab === 0) {
<div class="flex-container">
<div style="flex: 1 1 0">
<div class="field is-grouped is-grouped-multiline action-buttons">
<div class="control">
<button class="button is-danger" (click)="showDeleteModal()">Delete Torrent</button>
</div>
<div class="control">
<button class="button is-primary" (click)="showRetryModal()">Retry Torrent</button>
</div>
<div class="control">
<button class="button is-light" (click)="showUpdateSettingsModal()">Change Settings</button>
</div>
</div>
<div class="field">
<label class="label">Status</label>
{{ torrent | status }}
</div>
<div class="field">
<label class="label">Retry count</label>
{{ torrent.retryCount }} / {{ torrent.torrentRetryAttempts }}
</div>
<div class="field">
<label class="label">Hash</label>
<span class="break-all">{{ torrent.hash }}</span>
</div>
<div class="field">
<label class="label">Priority</label>
{{ torrent.priority || "" }}
</div>
<div class="field">
<label class="label">Category</label>
{{ torrent.category || "(no category set)" }}
</div>
<div class="field">
<label class="label">Downloader</label>
@switch (torrent.downloadClient) {
@case (0) {
Bezadd
}
@case (1) {
Aria2c
}
@case (2) {
Symlink Downloader
}
@case (3) {
Synology DownloadStation
}
}
</div>
<div class="field">
<label class="label">Post Download Action</label>
@switch (torrent.hostDownloadAction) {
@case (0) {
Download all files to host
}
@case (1) {
Don't download files to host
}
}
</div>
<div class="field">
<label class="label">Post Torrent Download Action</label>
@switch (torrent.downloadAction) {
@case (0) {
Download all files above a certain size
}
@case (1) {
Download all available files on Real-Debrid above a certain size
}
@case (2) {
Pick files I want to download
}
}
</div>
<div class="field">
<label class="label">Finished action</label>
@switch (torrent.finishedAction) {
@case (0) {
Do nothing
}
@case (1) {
Remove torrent from Real-Debrid and Real-Debrid Client
}
@case (2) {
Remove torrent from Real-Debrid
}
@case (3) {
Remove torrent from client
}
}
</div>
<div class="field">
<label class="label">Finished action delay</label>
{{ torrent.finishedActionDelay || 0 }}
@if (torrent.finishedActionDelay === 1) {
minute
} @else {
minutes
}
</div>
<div class="field">
<label class="label">Minimum file size to download</label>
{{ torrent.downloadMinSize }}MB
</div>
<div class="field">
<label class="label">Include files</label>
{{ torrent.includeRegex }}
</div>
<div class="field">
<label class="label">Exclude files</label>
{{ torrent.excludeRegex }}
</div>
@if (!torrent.isFile) {
<div class="field">
<label class="label">Magnet</label>
@if (!copied) {
<span [cdkCopyToClipboard]="torrent.fileOrMagnet" (click)="copied = true">
Click to copy magnet link to clipboard
</span>
} @else {
<span>Link copied to clipboard!</span>
}
</div>
} @else {
<div class="field">
<label class="label">Torrent file</label>
<span (click)="download()">Click to download torrent file</span>
</div>
}
<div class="field">
<label class="label">Added on</label>
{{ torrent.added | date: "fullDate" }} {{ torrent.added | date: "mediumTime" }}
</div>
<div class="field">
<label class="label">Files selected on</label>
@if (torrent.filesSelected === null) {
(no files selected yet)
} @else {
{{ torrent.filesSelected | date: "fullDate" }} {{ torrent.filesSelected | date: "mediumTime" }}
}
</div>
<div class="field">
<label class="label">Completed on</label>
@if (torrent.completed === null) {
(not completed yet)
} @else {
{{ torrent.completed | date: "fullDate" }} {{ torrent.completed | date: "mediumTime" }}
}
</div>
</div>
<div style="flex: 1 1 0">
<div class="field">
<label class="label">Real-Debrid ID</label>
{{ torrent.rdId }}
</div>
<div class="field">
<label class="label">Real-Debrid Name</label>
{{ torrent.rdName }}
</div>
<div class="field">
<label class="label">Real-Debrid Size</label>
{{ torrent.rdSize | filesize }}
</div>
<div class="field">
<label class="label">Real-Debrid Host</label>
{{ torrent.rdHost }}
</div>
<div class="field">
<label class="label">Real-Debrid Split</label>
{{ torrent.rdSplit }}
</div>
<div class="field">
<label class="label">Real-Debrid Progress</label>
{{ torrent.rdProgress || 0 }}%
</div>
<div class="field">
<label class="label">Real-Debrid Status</label>
@switch (torrent.rdStatus) {
@case (0) {
Processing
}
@case (1) {
Waiting For File Selection
}
@case (2) {
Downloading
}
@case (3) {
Finished
}
@case (99) {
Error
}
}
({{ torrent.rdStatusRaw }})
</div>
<div class="field">
<label class="label">Real-Debrid Added</label>
{{ torrent.rdAdded | date: "fullDate" }} {{ torrent.rdAdded | date: "mediumTime" }}
</div>
<div class="field">
<label class="label">Real-Debrid Ended</label>
{{ torrent.rdEnded | date: "fullDate" }} {{ torrent.rdEnded | date: "mediumTime" }}
</div>
<div class="field">
<label class="label">Real-Debrid Speed</label>
{{ torrent.rdSpeed || 0 }}
</div>
<div class="field">
<label class="label">Real-Debrid Seeders</label>
{{ torrent.rdSeeders || "0" }}
</div>
</div>
</div>
}
@if (activeTab === 1) {
<div>
<div class="field">
<div class="table-container">
<table class="table is-fullwidth">
<thead>
<tr>
<th>ID</th>
<th>Path</th>
<th>Size</th>
<th>Selected</th>
</tr>
</thead>
<tbody>
@for (file of torrent.files; track file.id) {
<tr>
<td>
{{ file.id }}
</td>
<td class="break-all">
{{ file.path }}
</td>
<td>
{{ file.bytes | filesize }}
</td>
<td>
@if (file.selected) {
<i class="fas fa-check" style="color: green"></i>
} @else {
<i class="fas fa-times" style="color: red"></i>
}
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
}
@if (activeTab === 2) {
<div>
<div class="field">
<div class="table-container">
<table class="table is-fullwidth is-hoverable">
<thead>
<tr>
<th style="width: 35px"></th>
<th>Link</th>
<th>Size</th>
<th>Status</th>
</tr>
</thead>
<tbody>
@for (download of torrent.downloads; track download.downloadId) {
@let expanded = downloadExpanded[download.downloadId];
<tr (click)="downloadExpanded[download.downloadId] = !expanded">
<td style="width: 35px">
@if (!expanded) {
<i class="fas fa-caret-right"></i>
} @else {
<i class="fas fa-caret-down"></i>
}
</td>
<td class="break-all">
@if (download.link) {
{{ download.link | decodeURI }}
}
@if (!download.link) {
{{ download.path }}
}
</td>
<td>
{{ download.bytesTotal | filesize }}
</td>
<td>
{{ download | downloadStatus }}
</td>
</tr>
@if (expanded) {
<tr class="separator">
<td style="width: 35px"></td>
<td colspan="5">
<div class="flex-container">
<div style="flex: 1 1 0">
<div class="field is-grouped">
<div class="control">
<button class="button is-primary" (click)="showDownloadRetryModal(download.downloadId)">
Retry Download
</button>
</div>
</div>
@if (download.error) {
<div class="field">
<label class="label">Error</label>
{{ download.error }}
</div>
}
<div class="field">
<label class="label">Real-Debrid Unrestricted Link</label>
@if (download.link) {
<a class="break-all" href="{{ download.link }}" target="_blank"> {{ download.link | decodeURI }}</a>
}
</div>
<div class="field break-all">
<label class="label">Real-Debrid Link</label>
{{ download.path }}
</div>
<div class="field">
<label class="label">Download</label>
{{ download.bytesDone | filesize }} / {{ download.bytesTotal | filesize }} ({{
download.speed | filesize
}}/s)
</div>
<div class="field">
<label class="label">Retry Count</label>
{{ download.retryCount }} / {{ torrent.downloadRetryAttempts }}
</div>
</div>
<div style="flex: 1 1 0">
<div class="field">
<label class="label">Added</label>
@if (download.added) {
{{ download.added | date: "fullDate" }} {{ download.added | date: "mediumTime" }}
} @else {
(not added yet)
}
</div>
<div class="field">
<label class="label">Download Queued</label>
@if (download.downloadQueued) {
{{ download.downloadQueued | date: "fullDate" }}
{{ download.downloadQueued | date: "mediumTime" }}
} @else {
(not queued for downloading yet)
}
</div>
<div class="field">
<label class="label">Download Started</label>
@if (download.downloadStarted) {
{{ download.downloadStarted | date: "fullDate" }}
{{ download.downloadStarted | date: "mediumTime" }}
} @else {
(not started downloading yet)
}
</div>
<div class="field">
<label class="label">Download Finished</label>
@if (download.downloadFinished) {
{{ download.downloadFinished | date: "fullDate" }}
{{ download.downloadFinished | date: "mediumTime" }}
} @else {
(not finished yet)
}
</div>
<div class="field">
<label class="label">Unpacking Queued</label>
@if (download.unpackingQueued) {
{{ download.unpackingQueued | date: "fullDate" }}
{{ download.unpackingQueued | date: "mediumTime" }}
} @else {
(not queued for unpacking yet)
}
</div>
<div class="field">
<label class="label">Unpacking Started</label>
@if (download.unpackingStarted) {
{{ download.unpackingStarted | date: "fullDate" }}
{{ download.unpackingStarted | date: "mediumTime" }}
} @else {
(not started unpacking yet)
}
</div>
<div class="field">
<label class="label">Unpacking Finished</label>
@if (download.unpackingFinished) {
{{ download.unpackingFinished | date: "fullDate" }}
{{ download.unpackingFinished | date: "mediumTime" }}
} @else {
(not finished unpacking yet)
}
</div>
<div class="field">
<label class="label">Completed</label>
@if (download.completed) {
{{ download.completed | date: "fullDate" }}
{{ download.completed | date: "mediumTime" }}
} @else {
(not completed yet)
}
</div>
</div>
</div>
</td>
</tr>
}
}
</tbody>
</table>
</div>
</div>
</div>
}
</div>
}
<div class="modal" [class.is-active]="isDeleteModalActive">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Delete torrent</p>
<button class="delete" aria-label="close" (click)="deleteCancel()"></button>
</header>
<section class="modal-card-body">
<p>Are you sure you want to delete this torrent?</p>
<div class="field">
<label class="label"></label>
<div class="control">
<label class="checkbox">
<input type="checkbox" [(ngModel)]="deleteData" (change)="updateDeleteSelectAll()" />
Delete Torrent from client
</label>
<br />
<label class="checkbox">
<input type="checkbox" [(ngModel)]="deleteRdTorrent" (change)="updateDeleteSelectAll()" />
Delete Torrent from provider
</label>
<br />
<label class="checkbox">
<input type="checkbox" [(ngModel)]="deleteLocalFiles" (change)="updateDeleteSelectAll()" />
Delete local files
</label>
<br />
<br />
<label class="checkbox">
<input type="checkbox" [(ngModel)]="deleteSelectAll" (change)="toggleDeleteSelectAllOptions()" />
Select All
</label>
</div>
</div>
<div class="notification is-primary">
Deleting a torrent from Real-Debrid will automatically delete it here too.
</div>
@if (deleteError?.length > 0) {
<div class="notification is-danger is-light">Error deleting torrent: {{ deleteError }}</div>
}
</section>
<footer class="modal-card-foot">
<button
class="button is-success"
(click)="deleteOk()"
[disabled]="deleting"
[ngClass]="{ 'is-loading': deleting }"
>
Delete
</button>
<button class="button" (click)="deleteCancel()" [disabled]="deleting" [ngClass]="{ 'is-loading': deleting }">
Cancel
</button>
</footer>
</div>
</div>
<div class="modal" [class.is-active]="isRetryModalActive">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Retry torrent</p>
<button class="delete" aria-label="close" (click)="retryCancel()"></button>
</header>
<section class="modal-card-body">
<p>Are you sure you want to retry this torrent?</p>
<p>
This action will delete all the torrent data + all local downloads. Then it will re-add the original magnet link
or torrent file to Real-Debrid.
</p>
@if (retryError?.length > 0) {
<div class="notification is-danger is-light">Error retrying torrent: {{ retryError }}</div>
}
</section>
<footer class="modal-card-foot">
<button
class="button is-success"
(click)="retryOk()"
[disabled]="retrying"
[ngClass]="{ 'is-loading': retrying }"
>
Retry
</button>
<button class="button" (click)="retryCancel()" [disabled]="retrying" [ngClass]="{ 'is-loading': retrying }">
Cancel
</button>
</footer>
</div>
</div>
<div class="modal" [class.is-active]="isDownloadRetryModalActive">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Retry download</p>
<button class="delete" aria-label="close" (click)="downloadRetryCancel()"></button>
</header>
<section class="modal-card-body">
<p>Are you sure you want to retry this download?</p>
<p>This action will remove the local download and re-download the file from Real-Debrid.</p>
@if (downloadRetryError?.length > 0) {
<div class="notification is-danger is-light">Error retrying download: {{ downloadRetryError }}</div>
}
</section>
<footer class="modal-card-foot">
<button
class="button is-success"
(click)="downloadRetryOk()"
[disabled]="downloadRetrying"
[ngClass]="{ 'is-loading': downloadRetrying }"
>
Retry
</button>
<button
class="button"
(click)="downloadRetryCancel()"
[disabled]="downloadRetrying"
[ngClass]="{ 'is-loading': downloadRetrying }"
>
Cancel
</button>
</footer>
</div>
</div>
<div class="modal" [class.is-active]="isUpdateSettingsModalActive">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Update torrent settings</p>
<button class="delete" aria-label="close" (click)="updateSettingsCancel()"></button>
</header>
<section class="modal-card-body">
<p>
Settings that are blank do not have the same values for each torrent. Updating a setting with a blank value will
not update it.
</p>
<div class="field">
<label class="label">Downloader</label>
<div class="control select is-fullwidth">
<select [(ngModel)]="updateSettingsDownloadClient">
<option [ngValue]="0">Bezzad</option>
<option [ngValue]="1">Aria2c</option>
<option [ngValue]="2">Symlink Downloader</option>
<option [ngValue]="3">Synology DownloadStation</option>
</select>
</div>
<p class="help">
Select which downloader is used to download this torrent from the debrid provider to your host.
</p>
</div>
<div class="field">
<label class="label">Post Download Action</label>
<div class="control select is-fullwidth">
<select [(ngModel)]="updateSettingsHostDownloadAction">
<option [ngValue]="null"></option>
<option [ngValue]="0">Download all files to host</option>
<option [ngValue]="1">Don't download any files to host</option>
</select>
</div>
<p class="help">
When a torrent is finished downloading on the provider, perform this action. Use this setting if you only want
to add files to Real-Debrid but not download them to the host.
</p>
</div>
<div class="field">
<label class="label">Category</label>
<div class="control">
<input class="input" type="text" [(ngModel)]="updateSettingsCategory" />
</div>
<p class="help">The category becomes a sub-folder in your main download path.</p>
</div>
<div class="field">
<label class="label">Priority</label>
<div class="control">
<input class="input" type="number" step="1" [(ngModel)]="updateSettingsPriority" />
</div>
<p class="help">
Set the priority for this torrent where 1 is the highest. When empty it will be assigned the lowest priority.
</p>
</div>
<div class="field">
<label class="label">Automatic retry downloads</label>
<div class="control">
<input
class="input"
type="number"
max="1000"
min="0"
step="1"
[(ngModel)]="updateSettingsDownloadRetryAttempts"
/>
</div>
<p class="help">When a single download fails it will retry it this many times before marking it as failed.</p>
</div>
<div class="field">
<label class="label">Automatic retry torrent</label>
<div class="control">
<input
class="input"
type="number"
max="1000"
min="0"
step="1"
[(ngModel)]="updateSettingsTorrentRetryAttempts"
/>
</div>
<p class="help">
When a single download has failed multiple times (see setting above) or when the torrent itself received an
error it will retry the full torrent this many times before marking it failed.
</p>
</div>
<div class="field">
<label class="label">Delete download when in error</label>
<div class="control">
<input class="input" type="number" max="1000" min="0" step="1" [(ngModel)]="updateSettingsDeleteOnError" />
</div>
<p class="help">
When a download has been in error for this many minutes, delete it from the provider and the client. 0 to
disable.
</p>
</div>
<div class="field">
<label class="label">Torrent maximum lifetime</label>
<div class="control">
<input
class="input"
type="number"
max="100000"
min="0"
step="1"
[(ngModel)]="updateSettingsTorrentLifetime"
/>
</div>
<p class="help">
The maximum lifetime of a torrent in minutes. When this time has passed, mark the torrent as error. If the
torrent is completed and has downloads, the lifetime setting will not apply. 0 to disable.
</p>
</div>
</section>
<footer class="modal-card-foot">
<button
class="button is-success"
(click)="updateSettingsOk()"
[disabled]="updating"
[ngClass]="{ 'is-loading': updating }"
>
Save
</button>
<button
class="button"
(click)="updateSettingsCancel()"
[disabled]="updating"
[ngClass]="{ 'is-loading': updating }"
>
Cancel
</button>
</footer>
</div>
</div>

View file

@ -0,0 +1,45 @@
.label {
margin-bottom: 0;
}
table {
tr {
cursor: pointer;
}
}
.break-all {
word-break: break-all;
}
.flex-container {
display: flex;
flex: 1 1 0;
gap: 20px;
flex-direction: row;
@media screen and (max-width: 1279px) {
flex-direction: column;
}
}
@media screen and (max-width: 768px) {
.action-buttons {
.control {
width: 100%;
.button {
width: 100%;
}
}
}
.tabs ul {
flex-wrap: wrap;
li {
flex-grow: 1;
text-align: center;
}
}
}

View file

@ -0,0 +1,276 @@
import { Component, DestroyRef, OnInit, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ActivatedRoute, Router } from '@angular/router';
import { saveAs } from 'file-saver-es';
import { Torrent } from '../models/torrent.model';
import { TorrentService } from '../torrent.service';
import { NgClass, DatePipe } from '@angular/common';
import { CdkCopyToClipboard } from '@angular/cdk/clipboard';
import { FormsModule } from '@angular/forms';
import { TorrentStatusPipe } from '../torrent-status.pipe';
import { DownloadStatusPipe } from '../download-status.pipe';
import { DecodeURIPipe } from '../decode-uri.pipe';
import { FileSizePipe } from '../filesize.pipe';
import { EMPTY, distinctUntilChanged, map, switchMap, tap, catchError } from 'rxjs';
@Component({
selector: 'app-torrent',
templateUrl: './torrent.component.html',
styleUrls: ['./torrent.component.scss'],
imports: [
NgClass,
CdkCopyToClipboard,
FormsModule,
DatePipe,
TorrentStatusPipe,
DownloadStatusPipe,
DecodeURIPipe,
FileSizePipe,
],
standalone: true,
})
export class TorrentComponent implements OnInit {
private destroyRef = inject(DestroyRef);
private activatedRoute = inject(ActivatedRoute);
private router = inject(Router);
private torrentService = inject(TorrentService);
private currentTorrentId: string | null = null;
public torrent: Torrent;
public activeTab: number = 0;
public copied: boolean = false;
public downloadExpanded: { [downloadId: string]: boolean } = {};
public isDeleteModalActive: boolean;
public deleteError: string;
public deleting: boolean;
public deleteSelectAll: boolean;
public deleteData: boolean;
public deleteRdTorrent: boolean;
public deleteLocalFiles: boolean;
public isRetryModalActive: boolean;
public retryError: string;
public retrying: boolean;
public isDownloadRetryModalActive: boolean;
public downloadRetryError: string;
public downloadRetrying: boolean;
public downloadRetryId: string;
public isUpdateSettingsModalActive: boolean;
public updateSettingsDownloadClient: number;
public updateSettingsHostDownloadAction: number;
public updateSettingsCategory: string;
public updateSettingsPriority: number;
public updateSettingsDownloadRetryAttempts: number;
public updateSettingsTorrentRetryAttempts: number;
public updateSettingsDeleteOnError: number;
public updateSettingsTorrentLifetime: number;
public updating: boolean;
ngOnInit(): void {
this.activatedRoute.params
.pipe(
map((params) => params['id'] as string),
distinctUntilChanged(),
tap((torrentId) => {
this.currentTorrentId = torrentId;
}),
switchMap((torrentId) =>
this.torrentService.get(torrentId).pipe(
catchError(() => {
this.router.navigate(['/']);
return EMPTY;
}),
),
),
takeUntilDestroyed(this.destroyRef),
)
.subscribe((torrent) => {
this.torrent = torrent;
this.currentTorrentId = torrent.torrentId;
});
this.torrentService.update$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((result) => {
this.update(result);
});
}
public update(torrents: Torrent[]): void {
const torrentId = this.currentTorrentId ?? this.torrent?.torrentId;
if (!torrentId) {
return;
}
const updatedTorrent = torrents.find((m) => m.torrentId === torrentId);
if (updatedTorrent == null) {
return;
}
const currentTorrent = this.torrent;
const hasIncomingFiles = Array.isArray(updatedTorrent.files) && updatedTorrent.files.length > 0;
this.torrent = {
...currentTorrent,
...updatedTorrent,
fileOrMagnet: updatedTorrent.fileOrMagnet ?? currentTorrent?.fileOrMagnet,
files: hasIncomingFiles ? updatedTorrent.files : currentTorrent?.files ?? [],
downloads: updatedTorrent.downloads ?? currentTorrent?.downloads ?? [],
};
}
public download(): void {
const binaryString = window.atob(this.torrent.fileOrMagnet);
const byteArray = new Uint8Array(binaryString.length);
for (let index = 0; index < binaryString.length; index += 1) {
byteArray[index] = binaryString.charCodeAt(index);
}
const blob = new Blob([byteArray], { type: 'application/x-bittorrent' });
saveAs(blob, `${this.torrent.rdName}.torrent`);
}
public showDeleteModal(): void {
this.deleteData = false;
this.deleteRdTorrent = false;
this.deleteLocalFiles = false;
this.deleteError = null;
this.isDeleteModalActive = true;
}
public deleteCancel(): void {
this.isDeleteModalActive = false;
}
public deleteOk(): void {
this.deleting = true;
this.torrentService
.delete(this.torrent.torrentId, this.deleteData, this.deleteRdTorrent, this.deleteLocalFiles)
.subscribe({
next: () => {
this.isDeleteModalActive = false;
this.deleting = false;
this.router.navigate(['/']);
},
error: (err) => {
this.deleteError = err.error;
this.deleting = false;
},
});
}
public showRetryModal(): void {
this.retryError = null;
this.isRetryModalActive = true;
}
public retryCancel(): void {
this.isRetryModalActive = false;
}
public retryOk(): void {
this.retrying = true;
this.torrentService.retry(this.torrent.torrentId).subscribe({
next: () => {
this.isRetryModalActive = false;
this.retrying = false;
this.router.navigate(['/']);
},
error: (err) => {
this.retryError = err.error;
this.retrying = false;
},
});
}
public showDownloadRetryModal(downloadId: string): void {
this.downloadRetryId = downloadId;
this.downloadRetryError = null;
this.isDownloadRetryModalActive = true;
}
public downloadRetryCancel(): void {
this.isDownloadRetryModalActive = false;
}
public downloadRetryOk(): void {
this.downloadRetrying = true;
this.torrentService.retryDownload(this.downloadRetryId).subscribe({
next: () => {
this.isDownloadRetryModalActive = false;
this.downloadRetrying = false;
},
error: (err) => {
this.downloadRetryError = err.error;
this.downloadRetrying = false;
},
});
}
public showUpdateSettingsModal(): void {
this.updateSettingsDownloadClient = this.torrent.downloadClient;
this.updateSettingsHostDownloadAction = this.torrent.hostDownloadAction;
this.updateSettingsCategory = this.torrent.category;
this.updateSettingsPriority = this.torrent.priority;
this.updateSettingsDownloadRetryAttempts = this.torrent.downloadRetryAttempts;
this.updateSettingsTorrentRetryAttempts = this.torrent.torrentRetryAttempts;
this.updateSettingsDeleteOnError = this.torrent.deleteOnError;
this.updateSettingsTorrentLifetime = this.torrent.lifetime;
this.isUpdateSettingsModalActive = true;
}
public updateSettingsCancel(): void {
this.isUpdateSettingsModalActive = false;
}
public updateSettingsOk(): void {
this.updating = true;
this.torrent.downloadClient = this.updateSettingsDownloadClient;
this.torrent.hostDownloadAction = this.updateSettingsHostDownloadAction;
this.torrent.category = this.updateSettingsCategory;
this.torrent.priority = this.updateSettingsPriority;
this.torrent.downloadRetryAttempts = this.updateSettingsDownloadRetryAttempts;
this.torrent.torrentRetryAttempts = this.updateSettingsTorrentRetryAttempts;
this.torrent.deleteOnError = this.updateSettingsDeleteOnError;
this.torrent.lifetime = this.updateSettingsTorrentLifetime;
this.torrentService.update(this.torrent).subscribe({
next: () => {
this.isUpdateSettingsModalActive = false;
this.updating = false;
},
error: () => {
this.isUpdateSettingsModalActive = false;
this.updating = false;
},
});
}
toggleDeleteSelectAllOptions() {
this.deleteData = this.deleteSelectAll;
this.deleteRdTorrent = this.deleteSelectAll;
this.deleteLocalFiles = this.deleteSelectAll;
}
updateDeleteSelectAll() {
this.deleteSelectAll = this.deleteData && this.deleteRdTorrent && this.deleteLocalFiles;
}
}

View file

@ -1,10 +0,0 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class UsersService {
constructor() {}
public getLoggedInUser() {}
}

View file

@ -1,3 +1,3 @@
export const environment = {
production: true
production: true,
};

View file

@ -1,9 +1,9 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// `ng build` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false
production: false,
};
/*
@ -13,4 +13,4 @@ export const environment = {
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
// import 'zone.js/plugins/zone-error'; // Included with Angular CLI.

View file

@ -1,9 +1,19 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Real-Debrid Torrent Client</title>
<base href="/" />
<script>
(function () {
const baseElement = document.querySelector("base");
if (baseElement) {
window["_app_base"] = baseElement.getAttribute("href");
console.log("setting base href to " + window["_app_base"]);
}
})();
</script>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>

View file

@ -1,13 +1,28 @@
import { enableProdMode } from '@angular/core';
import { enableProdMode, importProvidersFrom, provideZoneChangeDetection } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { FileSizePipe } from './app/filesize.pipe';
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { AuthInterceptor } from './app/auth.interceptor';
import { APP_BASE_HREF } from '@angular/common';
import { BrowserModule, bootstrapApplication } from '@angular/platform-browser';
import { AppRoutingModule } from './app/app-routing.module';
import { FormsModule } from '@angular/forms';
import { ClipboardModule } from '@angular/cdk/clipboard';
import { AppComponent } from './app/app.component';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch(err => console.error(err));
bootstrapApplication(AppComponent, {
providers: [
provideZoneChangeDetection(),
importProvidersFrom(BrowserModule, AppRoutingModule, FormsModule, ClipboardModule),
FileSizePipe,
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
{ provide: APP_BASE_HREF, useValue: (window as any)['_app_base'] || '/' },
provideHttpClient(withInterceptorsFromDi()),
],
}).catch((err) => console.error(err));

View file

@ -8,8 +8,8 @@
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
* automatically update themselves. This includes recent versions of Safari, Chrome (including
* Opera), Edge on the desktop, and iOS and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
@ -18,16 +18,6 @@
* BROWSER POLYFILLS
*/
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/**
* Web Animations `@angular/platform-browser/animations`
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
*/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
@ -55,7 +45,7 @@
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
import 'zone.js'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS

View file

@ -1,2 +1,2 @@
@import "../node_modules/bulma/bulma.sass";
@import "../node_modules/@fortawesome/fontawesome-free/css/all.css";
@forward "../node_modules/bulma/bulma.scss";
@forward "../node_modules/@fortawesome/fontawesome-free/css/all.css";

View file

@ -1,25 +0,0 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: {
context(path: string, deep?: boolean, filter?: RegExp): {
keys(): string[];
<T>(id: string): T;
};
};
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

View file

@ -1,14 +1,10 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.d.ts"
]
"files": ["src/main.ts", "src/polyfills.ts"],
"include": ["src/**/*.d.ts"]
}

View file

@ -1,23 +1,30 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"module": "esnext",
"moduleResolution": "node",
"moduleResolution": "bundler",
"importHelpers": true,
"target": "es2015",
"lib": [
"es2018",
"dom"
]
"strictNullChecks": false,
"target": "ES2022",
"module": "es2020",
"useDefineForClassFields": false
},
"angularCompilerOptions": {
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

View file

@ -1,18 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine",
"node"
]
},
"files": [
"src/test.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}

View file

@ -1,103 +0,0 @@
{
"extends": "tslint:recommended",
"rules": {
"align": {
"options": ["parameters", "statements"]
},
"array-type": false,
"arrow-return-shorthand": true,
"curly": true,
"deprecation": {
"severity": "warning"
},
"component-class-suffix": true,
"contextual-lifecycle": true,
"directive-class-suffix": true,
"directive-selector": [true, "attribute", "app", "camelCase"],
"component-selector": [true, "element", "app", "kebab-case"],
"eofline": true,
"import-blacklist": [true, "rxjs/Rx"],
"import-spacing": true,
"indent": {
"options": ["spaces"]
},
"max-classes-per-file": false,
"max-line-length": [true, 140],
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-console": [true, "debug", "info", "time", "timeEnd", "trace"],
"no-empty": false,
"no-inferrable-types": [true, "ignore-params"],
"no-non-null-assertion": true,
"no-redundant-jsdoc": true,
"no-switch-case-fall-through": true,
"no-var-requires": false,
"object-literal-key-quotes": [true, "as-needed"],
"quotemark": [true, "single"],
"semicolon": {
"options": ["always"]
},
"space-before-function-paren": {
"options": {
"anonymous": "never",
"asyncArrow": "always",
"constructor": "never",
"method": "never",
"named": "never"
}
},
"typedef-whitespace": {
"options": [
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
},
{
"call-signature": "onespace",
"index-signature": "onespace",
"parameter": "onespace",
"property-declaration": "onespace",
"variable-declaration": "onespace"
}
]
},
"variable-name": {
"options": ["ban-keywords", "check-format", "allow-pascal-case"]
},
"whitespace": {
"options": [
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type",
"check-typecast"
]
},
"no-conflicting-lifecycle": true,
"no-host-metadata-property": true,
"no-input-rename": true,
"no-inputs-metadata-property": true,
"no-output-native": true,
"no-output-on-prefix": true,
"no-output-rename": true,
"no-outputs-metadata-property": true,
"template-banana-in-box": true,
"template-no-negated-async": true,
"use-lifecycle-interface": true,
"use-pipe-transform-interface": true
},
"rulesDirectory": ["codelyzer"]
}

View file

@ -0,0 +1,13 @@
#!/usr/bin/with-contenv bash
# cleanup pid if it exists
[[ -e /config/rtdclient.pid ]] && \
rm -rf /config/rtdclient.pid
[[ -e /data/rtdclient.pid ]] && \
rm -rf /data/rtdclient.pid
# permissions
echo "Setting permissions"
chown -R abc:abc \
/app /data

View file

@ -0,0 +1 @@
oneshot

View file

@ -0,0 +1 @@
/etc/s6-overlay/s6-rc.d/init-rdt-client/run

Some files were not shown because too many files have changed in this diff Show more