Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 42 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# Real-Debrid Torrent & Usenet Client

This is a web interface to manage your torrents on Real-Debrid, AllDebrid, Premiumize, TorBox or DebridLink. It supports the following features:
This is a web interface to manage your torrents on Real-Debrid, AllDebrid, Premiumize, TorBox, DebridLink or HIGHWAY. It supports the following features:

- 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
- Add usenet downloads through NZB files (TorBox, Premiumize and HIGHWAY only)
- Download all files from Real-Debrid, AllDebrid, Premiumize, TorBox or HIGHWAY 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, AllDebrid, Premiumize, Torbox or DebridLink!**
**You will need a Premium service at Real-Debrid, AllDebrid, Premiumize, Torbox or DebridLink, or a HIGHWAY account!**

[Click here to sign up for Real-Debrid.](https://real-debrid.com/?id=1348683)

Expand All @@ -21,6 +21,8 @@ This is a web interface to manage your torrents on Real-Debrid, AllDebrid, Premi

[Click here to sign up for DebridLink.](https://debrid-link.fr/id/6duif)

[Click here for HIGHWAY.](https://high-way.me/)

<sub>(referal links so I can get a few free premium days)</sub>

## Docker Setup
Expand Down Expand Up @@ -171,6 +173,41 @@ It has the following options:

> **The Synology *Download Path* (above) and rdt-client's general *Download path* must resolve to the exact same physical folder on the NAS.**

### HIGHWAY provider

[HIGHWAY](https://high-way.me/) (`high-way.me`) is also a multi hoster that downloads torrents **and** usenet server-side. rdt-client pushes magnets/NZBs to it and pulls the finished files down.

Configuration under `Settings` &rarr; `Provider`:

- **Provider**: `HIGHWAY`
- **API Key**: a token you create once in the [HIGHWAY Center](https://high-way.me/) (log in, open **Sitzungen** &rarr; **API-Token erstellen**). This is a 32-character hex string and is revocable from that same list.

Getting the bytes to disk — two modes:

**A. Symlink + rclone WebDAV mount (recommended).** Mount HIGHWAY's WebDAV and use the **Symlink** download client. Example `rclone.conf`:

```ini
[highway]
type = webdav
url = https://cloud.high-way.me/dav/
vendor = other
user = <highway username>
pass = <highway WebDAV / "usenet" password, or the same 32-hex API token from Settings>
```

```
rclone mount highway: /mnt/highway --vfs-cache-mode full
```

Set **Rclone mount path** to `/mnt/highway/` and set the **Post Torrent Download Action** so it does **not** remove the transfer from the provider. rdt-client symlinks the finished files straight from the mount — no re-download.

**B. Internal / Aria2c (no mount).** rdt-client downloads each file directly using its own signed URL. The usual file filters (include/exclude regex, minimum size) apply. If HIGHWAY hasn't signed per-file it falls back to fetching the whole transfer as one ZIP.

Notes:

- HIGHWAY torrents leech from peers server-side, so expect real `downloading` progress for minutes/hours. Only content HIGHWAY already has is linked instantly.
- When HIGHWAY is at its active-transfer limit a new add goes into HIGHWAY's own queue and reports `queued`; this is normal and it will start automatically.

### Troubleshooting

- If you forgot your logins simply delete the `rdtclient.db` and restart the service.
Expand Down Expand Up @@ -199,7 +236,7 @@ Notice: the progress and ETA reported in Sonarr's Activity tab will not be accur

#### 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.
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, Premiumize or HIGHWAY.

1. Login to Sonarr or Radarr and click `Settings`.
1. Go to the `Download Client` tab and click the plus to add.
Expand Down
1 change: 1 addition & 0 deletions client/src/app/models/profile.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export class Profile {
public provider: string;
public userName: string;
public expiration: Date;
public premium: boolean;
public currentVersion: string;
public latestVersion: string;
public isInsecure: boolean;
Expand Down
2 changes: 2 additions & 0 deletions client/src/app/navbar/navbar.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
</span>
@if (profile.expiration) {
<span>Premium Status: {{ profile.expiration | date }}</span>
} @else if (profile.premium) {
<span>Premium Status: Unlimited</span>
} @else {
<span class="no-premium">Not premium</span>
}
Expand Down
3 changes: 3 additions & 0 deletions client/src/app/navbar/navbar.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ export class NavbarComponent implements OnInit {
case 'DebridLink':
this.providerLink = 'https://debrid-link.com/';
break;
case 'HIGHWAY':
this.providerLink = 'https://high-way.me/';
break;
}
});

Expand Down
11 changes: 10 additions & 1 deletion client/src/app/setup/setup.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
<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:
Not premium yet? You have the choice of 6 providers:
<br />
<a href="https://real-debrid.com/?id=1348683" target="_blank" rel="noopener"
>Use this link to sign up to Real-Debrid.</a
Expand All @@ -81,6 +81,8 @@
<a href="https://debrid-link.com/" target="_blank" rel="noopener"
>Use this link to sign up to DebridLink.</a
>
<br />
<a href="https://high-way.me/" target="_blank" rel="noopener">Use this link to sign up to HIGHWAY.</a>
<small>(Referal links)</small>
</div>
<div class="field">
Expand All @@ -92,6 +94,7 @@
<option [ngValue]="2">Premiumize</option>
<option [ngValue]="3">TorBox</option>
<option [ngValue]="4">DebridLink</option>
<option [ngValue]="5">HIGHWAY</option>
</select>
</div>
</div>
Expand Down Expand Up @@ -147,6 +150,12 @@
>.
</p>
}
@if (provider === 5) {
<p class="help">
Log in to your HIGHWAY Center, open <strong>Sitzungen</strong> and use
<strong>API-Token erstellen</strong> to create a token.
</p>
}
</div>
<div class="field">
<button
Expand Down
16 changes: 11 additions & 5 deletions client/src/app/torrent/torrent.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -200,19 +200,25 @@
<div class="field">
<label class="label">Real-Debrid Status</label>
@switch (torrent.rdStatus) {
@case (0) {
@case (RealDebridStatus.Queued) {
Queued
}
@case (RealDebridStatus.Processing) {
Processing
}
@case (1) {
@case (RealDebridStatus.WaitingForFileSelection) {
Waiting For File Selection
}
@case (2) {
@case (RealDebridStatus.Downloading) {
Downloading
}
@case (3) {
@case (RealDebridStatus.Finished) {
Finished
}
@case (99) {
@case (RealDebridStatus.Uploading) {
Uploading
}
@case (RealDebridStatus.Error) {
Error
}
}
Expand Down
4 changes: 3 additions & 1 deletion client/src/app/torrent/torrent.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ 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 { RealDebridStatus, Torrent } from '../models/torrent.model';
import { TorrentService } from '../torrent.service';
import { NgClass, DatePipe } from '@angular/common';
import { CdkCopyToClipboard } from '@angular/cdk/clipboard';
Expand Down Expand Up @@ -36,6 +36,8 @@ export class TorrentComponent implements OnInit {
private torrentService = inject(TorrentService);
private currentTorrentId: string | null = null;

protected readonly RealDebridStatus = RealDebridStatus;

public torrent: Torrent;

public activeTab: number = 0;
Expand Down
5 changes: 4 additions & 1 deletion server/RdtClient.Data/Enums/Provider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@ public enum Provider
TorBox,

[Description("DebridLink")]
DebridLink
DebridLink,

[Description("HIGHWAY")]
HIGHWAY
}
6 changes: 6 additions & 0 deletions server/RdtClient.Data/Models/DebridClient/DebridClientUser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,10 @@ public class DebridClientUser
{
public String? Username { get; set; }
public DateTimeOffset? Expiration { get; set; }

/// <summary>
/// Explicitly premium even without a dated <see cref="Expiration" /> (e.g. an unlimited/lifetime plan).
/// Most providers only ever set <see cref="Expiration" />, which is premium enough on its own.
/// </summary>
public Boolean Premium { get; set; }
}
4 changes: 3 additions & 1 deletion server/RdtClient.Data/Models/Internal/DbSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ public class DbSettingsProvider
<a href=""https://www.premiumize.me/"" target=""_blank"" rel=""noopener"">https://www.premiumize.me/</a>
<a href=""https://debrid-link.com/"" target=""_blank"" rel=""noopener"">https://debrid-link.com/</a>
<a href=""https://torbox.app/"" target=""_blank"" rel=""noopener"">https://torbox.app/</a>
<a href=""https://high-way.me/"" target=""_blank"" rel=""noopener"">https://high-way.me/</a>
At this point only 1 provider can be used at the time.")]
public Provider Provider { get; set; } = Provider.RealDebrid;

Expand All @@ -208,7 +209,8 @@ public class DbSettingsProvider
or
<a href=""https://torbox.app/settings/"" target=""_blank"" rel=""noopener"">https://torbox.app/settings/</a>
or
<a href=""https://debrid-link.com/webapp/apikey"" target=""_blank"" rel=""noopener"">https://debrid-link.com/webapp/apikey</a>")]
<a href=""https://debrid-link.com/webapp/apikey"" target=""_blank"" rel=""noopener"">https://debrid-link.com/webapp/apikey</a>
For HIGHWAY, log in to your HIGHWAY Center and create a token under ""Sitzungen"" &rarr; ""API-Token erstellen"".")]
public String ApiKey { get; set; } = "";

/// <summary>
Expand Down
1 change: 1 addition & 0 deletions server/RdtClient.Data/Models/Internal/Profile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public class Profile
public String? Provider { get; set; }
public String? UserName { get; set; }
public DateTimeOffset? Expiration { get; set; }
public Boolean Premium { get; set; }
public String? CurrentVersion { get; set; }
public String? LatestVersion { get; set; }
public Boolean? IsInsecure { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public WatchFolderCheckerTests()
DownloadClient = new()
});

_torrentsServiceMock = new(null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, _settings, new TorrentRunnerState());
_torrentsServiceMock = new(null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, _settings, new TorrentRunnerState());

_serviceProviderMock
.Setup(x => x.GetService(typeof(IServiceScopeFactory)))
Expand Down
1 change: 1 addition & 0 deletions server/RdtClient.Service.Test/Services/NzbTorrentsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public NzbTorrentsTest()
null!,
null!,
null!,
null!,
new TestSettings(),
new TorrentRunnerState());
}
Expand Down
2 changes: 1 addition & 1 deletion server/RdtClient.Service.Test/Services/QBittorrentTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public QBittorrentTest()
_loggerMock = new();
_settings = new();
_runnerState = new();
_torrentsMock = new(null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, _settings, _runnerState);
_torrentsMock = new(null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, _settings, _runnerState);
_authenticationMock = new(null!, null!, null!);

_qBittorrent = new(_loggerMock.Object, _settings, _authenticationMock.Object, _torrentsMock.Object, null!, _runnerState);
Expand Down
2 changes: 1 addition & 1 deletion server/RdtClient.Service.Test/Services/SabnzbdTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class SabnzbdTest
public SabnzbdTest()
{
_settings = new();
_torrentsMock = new(null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, _settings, new TorrentRunnerState());
_torrentsMock = new(null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, _settings, new TorrentRunnerState());
_torrentsMock.Setup(t => t.Get()).ReturnsAsync(new List<Torrent>());
}

Expand Down
Loading