From a32e1631de25af0362a0f28528fa59389fe9c7bb Mon Sep 17 00:00:00 2001 From: Paul Friederichsen Date: Thu, 13 Oct 2022 23:37:50 -0500 Subject: [PATCH 1/3] Start moving saucenao into a dialog --- src/app/app.module.ts | 4 +- .../file-info-sheet.component.html | 4 ++ .../file-info-sheet.component.ts | 24 +++++++ .../saucenao-dialog.component.html | 35 ++++++++++ .../saucenao-dialog.component.scss | 18 +++++ .../saucenao-dialog.component.spec.ts | 23 +++++++ .../saucenao-dialog.component.ts | 68 +++++++++++++++++++ src/app/saucenao.service.ts | 27 ++++++-- src/app/send/send.component.ts | 17 ++++- 9 files changed, 210 insertions(+), 10 deletions(-) create mode 100644 src/app/saucenao-dialog/saucenao-dialog.component.html create mode 100644 src/app/saucenao-dialog/saucenao-dialog.component.scss create mode 100644 src/app/saucenao-dialog/saucenao-dialog.component.spec.ts create mode 100644 src/app/saucenao-dialog/saucenao-dialog.component.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index cb4285b..bf00d94 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -68,6 +68,7 @@ import { TagNamespaceClassPipe } from './utils/tag-utils'; import { SystemPredicateDialogComponent } from './system-predicate-dialog/system-predicate-dialog.component'; import { MatNativeDateModule } from '@angular/material/core'; import { SortInputComponent } from './sort-input/sort-input.component'; +import { SaucenaoDialogComponent } from './saucenao-dialog/saucenao-dialog.component'; const MAT_MODULES = [ @@ -119,7 +120,8 @@ const MAT_MODULES = [ TagInputDialogComponent, TagNamespaceClassPipe, SystemPredicateDialogComponent, - SortInputComponent + SortInputComponent, + SaucenaoDialogComponent ], imports: [ BrowserModule, diff --git a/src/app/file-info-sheet/file-info-sheet.component.html b/src/app/file-info-sheet/file-info-sheet.component.html index b46cee3..efd3dae 100644 --- a/src/app/file-info-sheet/file-info-sheet.component.html +++ b/src/app/file-info-sheet/file-info-sheet.component.html @@ -26,6 +26,10 @@

File Info

link Copy hyshare URL + + + + + + + + error + No results found + + + + + + + diff --git a/src/app/saucenao-dialog/saucenao-dialog.component.scss b/src/app/saucenao-dialog/saucenao-dialog.component.scss new file mode 100644 index 0000000..6a6bb52 --- /dev/null +++ b/src/app/saucenao-dialog/saucenao-dialog.component.scss @@ -0,0 +1,18 @@ +.saucenao-thumbnail { + border-radius: 4px !important; +} + +.metadata-line span:not(:last-of-type) { + margin-right: 20px; +} + +.saucenao-card, .url-info-card { + margin-top: 16px; +} + +.saucenao-results { + margin-top: 16px; +} +.saucenao-spinner { + margin: 32px auto 0; +} diff --git a/src/app/saucenao-dialog/saucenao-dialog.component.spec.ts b/src/app/saucenao-dialog/saucenao-dialog.component.spec.ts new file mode 100644 index 0000000..a6de48a --- /dev/null +++ b/src/app/saucenao-dialog/saucenao-dialog.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SaucenaoDialogComponent } from './saucenao-dialog.component'; + +describe('SaucenaoDialogComponent', () => { + let component: SaucenaoDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ SaucenaoDialogComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(SaucenaoDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/saucenao-dialog/saucenao-dialog.component.ts b/src/app/saucenao-dialog/saucenao-dialog.component.ts new file mode 100644 index 0000000..ec432ec --- /dev/null +++ b/src/app/saucenao-dialog/saucenao-dialog.component.ts @@ -0,0 +1,68 @@ +import { Component, Inject, OnInit } from '@angular/core'; +import { MatDialog, MatDialogConfig, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { catchError, Observable } from 'rxjs'; +import { AddUrlOptions, HydrusAddService } from '../hydrus-add.service'; +import { SaucenaoService, SaucenaoResults, SaucenaoUrlorFile } from '../saucenao.service'; + +interface SaucenaoDialogData { + urlOrFile: SaucenaoUrlorFile; + addUrlOptions?: AddUrlOptions; +}; + +@Component({ + selector: 'app-saucenao-dialog', + templateUrl: './saucenao-dialog.component.html', + styleUrls: ['./saucenao-dialog.component.scss'] +}) +export class SaucenaoDialogComponent implements OnInit { + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: SaucenaoDialogData, + public saucenaoService: SaucenaoService, + private addService: HydrusAddService, + private snackbar: MatSnackBar + ) { } + + onNoClick(): void { + this.dialogRef.close(); + } + + ngOnInit(): void { + } + + saucenaoResults$: Observable = this.saucenaoService.search(this.data.urlOrFile).pipe( + catchError((err, caught) => { + this.snackbar.open('Error: ' + err.message, undefined, { + duration: 5000 + }); + throw err; + }) + ) + + send(url: string) { + this.addService.addUrl(url, this.data.addUrlOptions).subscribe(res => { + this.snackbar.open(res.human_result_text, undefined, { + duration: 5000 + }); + }, error => { + console.log(error); + this.snackbar.open(`Error: ${error.message}`, undefined, { + duration: 10000 + }); + }); + } + + static open(dialog: MatDialog, data?: SaucenaoDialogData, config?: MatDialogConfig) { + return dialog.open( + SaucenaoDialogComponent, + { + width: '80vw', + data, + ...config + } + ); + } + +} diff --git a/src/app/saucenao.service.ts b/src/app/saucenao.service.ts index 363b01b..a071d2c 100644 --- a/src/app/saucenao.service.ts +++ b/src/app/saucenao.service.ts @@ -31,6 +31,8 @@ interface SacuenaoOptions { db?: number; } +export type SaucenaoUrlorFile = {url: string} | {file: Blob}; + export interface SaucenaoResults extends SagiriResult { urls: { site: string; @@ -51,7 +53,7 @@ export class SaucenaoService { constructor(private http: HttpClient, private settings: SettingsService) { } - buildForm(data: SacuenaoOptions & ({url: string} | {file: Blob})): FormData { + buildForm(data: SacuenaoOptions & SaucenaoUrlorFile): FormData { const form = new FormData(); for(const [key, value] of Object.entries(data)) { form.set(key, value); @@ -63,14 +65,25 @@ export class SaucenaoService { return !!this.settings.appSettings.saucenaoApiKey && !!this.settings.appSettings.saucenaoSearchProxy; } - public searchResponse(url: string, options?: SacuenaoOptions): Observable { + public validSaucenaoMime(mime: string) { + return ([ + 'image/jpeg', + 'image/jpg', + 'image/png', + 'image/gif', + 'image/bmp', + 'image/webp' + ].includes(mime)); + } + + public searchResponse(urlOrFile: SaucenaoUrlorFile, options?: SacuenaoOptions): Observable { return this.http.post(this.settings.appSettings.saucenaoSearchProxy, this.buildForm( { ...defaultSaucenaoOptions, ...options, api_key: this.settings.appSettings.saucenaoApiKey, - url + ...urlOrFile })).pipe( catchError(err => { if(!err.error.header) { @@ -106,16 +119,16 @@ export class SaucenaoService { ); } - public filteredSearchResponse(url: string, options?: SacuenaoOptions, minSimilarity: number = 70): Observable { - return this.searchResponse(url, options).pipe( + public filteredSearchResponse(urlOrFile: SaucenaoUrlorFile, options?: SacuenaoOptions, minSimilarity: number = 70): Observable { + return this.searchResponse(urlOrFile, options).pipe( map(resp => resp.results.filter(({ header: { index_id: id, similarity}}) => !!sites[id] && similarity >= minSimilarity) .sort((a, b) => b.header.similarity - a.header.similarity)) ); } // Adapted from https://github.com/ClarityCafe/Sagiri - public search(url: string, options?: SacuenaoOptions): Observable { - return this.filteredSearchResponse(url, options).pipe( + public search(urlOrFile: SaucenaoUrlorFile, options?: SacuenaoOptions): Observable { + return this.filteredSearchResponse(urlOrFile, options).pipe( map(results => results.map(result => { const { url, name, id, authorName, authorUrl }: { url: string, name: string, id: Indices, authorName: string | null, authorUrl: string | null } = resolveResult(result); const { diff --git a/src/app/send/send.component.ts b/src/app/send/send.component.ts index 743b8be..e4623f9 100644 --- a/src/app/send/send.component.ts +++ b/src/app/send/send.component.ts @@ -7,6 +7,8 @@ import { HydrusURLInfo, HydrusURLFiles } from '../hydrus-url'; import { ActivatedRoute, Router } from '@angular/router'; import { MatSnackBar } from '@angular/material/snack-bar'; import { SaucenaoService, SaucenaoResults } from '../saucenao.service'; +import { MatDialog } from '@angular/material/dialog'; +import { SaucenaoDialogComponent } from '../saucenao-dialog/saucenao-dialog.component'; @Component({ selector: 'app-send', @@ -25,6 +27,7 @@ export class SendComponent implements OnInit { private route: ActivatedRoute, private snackbar: MatSnackBar, private router: Router, + public dialog: MatDialog, public saucenaoService: SaucenaoService ) { } @@ -106,7 +109,7 @@ export class SendComponent implements OnInit { } saucenaoLookup() { - this.saucenaoLoading = true; + /* this.saucenaoLoading = true; this.saucenaoResults = null; const lookupUrl = this.sendForm.value.sendUrl; this.saucenaoService.search(lookupUrl).subscribe( @@ -120,7 +123,17 @@ export class SendComponent implements OnInit { duration: 5000 }); console.log(err); - }); + }); */ + const addUrlOptions: AddUrlOptions = {}; + if (this.sendForm.value.destPageName !== '') { + addUrlOptions.destination_page_name = this.sendForm.value.destPageName; + } + SaucenaoDialogComponent.open(this.dialog, { + urlOrFile: { + url: this.sendForm.value.sendUrl + }, + addUrlOptions + }) } } From 8b8be8f0caa547759a4ec1f133ac859a09a7ed6d Mon Sep 17 00:00:00 2001 From: Paul Friederichsen Date: Tue, 25 Oct 2022 12:14:27 -0500 Subject: [PATCH 2/3] Fix issues in saucenao service --- src/app/saucenao.service.ts | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/app/saucenao.service.ts b/src/app/saucenao.service.ts index a071d2c..3d5041f 100644 --- a/src/app/saucenao.service.ts +++ b/src/app/saucenao.service.ts @@ -22,12 +22,12 @@ import { SettingsService } from './settings.service'; } */ interface SacuenaoOptions { - numres?: number; output_type?: 0 | 2; api_key?: string; - testmode?: 1 | null; - dbmask?: number; - dbmaski?: number; +} + +interface SaucenaoQuery { + numres?: number; db?: number; } @@ -40,10 +40,9 @@ export interface SaucenaoResults extends SagiriResult { }[]; } -const defaultSaucenaoOptions: SacuenaoOptions = { +const defaultSaucenaoQuery: SaucenaoQuery = { db: 999, - output_type: 2, - numres: 5, + //numres: 5, }; @Injectable({ @@ -76,15 +75,20 @@ export class SaucenaoService { ].includes(mime)); } - public searchResponse(urlOrFile: SaucenaoUrlorFile, options?: SacuenaoOptions): Observable { + public searchResponse(urlOrFile: SaucenaoUrlorFile, queryOptions?: SaucenaoQuery): Observable { return this.http.post(this.settings.appSettings.saucenaoSearchProxy, this.buildForm( { - ...defaultSaucenaoOptions, - ...options, + output_type: 2, api_key: this.settings.appSettings.saucenaoApiKey, ...urlOrFile - })).pipe( + }), + { + params: { + ...defaultSaucenaoQuery, + ...queryOptions, + } + }).pipe( catchError(err => { if(!err.error.header) { throw err; @@ -119,16 +123,16 @@ export class SaucenaoService { ); } - public filteredSearchResponse(urlOrFile: SaucenaoUrlorFile, options?: SacuenaoOptions, minSimilarity: number = 70): Observable { - return this.searchResponse(urlOrFile, options).pipe( + public filteredSearchResponse(urlOrFile: SaucenaoUrlorFile, queryOptions?: SaucenaoQuery, minSimilarity: number = 70): Observable { + return this.searchResponse(urlOrFile, queryOptions).pipe( map(resp => resp.results.filter(({ header: { index_id: id, similarity}}) => !!sites[id] && similarity >= minSimilarity) .sort((a, b) => b.header.similarity - a.header.similarity)) ); } // Adapted from https://github.com/ClarityCafe/Sagiri - public search(urlOrFile: SaucenaoUrlorFile, options?: SacuenaoOptions): Observable { - return this.filteredSearchResponse(urlOrFile, options).pipe( + public search(urlOrFile: SaucenaoUrlorFile, queryOptions?: SaucenaoQuery): Observable { + return this.filteredSearchResponse(urlOrFile, queryOptions).pipe( map(results => results.map(result => { const { url, name, id, authorName, authorUrl }: { url: string, name: string, id: Indices, authorName: string | null, authorUrl: string | null } = resolveResult(result); const { From fc91934085c947347687d2f7a8c24a4e0bc2056f Mon Sep 17 00:00:00 2001 From: Paul Friederichsen Date: Tue, 25 Oct 2022 12:46:24 -0500 Subject: [PATCH 3/3] tweak dialog, revert send to not use dialog for now --- src/app/saucenao-dialog/saucenao-dialog.component.html | 8 ++++---- src/app/saucenao-dialog/saucenao-dialog.component.ts | 5 +++-- src/app/send/send.component.html | 2 +- src/app/send/send.component.ts | 10 +++++----- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/app/saucenao-dialog/saucenao-dialog.component.html b/src/app/saucenao-dialog/saucenao-dialog.component.html index e26a14d..31af8b9 100644 --- a/src/app/saucenao-dialog/saucenao-dialog.component.html +++ b/src/app/saucenao-dialog/saucenao-dialog.component.html @@ -1,13 +1,13 @@

SauceNAO Lookup

- +
{{result.site}}{{result.authorName ? ' - ' + result.authorName : ''}} {{result.similarity}}% - +
{{url.site}}
@@ -20,8 +20,8 @@

SauceNAO Lookup

-
- + +
error diff --git a/src/app/saucenao-dialog/saucenao-dialog.component.ts b/src/app/saucenao-dialog/saucenao-dialog.component.ts index ec432ec..056c435 100644 --- a/src/app/saucenao-dialog/saucenao-dialog.component.ts +++ b/src/app/saucenao-dialog/saucenao-dialog.component.ts @@ -1,7 +1,7 @@ import { Component, Inject, OnInit } from '@angular/core'; import { MatDialog, MatDialogConfig, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { MatSnackBar } from '@angular/material/snack-bar'; -import { catchError, Observable } from 'rxjs'; +import { catchError, Observable, tap } from 'rxjs'; import { AddUrlOptions, HydrusAddService } from '../hydrus-add.service'; import { SaucenaoService, SaucenaoResults, SaucenaoUrlorFile } from '../saucenao.service'; @@ -33,6 +33,7 @@ export class SaucenaoDialogComponent implements OnInit { } saucenaoResults$: Observable = this.saucenaoService.search(this.data.urlOrFile).pipe( + tap(console.log), catchError((err, caught) => { this.snackbar.open('Error: ' + err.message, undefined, { duration: 5000 @@ -58,7 +59,7 @@ export class SaucenaoDialogComponent implements OnInit { return dialog.open( SaucenaoDialogComponent, { - width: '80vw', + maxWidth: '95vw', data, ...config } diff --git a/src/app/send/send.component.html b/src/app/send/send.component.html index f8044f1..7dc07a2 100644 --- a/src/app/send/send.component.html +++ b/src/app/send/send.component.html @@ -21,7 +21,7 @@

Send to Hydrus

formControlName="destPageName" placeholder="" >
- +
diff --git a/src/app/send/send.component.ts b/src/app/send/send.component.ts index e4623f9..23189f2 100644 --- a/src/app/send/send.component.ts +++ b/src/app/send/send.component.ts @@ -109,10 +109,10 @@ export class SendComponent implements OnInit { } saucenaoLookup() { - /* this.saucenaoLoading = true; + this.saucenaoLoading = true; this.saucenaoResults = null; const lookupUrl = this.sendForm.value.sendUrl; - this.saucenaoService.search(lookupUrl).subscribe( + this.saucenaoService.search({url: lookupUrl}).subscribe( results => { this.saucenaoLoading = false; this.saucenaoResults = results; @@ -123,8 +123,8 @@ export class SendComponent implements OnInit { duration: 5000 }); console.log(err); - }); */ - const addUrlOptions: AddUrlOptions = {}; + }); + /* const addUrlOptions: AddUrlOptions = {}; if (this.sendForm.value.destPageName !== '') { addUrlOptions.destination_page_name = this.sendForm.value.destPageName; } @@ -133,7 +133,7 @@ export class SendComponent implements OnInit { url: this.sendForm.value.sendUrl }, addUrlOptions - }) + }) */ } }