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
This commit is contained in:
Cucumberrbob 2025-03-23 13:30:05 +00:00
commit 5a1b96b909
No known key found for this signature in database
GPG key ID: 2B935C47401C3614
53 changed files with 2056 additions and 12891 deletions

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

@ -0,0 +1,140 @@
name: Release
on:
push:
branches: [main]
permissions:
id-token: write
contents: write
packages: write
jobs:
test:
name: Build and Test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: '9'
- name: Test Backend
working-directory: server
run: dotnet test
version:
name: Increment version
needs: test
runs-on: ubuntu-latest
outputs:
version: ${{ steps.increment-version.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Increment latest version tag
id: increment-version
run: |
# Retry mechanism
$maxRetries = 5
$retryDelay = 5
$attempt = 0
$success = $false
while ($attempt -lt $maxRetries -and -not $success) {
try {
git fetch --tags
$tags = git tag --sort=-v:refname | Where-Object { $_ -match '^v\d+\.\d+\.\d+$' }
$latestTag = $tags -split '\n' | Select-Object -First 1
$versionNumbers = $latestTag -split '\.'
$versionNumbers[-1] = [string]([int]$versionNumbers[-1] + 1)
$newVersion = $versionNumbers -join '.'
Write-Output "New incremented version is $newVersion"
echo "version=$newVersion" | Out-File -Append -FilePath $Env:GITHUB_OUTPUT
git tag $newVersion
git push origin $newVersion
$success = $true
Write-Output "Tag $newVersion pushed successfully!"
} catch {
Write-Output "Failed to push tag $newVersion. Attempt $($attempt + 1) of $maxRetries."
Write-Output "Error details: $($_.Exception.Message)"
$attempt++
if ($attempt -lt $maxRetries) {
Write-Output "Retrying in $retryDelay seconds..."
Start-Sleep -Seconds $retryDelay
} else {
Write-Error "Failed to push tag $newVersion after $maxRetries attempts. Exiting."
exit 1
}
}
}
shell: pwsh
release:
name: Create GitHub release
needs: [test, version]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate changelog
id: changelog
uses: mikepenz/release-changelog-builder-action@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "lts/*"
- name: Set up .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: '9'
- name: Build Frontend
working-directory: client
run: |
npm ci
npm run build
- name: Build and Publish Backend
working-directory: server
shell: pwsh
run: |
$v = "${{ needs.version.outputs.version }}".TrimStart('v')
dotnet restore
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
run: |
cd publish
zip -r ../RealDebridClient.zip .
cd ..
- name: Create Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.version.outputs.version }}
name: ${{ needs.version.outputs.version }}
body: ${{ steps.changelog.outputs.changelog }}
draft: false
prerelease: false
files: RealDebridClient.zip
token: ${{ secrets.GITHUB_TOKEN }}

14
client/.ncurc.js Normal file
View file

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

View file

