From 70adae649a31bc68e2dcd61e16f35c917c22b164 Mon Sep 17 00:00:00 2001
From: Bilal Ahmed <45879053+bill-ahmed@users.noreply.github.com>
Date: Thu, 2 Sep 2021 19:09:51 -0400
Subject: [PATCH] Handle magnet links (#134)
* support for registering magnet url
* open upload dialog if manget url present
* misc. improvements
* enforce https
---
src/app/home/home.component.ts | 20 ++++++++++++++++++-
.../add-torrent-dialog.component.html | 2 +-
.../add-torrent-dialog.component.ts | 20 ++++++++++++++-----
.../web-ui-settings.component.html | 12 +++++++++++
.../web-ui-settings.component.ts | 20 ++++++++++++++++++-
.../notifications/snackbar.service.ts | 8 +++++++-
6 files changed, 73 insertions(+), 9 deletions(-)
diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts
index f7be786a..84be46c4 100644
--- a/src/app/home/home.component.ts
+++ b/src/app/home/home.component.ts
@@ -42,15 +42,19 @@ export class HomeComponent implements OnInit {
this.getUserPreferences();
this.isDarkTheme = this.theme.getThemeSubscription();
this.getQbitBuildInfo();
+
+ // Check if user was led here via a magnet download link
+ this.handleMagnetURLCheck();
}
/** Open the modal for adding a new torrent */
- openAddTorrentDialog(): void {
+ openAddTorrentDialog(opts?: any): void {
const addTorDialogRef = this.dialog.open(AddTorrentDialogComponent,
{
disableClose: true,
panelClass: "generic-dialog",
minWidth: "40%",
+ data: opts
}
);
@@ -79,6 +83,20 @@ export class HomeComponent implements OnInit {
handleAddTorrentDialogClosed(data: any): void {
}
+ handleMagnetURLCheck() {
+ let downloadHash = '#download=';
+
+ // Make sure it exists and is at the front
+ if(location.hash.indexOf(downloadHash) !== 0)
+ return;
+
+ // Now that it exists, we can take it by reading up to downloadHash's length
+ let target_url = decodeURIComponent(location.hash.substring(downloadHash.length));
+ history.replaceState('', document.title, location.pathname);
+
+ this.openAddTorrentDialog({ magnetURL: target_url });
+ }
+
public toggleTheme(): void {
this.theme.setDarkTheme(!this.theme.getCurrentValue());
}
diff --git a/src/app/modals/add-torrent-dialog/add-torrent-dialog.component.html b/src/app/modals/add-torrent-dialog/add-torrent-dialog.component.html
index a509adc0..e9625e2a 100644
--- a/src/app/modals/add-torrent-dialog/add-torrent-dialog.component.html
+++ b/src/app/modals/add-torrent-dialog/add-torrent-dialog.component.html
@@ -2,7 +2,7 @@
Upload Torrents
-
+
diff --git a/src/app/modals/add-torrent-dialog/add-torrent-dialog.component.ts b/src/app/modals/add-torrent-dialog/add-torrent-dialog.component.ts
index 69810916..1641d299 100644
--- a/src/app/modals/add-torrent-dialog/add-torrent-dialog.component.ts
+++ b/src/app/modals/add-torrent-dialog/add-torrent-dialog.component.ts
@@ -1,8 +1,8 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, Inject, OnInit } from '@angular/core';
// Material UI Components
import { MatFormField } from '@angular/material/form-field';
-import { MatDialogRef, MatDialog } from '@angular/material/dialog';
+import { MatDialogRef, MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { TorrentDataStoreService } from '../../services/torrent-management/torrent-data-store.service';
import { FileSystemDialogComponent } from '../file-system-dialog/file-system-dialog.component';
import { ThemeService } from '../../services/theme.service';
@@ -34,16 +34,24 @@ export class AddTorrentDialogComponent implements OnInit {
public serialized_nodes: SerializedNode[] = [];
/** Keep track of the mat-tab the user is currently in. */
- private currentTab: MatTabChangeEvent;
+ public currentTab: MatTabChangeEvent;
private fileSystemExplorerDialogREF: MatDialogRef
;
+ private inputData: any;
+
constructor(private appConfig: ApplicationConfigService, private dialogRef:MatDialogRef, private data_store: TorrentDataStoreService,
- private torrentParser: TorrentParserService, public fileSystemDialog: MatDialog, public snackbar: SnackbarService, private theme: ThemeService) { }
+ private torrentParser: TorrentParserService, public fileSystemDialog: MatDialog, public snackbar: SnackbarService, private theme: ThemeService,
+ @Inject(MAT_DIALOG_DATA) inputData) { this.inputData = inputData }
ngOnInit(): void {
this.isDarkTheme = this.theme.getThemeSubscription();
this.updateDefaultSaveLocationFromDisk();
this.appConfig.getUserPreferences().then(pref => { this.show_torrent_contents = pref.web_ui_options.upload_torrents?.show_parsed_torrents_from_file ?? true });
+
+ if(this.inputData?.magnetURL) {
+ this.urlsToUpload = this.inputData.magnetURL;
+ this.handleTabChange({ index: 1 } as any)
+ }
}
handleTabChange(event: MatTabChangeEvent) {
@@ -93,7 +101,9 @@ export class AddTorrentDialogComponent implements OnInit {
} catch (error) {
console.error('uploaded magnet URLs!', error);
- this.snackbar.enqueueSnackBar("Error uploding torrents, check console log for details.", { type: 'error' });
+
+ if(error?.status !== 200 && error?.statusText !== 'OK')
+ this.snackbar.enqueueSnackBar("Error uploding torrents, check console log for details.", { type: 'error' });
}
}
diff --git a/src/app/modals/settings/web-ui-settings/web-ui-settings.component.html b/src/app/modals/settings/web-ui-settings/web-ui-settings.component.html
index 5922ec7f..cfea190b 100644
--- a/src/app/modals/settings/web-ui-settings/web-ui-settings.component.html
+++ b/src/app/modals/settings/web-ui-settings/web-ui-settings.component.html
@@ -23,6 +23,18 @@ Notifications
+
+
+ Events
+
+
+
+
+
+
diff --git a/src/app/modals/settings/web-ui-settings/web-ui-settings.component.ts b/src/app/modals/settings/web-ui-settings/web-ui-settings.component.ts
index eb5aedf9..1a388952 100644
--- a/src/app/modals/settings/web-ui-settings/web-ui-settings.component.ts
+++ b/src/app/modals/settings/web-ui-settings/web-ui-settings.component.ts
@@ -5,6 +5,7 @@ import { FormControl, Validators } from '@angular/forms';
import { AuthService } from 'src/app/services/auth/auth.service';
import { NetworkConnectionInformationService } from 'src/app/services/network/network-connection-information.service';
import { ApplicationDefaults } from 'src/app/services/app/defaults';
+import { SnackbarService } from 'src/app/services/notifications/snackbar.service';
@Component({
selector: 'app-web-ui-settings',
@@ -36,7 +37,7 @@ export class WebUiSettingsComponent implements OnInit {
private web_ui_options: WebUISettings;
- constructor(private appConfig: ApplicationConfigService, private auth_service: AuthService) { this.theme_options = ApplicationConfigService.THEME_OPTIONS }
+ constructor(private appConfig: ApplicationConfigService, private auth_service: AuthService, private snackbar: SnackbarService) { this.theme_options = ApplicationConfigService.THEME_OPTIONS }
ngOnInit(): void {
this.web_ui_options = this.appConfig.getWebUISettings();
@@ -67,6 +68,23 @@ export class WebUiSettingsComponent implements OnInit {
}
}
+ registerMagnetHandler() {
+ if(location.protocol !== 'https:') {
+ this.snackbar.enqueueSnackBar('HTTPS is required to register magnet URLs!', { type: 'error' });
+ return;
+ }
+
+ if(typeof navigator.registerProtocolHandler !== 'function') {
+ this.snackbar.enqueueSnackBar('Your browser does not support registering magnet URLs.', { type: 'error' });
+ return;
+ }
+
+ let templateHashString = 'download=%s';
+ let templateURL = location.origin + location.pathname + `#${templateHashString}`;
+
+ navigator.registerProtocolHandler('magnet', templateURL, 'qBittorrent WebUI magnet handler');
+ }
+
_resetAllSettings() {
if(confirm('Are you sure you want to RESET ALL WEB UI settings?')) {
// Reset & refresh for changes to take affect
diff --git a/src/app/services/notifications/snackbar.service.ts b/src/app/services/notifications/snackbar.service.ts
index e504f802..5c343495 100644
--- a/src/app/services/notifications/snackbar.service.ts
+++ b/src/app/services/notifications/snackbar.service.ts
@@ -52,7 +52,13 @@ export class SnackbarService {
// If user chose not to view snackbar notifications, then don't enqueue them and instead log to console
if(!this.appConfig.canViewSnackbarNotification()) {
- options?.type === 'error' ? console.error('[Error] ' + message) : console.log(`[Notice] ` + message);
+ if(options?.type === 'error'){
+ console.error('[Error] ' + message)
+ alert('[Error] ' + message)
+ }
+ else
+ console.log(`[Notice] ` + message)
+
return;
}