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; }