@ -57,8 +57,8 @@
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
"maximumWarning": "2mb",
"maximumError": "2mb"
},
{
"type": "anyComponentStyle",

9733
client/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -6,48 +6,48 @@
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"update": "ng update --force --allow-dirty @angular/cli @angular/core @angular/cdk @angular/flex-layout",
"update": "ng update --force --allow-dirty @angular/cli @angular/core @angular/cdk",
"lint": "ng lint",
"prettier": "prettier --write \"./**/*.{ts,html,json}\""
},
"private": true,
"dependencies": {
"@angular/animations": "^19.2.0",
"@angular/cdk": "^19.2.1",
"@angular/common": "^19.2.0",
"@angular/compiler": "^19.2.0",
"@angular/core": "^19.2.0",
"@angular/forms": "^19.2.0",
"@angular/platform-browser": "^19.2.0",
"@angular/platform-browser-dynamic": "^19.2.0",
"@angular/router": "^19.2.0",
"@fortawesome/fontawesome-free": "^6.4.2",
"@microsoft/signalr": "^6.0.21",
"bulma": "^0.9.4",
"curray": "^1.0.11",
"@angular/animations": "^19.2.3",
"@angular/cdk": "^19.2.6",
"@angular/common": "^19.2.3",
"@angular/compiler": "^19.2.3",
"@angular/core": "^19.2.3",
"@angular/forms": "^19.2.3",
"@angular/platform-browser": "^19.2.3",
"@angular/platform-browser-dynamic": "^19.2.3",
"@angular/router": "^19.2.3",
"@fortawesome/fontawesome-free": "^6.7.2",
"@microsoft/signalr": "^8.0.7",
"bulma": "^1.0.3",
"curray": "^1.0.12",
"file-saver-es": "^2.0.5",
"ngx-filesize": "^3.0.5",
"rxjs": "~7.8.1",
"tslib": "^2.6.2",
"zone.js": "~0.15.0"
"filesize": "^10.1.6",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "^0.15.0"
},
"devDependencies": {
"@angular-eslint/builder": "19.1.0",
"@angular-eslint/eslint-plugin": "19.1.0",
"@angular-eslint/eslint-plugin-template": "19.1.0",
"@angular-eslint/schematics": "19.1.0",
"@angular-eslint/template-parser": "19.1.0",
"@angular/build": "^19.2.0",
"@angular/cli": "^19.2.0",
"@angular/compiler-cli": "^19.2.0",
"@angular/language-service": "^19.2.0",
"@types/file-saver": "^2.0.5",
"@types/file-saver-es": "^2.0.1",
"@types/node": "^20.5.8",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"eslint": "^8.57.0",
"prettier": "^3.0.3",
"typescript": "5.7.3"
"@angular-eslint/builder": "19.2.1",
"@angular-eslint/eslint-plugin": "19.2.1",
"@angular-eslint/eslint-plugin-template": "19.2.1",
"@angular-eslint/schematics": "19.2.1",
"@angular-eslint/template-parser": "19.2.1",
"@angular/build": "^19.2.4",
"@angular/cli": "^19.2.4",
"@angular/compiler-cli": "^19.2.3",
"@angular/language-service": "^19.2.3",
"@types/file-saver": "^2.0.7",
"@types/file-saver-es": "^2.0.3",
"@types/node": "^22.13.11",
"@typescript-eslint/eslint-plugin": "^8.27.0",
"@typescript-eslint/parser": "^8.27.0",
"eslint": "^9.23.0",
"prettier": "^3.5.3",
"typescript": "5.8.2"
}
}

View file

@ -5,10 +5,10 @@ import { Torrent, TorrentFileAvailability } from '../models/torrent.model';
import { SettingsService } from '../settings.service';
@Component({
selector: 'app-add-new-torrent',
templateUrl: './add-new-torrent.component.html',
styleUrls: ['./add-new-torrent.component.scss'],
standalone: false
selector: 'app-add-new-torrent',
templateUrl: './add-new-torrent.component.html',
styleUrls: ['./add-new-torrent.component.scss'],
standalone: false,
})
export class AddNewTorrentComponent implements OnInit {
public fileName: string;

View file

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

View file

@ -1,11 +1,10 @@
import 'curray';
import { ClipboardModule } from '@angular/cdk/clipboard';
import { APP_BASE_HREF } from '@angular/common';
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { curray } from 'curray';
import { FileSizePipe, NgxFilesizeModule } from 'ngx-filesize';
import { AddNewTorrentComponent } from './add-new-torrent/add-new-torrent.component';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@ -23,8 +22,7 @@ import { TorrentStatusPipe } from './torrent-status.pipe';
import { TorrentTableComponent } from './torrent-table/torrent-table.component';
import { TorrentComponent } from './torrent/torrent.component';
import { SortPipe } from './sort.pipe';
curray();
import { FileSizePipe } from './filesize.pipe';
@NgModule({
declarations: [
@ -43,9 +41,10 @@ curray();
ProfileComponent,
Nl2BrPipe,
SortPipe,
FileSizePipe,
],
bootstrap: [AppComponent],
imports: [BrowserModule, AppRoutingModule, FormsModule, NgxFilesizeModule, ClipboardModule],
imports: [BrowserModule, AppRoutingModule, FormsModule, ClipboardModule],
providers: [
FileSizePipe,
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },

View file

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

View file

@ -1,10 +1,10 @@
import { Pipe, PipeTransform } from '@angular/core';
import { FileSizePipe } from 'ngx-filesize';
import { Download } from './models/download.model';
import { FileSizePipe } from './filesize.pipe';
@Pipe({
name: 'downloadStatus',
standalone: false
name: 'downloadStatus',
standalone: false,
})
export class DownloadStatusPipe implements PipeTransform {
constructor(private pipe: FileSizePipe) {}

View file

@ -0,0 +1,20 @@
import { Pipe, PipeTransform } from '@angular/core';
import { filesize } from 'filesize';
@Pipe({
name: 'filesize',
standalone: false,
})
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

@ -3,10 +3,10 @@ import { Router } from '@angular/router';
import { AuthService } from '../auth.service';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss'],
standalone: false
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss'],
standalone: false,
})
export class LoginComponent {
public userName: string;

View file

@ -1,10 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-main-layout',
templateUrl: './main-layout.component.html',
styleUrls: ['./main-layout.component.scss'],
standalone: false
selector: 'app-main-layout',
templateUrl: './main-layout.component.html',
styleUrls: ['./main-layout.component.scss'],
standalone: false,
})
export class MainLayoutComponent {
constructor() {}

View file

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

View file

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

View file

@ -55,16 +55,25 @@
<a class="navbar-item" routerLink="profile"> Profile </a>
<a class="navbar-item" (click)="logout()"> Logout </a>
<hr class="navbar-divider" />
<a href="https://github.com/rogerfar/rdt-client" target="_blank" class="navbar-item">Version 2.0.102</a>
<a href="https://github.com/rogerfar/rdt-client" target="_blank" class="navbar-item">Version {{ version }}</a>
</div>
</div>
</div>
</div>
</nav>
<div
class="notification is-warning"
*ngIf="profile && profile.latestVersion && profile.currentVersion !== profile.latestVersion"
class="notification"
[ngClass]="profile.isInsecure ? 'is-danger' : 'is-warning'"
*ngIf="
profile &&
(!profile.disableUpdateNotification || profile.isInsecure) &&
profile.latestVersion &&
profile.currentVersion !== profile.latestVersion
"
>
Version {{ profile.latestVersion }} of RealDebrid Client was found. You are currently on version
{{ profile.currentVersion }}.
<span *ngIf="profile.isInsecure"> 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

@ -1,26 +1,33 @@
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { NavigationEnd, Router } from '@angular/router';
import { AuthService } from '../auth.service';
import { Profile } from '../models/profile.model';
import { SettingsService } from '../settings.service';
@Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.scss'],
standalone: false
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.scss'],
standalone: false,
})
export class NavbarComponent implements OnInit {
public showMobileMenu = false;
public profile: Profile;
public providerLink: string;
public version: string;
constructor(
private settingsService: SettingsService,
private authService: AuthService,
private router: Router,
) {}
) {
this.router.events.subscribe((event) => {
if (event instanceof NavigationEnd) {
this.showMobileMenu = false;
}
});
}
ngOnInit(): void {
this.settingsService.getProfile().subscribe((result) => {
@ -44,6 +51,10 @@ export class NavbarComponent implements OnInit {
break;
}
});
this.settingsService.getVersion().subscribe((result) => {
this.version = result.version;
});
}
public logout(): void {

View file

@ -2,8 +2,8 @@ import { Pipe, PipeTransform, SecurityContext, VERSION } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
@Pipe({
name: 'nl2br',
standalone: false
name: 'nl2br',
standalone: false,
})
export class Nl2BrPipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) {}

View file

@ -2,10 +2,10 @@ import { Component } from '@angular/core';
import { AuthService } from '../auth.service';
@Component({
selector: 'app-profile',
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.scss'],
standalone: false
selector: 'app-profile',
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.scss'],
standalone: false,
})
export class ProfileComponent {
constructor(private authService: AuthService) {}

View file

@ -4,6 +4,7 @@ import { Observable } from 'rxjs';
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',
@ -26,6 +27,10 @@ export class SettingsService {
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 });
}

View file

@ -3,10 +3,10 @@ import { SettingsService } from 'src/app/settings.service';
import { Setting } from '../models/setting.model';
@Component({
selector: 'app-settings',
templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss'],
standalone: false
selector: 'app-settings',
templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss'],
standalone: false,
})
export class SettingsComponent implements OnInit {
public activeTab = 0;

View file

@ -3,10 +3,10 @@ import { Router } from '@angular/router';
import { AuthService } from '../auth.service';
@Component({
selector: 'app-setup',
templateUrl: './setup.component.html',
styleUrls: ['./setup.component.scss'],
standalone: false
selector: 'app-setup',
templateUrl: './setup.component.html',
styleUrls: ['./setup.component.scss'],
standalone: false,
})
export class SetupComponent {
public userName: string;

View file

@ -1,8 +1,8 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'sort',
standalone: false
name: 'sort',
standalone: false,
})
export class SortPipe implements PipeTransform {
transform(array: any[], field: string, order: 'asc' | 'desc' = 'asc'): any[] {

View file

@ -1,10 +1,10 @@
import { Pipe, PipeTransform } from '@angular/core';
import { FileSizePipe } from 'ngx-filesize';
import { RealDebridStatus, Torrent } from './models/torrent.model';
import { FileSizePipe } from './filesize.pipe';
@Pipe({
name: 'status',
standalone: false
name: 'status',
standalone: false,
})
export class TorrentStatusPipe implements PipeTransform {
constructor(private pipe: FileSizePipe) {}

View file

@ -33,7 +33,7 @@
[checked]="selectedTorrents.contains(torrent.torrentId)"
/>
</td>
<td (click)="openTorrent(torrent.torrentId)">
<td (click)="openTorrent(torrent.torrentId)" class="break-all">
{{ torrent.rdName }}
</td>
<td>

View file

@ -1,6 +1,9 @@
table {
tr {
cursor: pointer;
td.break-all {
word-break: break-all;
}
}
}

View file

@ -5,10 +5,10 @@ import { TorrentService } from '../torrent.service';
import { forkJoin, Observable } from 'rxjs';
@Component({
selector: 'app-torrent-table',
templateUrl: './torrent-table.component.html',
styleUrls: ['./torrent-table.component.scss'],
standalone: false
selector: 'app-torrent-table',
templateUrl: './torrent-table.component.html',
styleUrls: ['./torrent-table.component.scss'],
standalone: false,
})
export class TorrentTableComponent implements OnInit {
public torrents: Torrent[] = [];

View file

@ -252,7 +252,7 @@
<td style="width: 35px"></td>
<td colspan="5">
<div class="flex-container">
<div style="flex: 1 1 0;">
<div style="flex: 1 1 0">
<div class="field is-grouped">
<div class="control">
<button class="button is-primary" (click)="showDownloadRetryModal(download.downloadId)">
@ -285,7 +285,7 @@
{{ download.retryCount }} / {{ torrent.downloadRetryAttempts }}
</div>
</div>
<div style="flex: 1 1 0;">
<div style="flex: 1 1 0">
<div class="field">
<label class="label">Added</label>
<ng-container *ngIf="download.added">

View file

@ -5,10 +5,10 @@ import { Torrent } from '../models/torrent.model';
import { TorrentService } from '../torrent.service';
@Component({
selector: 'app-torrent',
templateUrl: './torrent.component.html',
styleUrls: ['./torrent.component.scss'],
standalone: false
selector: 'app-torrent',
templateUrl: './torrent.component.html',
styleUrls: ['./torrent.component.scss'],
standalone: false,
})
export class TorrentComponent implements OnInit {
public torrent: Torrent;

View file

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

4611
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,20 +0,0 @@
{
"name": "rdt-client",
"version": "2.0.102",
"description": "This is a web interface to manage your torrents on Real-Debrid.",
"main": "index.js",
"dependencies": {
"gh-release": "^7.0.2"
},
"devDependencies": {},
"repository": {
"type": "git",
"url": "git+https://github.com/rogerfar/rdt-client.git"
},
"author": "Roger Far",
"license": "MIT",
"bugs": {
"url": "https://github.com/rogerfar/rdt-client/issues"
},
"homepage": "https://github.com/rogerfar/rdt-client#readme"
}

View file

@ -74,6 +74,10 @@ Supports the following parameters:
[DisplayName("Copy added torrent files")]
[Description("When a torrent file or magnet is added, create a copy in this directory.")]
public String? CopyAddedTorrents { get; set; } = null;
[DisplayName("Disable update notifications")]
[Description("Ignore update notifications. You will still be notified if the version you are running has a security vulnerability.")]
public Boolean DisableUpdateNotifications { get; set; } = false;
}
public class DbSettingsDownloadClient
@ -111,8 +115,8 @@ public class DbSettingsDownloadClient
[Description("Timeout in milliseconds before the downloader times out.")]
public Int32 Timeout { get; set; } = 5000;
[DisplayName("Proxy Server (only used for the Internal Downloader)")]
[Description("Address of a proxy server to download through (only used for the Internal Downloader).")]
[DisplayName("Proxy Server (only used for the Bezzad Downloader)")]
[Description("Address of a proxy server to download through (only used for the Bezzad Downloader).")]
public String? ProxyServer { get; set; } = null;
[DisplayName("Aria2c URL (only used for the Aria2c Downloader)")]

View file

@ -7,4 +7,6 @@ public class Profile
public DateTimeOffset? Expiration { get; set; }
public String? CurrentVersion { get; set; }
public String? LatestVersion { get; set; }
public Boolean? IsInsecure { get; set; }
public Boolean? DisableUpdateNotification { get; set; }
}

View file

@ -8,11 +8,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.2" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="9.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.2">
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.3" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="9.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View file

@ -10,16 +10,16 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AllDebrid.NET" Version="1.0.17" />
<PackageReference Include="AllDebrid.NET" Version="1.0.18" />
<PackageReference Include="coverlet.collector" Version="6.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="TestableIO.System.IO.Abstractions" Version="22.0.11" />
<PackageReference Include="TestableIO.System.IO.Abstractions.TestingHelpers" Version="22.0.11" />
<PackageReference Include="TestableIO.System.IO.Abstractions.Wrappers" Version="22.0.11" />
<PackageReference Include="TestableIO.System.IO.Abstractions" Version="22.0.12" />
<PackageReference Include="TestableIO.System.IO.Abstractions.TestingHelpers" Version="22.0.12" />
<PackageReference Include="TestableIO.System.IO.Abstractions.Wrappers" Version="22.0.12" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
<PrivateAssets>all</PrivateAssets>

View file

@ -1,6 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using RdtClient.Data.Enums;
using RdtClient.Service.Services;
namespace RdtClient.Service.BackgroundServices;
@ -26,8 +27,8 @@ public class ProviderUpdater(ILogger<TaskRunner> logger, IServiceProvider servic
try
{
var torrents = await torrentService.Get();
if (_nextUpdate < DateTime.UtcNow && ((torrents.Count > 0 && !Settings.Get.Provider.AutoImport) || Settings.Get.Provider.AutoImport))
if (_nextUpdate < DateTime.UtcNow && (Settings.Get.Provider.AutoImport || torrents.Any(t => t.RdStatus != TorrentStatus.Finished)))
{
logger.LogDebug($"Updating torrent info from debrid provider");

View file

@ -9,6 +9,11 @@ public class UpdateChecker(ILogger<UpdateChecker> logger) : BackgroundService
{
public static String? CurrentVersion { get; private set; }
public static String? LatestVersion { get; private set; }
public static Boolean? IsInsecure { get; private set; }
private static readonly List<String> KnownGhsaIds = [
];
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
@ -34,18 +39,9 @@ public class UpdateChecker(ILogger<UpdateChecker> logger) : BackgroundService
{
try
{
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.UserAgent.Add(new("RdtClient", CurrentVersion));
var response = await httpClient.GetStringAsync($"https://api.github.com/repos/rogerfar/rdt-client/tags?per_page=1", stoppingToken);
var gitHubReleases = await GitHubRequest<List<GitHubReleasesResponse>>("/repos/rogerfar/rdt-client/tags?per_page=1", stoppingToken);
var gitHubReleases = JsonConvert.DeserializeObject<List<GitHubReleasesResponse>>(response);
if (gitHubReleases == null || gitHubReleases.Count == 0)
{
return;
}
var latestRelease = gitHubReleases.FirstOrDefault(m => m.Name != null)?.Name;
var latestRelease = gitHubReleases?.FirstOrDefault(m => m.Name != null)?.Name;
if (latestRelease == null)
{
@ -59,6 +55,18 @@ public class UpdateChecker(ILogger<UpdateChecker> logger) : BackgroundService
}
LatestVersion = latestRelease;
var gitHubSecurityAdvisories = await GitHubRequest<List<GitHubSecurityAdvisoriesResponse>>("/repos/rogerfar/rdt-client/security-advisories", stoppingToken);
var unseenGhsaIds = gitHubSecurityAdvisories?.Where(advisory => !KnownGhsaIds.Contains(advisory.GhsaId));
if (unseenGhsaIds == null)
{
logger.LogWarning($"Unable to find security advisories on GitHub");
return;
}
IsInsecure = unseenGhsaIds.Any();
}
catch (Exception ex)
{
@ -70,10 +78,25 @@ public class UpdateChecker(ILogger<UpdateChecker> logger) : BackgroundService
logger.LogInformation("UpdateChecker stopped.");
}
private async Task<T?> GitHubRequest<T>(String endpoint, CancellationToken cancellationToken)
{
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.UserAgent.Add(new("RdtClient", CurrentVersion));
var response = await httpClient.GetStringAsync($"https://api.github.com{endpoint}", cancellationToken);
return JsonConvert.DeserializeObject<T>(response);
}
}
public class GitHubReleasesResponse
{
[JsonProperty("name")]
public String? Name { get; set; }
}
public class GitHubSecurityAdvisoriesResponse
{
[JsonProperty("ghsa_id")]
public required String GhsaId { get; set; }
}

View file

@ -9,23 +9,23 @@
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="AllDebrid.NET" Version="1.0.17" />
<PackageReference Include="Aria2.NET" Version="1.0.5" />
<PackageReference Include="AllDebrid.NET" Version="1.0.18" />
<PackageReference Include="Aria2.NET" Version="1.0.6" />
<PackageReference Include="DebridLinkFr.NET" Version="1.0.4" />
<PackageReference Include="Downloader" Version="3.3.3" />
<PackageReference Include="Downloader.NET" Version="1.0.14" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="9.0.2" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.2.0" />
<PackageReference Include="Downloader" Version="3.3.4" />
<PackageReference Include="Downloader.NET" Version="1.0.15" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="9.0.3" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.3.0" />
<PackageReference Include="MonoTorrent" Version="3.0.2" />
<PackageReference Include="Polly" Version="8.5.2" />
<PackageReference Include="Premiumize.NET" Version="1.0.9" />
<PackageReference Include="RD.NET" Version="2.1.7" />
<PackageReference Include="Premiumize.NET" Version="1.0.10" />
<PackageReference Include="RD.NET" Version="2.1.11" />
<PackageReference Include="Serilog" Version="4.2.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="SharpCompress" Version="0.39.0" />
<PackageReference Include="Synology.Api.Client" Version="0.3.91" />
<PackageReference Include="TestableIO.System.IO.Abstractions" Version="22.0.11" />
<PackageReference Include="TestableIO.System.IO.Abstractions.Wrappers" Version="22.0.11" />
<PackageReference Include="Synology.Api.Client" Version="0.3.93" />
<PackageReference Include="TestableIO.System.IO.Abstractions" Version="22.0.12" />
<PackageReference Include="TestableIO.System.IO.Abstractions.Wrappers" Version="22.0.12" />
<PackageReference Include="TorBox.NET" Version="1.5.0" />
</ItemGroup>

View file

@ -147,7 +147,7 @@ public class RealDebridTorrentClient(ILogger<RealDebridTorrentClient> logger, IH
public async Task SelectFiles(Data.Models.Data.Torrent torrent)
{
IList<TorrentClientFile> files;
List<TorrentClientFile> files;
Log("Seleting files", torrent);
@ -158,32 +158,17 @@ public class RealDebridTorrentClient(ILogger<RealDebridTorrentClient> logger, IH
}
else
{
Log("Selecting all files", torrent);
Log("Selecting files", torrent);
files = [.. torrent.Files];
}
Log($"Selecting {files.Count}/{torrent.Files.Count} files", torrent);
files = files.Where(f => fileFilter.IsDownloadable(torrent, f.Path, f.Bytes)).ToList();
if (files.Count == 0)
{
Log($"Filtered all files out! Downloading ALL files instead!", torrent);
files = torrent.Files;
}
Log($"Selecting {files.Count}/{torrent.Files.Count} files", torrent);
var fileIds = files.Select(m => m.Id.ToString()).ToArray();
Log($"Selecting files:");
foreach (var file in files)
{
Log($"{file.Id}: {file.Path} ({file.Bytes}b)");
}
Log("", torrent);
await GetClient().Torrents.SelectFilesAsync(torrent.RdId!, [.. fileIds]);
}

View file

@ -1,9 +1,11 @@
using System.Reflection;
using System.Diagnostics;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using TorBoxNET;
using RdtClient.Data.Enums;
using RdtClient.Data.Models.TorrentClient;
using System.Web;
using RdtClient.Data.Models.Data;
using RdtClient.Service.Helpers;
@ -24,7 +26,7 @@ public class TorBoxTorrentClient(ILogger<TorBoxTorrentClient> logger, IHttpClien
}
var httpClient = httpClientFactory.CreateClient();
httpClient.DefaultRequestHeaders.Add("User-Agent", "rdt-client");
httpClient.DefaultRequestHeaders.Add("User-Agent", $"rdt-client {Assembly.GetEntryAssembly()?.GetName().Version}");
httpClient.Timeout = TimeSpan.FromSeconds(Settings.Get.Provider.Timeout);
var torBoxNetClient = new TorBoxNetClient(null, httpClient, 5);

View file

@ -417,7 +417,9 @@ public class Torrents(
UserName = user.Username,
Expiration = user.Expiration,
CurrentVersion = UpdateChecker.CurrentVersion,
LatestVersion = UpdateChecker.LatestVersion
LatestVersion = UpdateChecker.LatestVersion,
IsInsecure = UpdateChecker.IsInsecure,
DisableUpdateNotification = Settings.Get.General.DisableUpdateNotifications
};
return profile;

View file

@ -67,7 +67,7 @@ public class AuthController(Authentication authentication, Settings settings) :
return Ok();
}
[AllowAnonymous]
[Authorize(Policy = "AuthSetting")]
[Route("SetupProvider")]
[HttpPost]
public async Task<ActionResult> SetupProvider([FromBody] AuthControllerSetupProviderRequest? request)
@ -82,13 +82,6 @@ public class AuthController(Authentication authentication, Settings settings) :
return StatusCode(401);
}
var user = await authentication.GetUser();
if (user != null)
{
return StatusCode(401);
}
await settings.Update("Provider:Provider", request.Provider);
await settings.Update("Provider:ApiKey", request.Token);

View file

@ -1,4 +1,5 @@
using System.Diagnostics;
using System.Reflection;
using Aria2NET;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@ -44,6 +45,18 @@ public class SettingsController(Settings settings, Torrents torrents) : Controll
var profile = await torrents.GetProfile();
return Ok(profile);
}
[HttpGet]
[Route("Version")]
public ActionResult<Version> Version()
{
var version = Assembly.GetExecutingAssembly().GetName().Version!;
return Ok(new
{
Version = version
});
}
[HttpPost]
[Route("TestPath")]

View file

@ -29,15 +29,15 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="9.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="9.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.2">
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="9.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="9.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.2" />
<PackageReference Include="Microsoft.Extensions.Identity.Core" Version="9.0.2" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.3" />
<PackageReference Include="Microsoft.Extensions.Identity.Core" Version="9.0.3" />
<PackageReference Include="Serilog" Version="4.2.0" />
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />

0
docker-build-dev.ps1 → tools/docker-build-dev.ps1 Executable file → Normal file
View file

0
docker-build.ps1 → tools/docker-build.ps1 Executable file → Normal file
View file

0
docker-publish.ps1 → tools/docker-publish.ps1 Executable file → Normal file
View file

View file

@ -3,14 +3,10 @@ $utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
$version = (Get-Content "package.json" | ConvertFrom-Json).version
$csProj = "$pwd\server\RdtClient.Web\RdtClient.Web.csproj"
$navbar = "$pwd\client\src\app\navbar\navbar.component.html";
$newCsProj = (Get-Content $csProj) -replace '<Version>.*?<\/Version>', "<Version>$version</Version>"
[System.IO.File]::WriteAllLines($csProj, $newCsProj, $utf8NoBomEncoding)
$newNavbar = (Get-Content $navbar) -replace 'Version .*?<', "Version $version<"
[System.IO.File]::WriteAllLines($navbar, $newNavbar, $utf8NoBomEncoding)
Write-Output "Commit and push now, press any key to continue"
[Console]::ReadKey()