diff --git a/API/Controllers/SeriesController.cs b/API/Controllers/SeriesController.cs index dadfc74b7..c7134c34f 100644 --- a/API/Controllers/SeriesController.cs +++ b/API/Controllers/SeriesController.cs @@ -648,13 +648,13 @@ public class SeriesController : BaseApiController /// /// This will perform the fix match /// - /// + /// /// /// [HttpPost("update-match")] - public ActionResult UpdateSeriesMatch([FromQuery] int seriesId, [FromQuery] int aniListId, [FromQuery] long? malId) + public ActionResult UpdateSeriesMatch([FromQuery] int seriesId, [FromQuery] int? aniListId, [FromQuery] long? malId, [FromQuery] int? cbrId) { - BackgroundJob.Enqueue(() => _externalMetadataService.FixSeriesMatch(seriesId, aniListId, malId)); + BackgroundJob.Enqueue(() => _externalMetadataService.FixSeriesMatch(seriesId, aniListId, malId, cbrId)); return Ok(); } diff --git a/API/DTOs/KavitaPlus/ExternalMetadata/SeriesDetailPlusApiDto.cs b/API/DTOs/KavitaPlus/ExternalMetadata/SeriesDetailPlusApiDto.cs index f67fb1f4c..26411bce7 100644 --- a/API/DTOs/KavitaPlus/ExternalMetadata/SeriesDetailPlusApiDto.cs +++ b/API/DTOs/KavitaPlus/ExternalMetadata/SeriesDetailPlusApiDto.cs @@ -13,4 +13,5 @@ internal class SeriesDetailPlusApiDto public ExternalSeriesDetailDto? Series { get; set; } public int? AniListId { get; set; } public long? MalId { get; set; } + public int? CbrId { get; set; } } diff --git a/API/DTOs/KavitaPlus/Metadata/ExternalSeriesDetailDto.cs b/API/DTOs/KavitaPlus/Metadata/ExternalSeriesDetailDto.cs index a5a037cc3..73adead93 100644 --- a/API/DTOs/KavitaPlus/Metadata/ExternalSeriesDetailDto.cs +++ b/API/DTOs/KavitaPlus/Metadata/ExternalSeriesDetailDto.cs @@ -15,6 +15,7 @@ public class ExternalSeriesDetailDto public string Name { get; set; } public int? AniListId { get; set; } public long? MALId { get; set; } + public int? CbrId { get; set; } public IList Synonyms { get; set; } = []; public PlusMediaFormat PlusMediaFormat { get; set; } public string? SiteUrl { get; set; } diff --git a/API/DTOs/Scrobbling/PlusSeriesDto.cs b/API/DTOs/Scrobbling/PlusSeriesDto.cs index 75e443d2e..50a3c5784 100644 --- a/API/DTOs/Scrobbling/PlusSeriesDto.cs +++ b/API/DTOs/Scrobbling/PlusSeriesDto.cs @@ -9,6 +9,10 @@ public record PlusSeriesRequestDto public long? MalId { get; set; } public string? GoogleBooksId { get; set; } public string? MangaDexId { get; set; } + /// + /// ComicBookRoundup Id + /// + public int? CbrId { get; set; } public string SeriesName { get; set; } public string? AltSeriesName { get; set; } public PlusMediaFormat MediaFormat { get; set; } diff --git a/API/Services/Plus/ExternalMetadataService.cs b/API/Services/Plus/ExternalMetadataService.cs index a73701cf8..3c2a9cee5 100644 --- a/API/Services/Plus/ExternalMetadataService.cs +++ b/API/Services/Plus/ExternalMetadataService.cs @@ -49,7 +49,7 @@ public interface IExternalMetadataService Task> GetStacksForUser(int userId); Task> MatchSeries(MatchSeriesDto dto); - Task FixSeriesMatch(int seriesId, int anilistId, long? malId); + Task FixSeriesMatch(int seriesId, int? aniListId, long? malId, int? cbrId); Task UpdateSeriesDontMatch(int seriesId, bool dontMatch); Task WriteExternalMetadataToSeries(ExternalSeriesDetailDto externalMetadata, int seriesId); } @@ -196,7 +196,7 @@ public class ExternalMetadataService : IExternalMetadataService { var license = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey)).Value; var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(dto.SeriesId, - SeriesIncludes.Metadata | SeriesIncludes.ExternalMetadata); + SeriesIncludes.Metadata | SeriesIncludes.ExternalMetadata | SeriesIncludes.Library); if (series == null) return []; var potentialAnilistId = ScrobblingService.ExtractId(dto.Query, ScrobblingService.AniListWeblinkWebsite); @@ -210,7 +210,7 @@ public class ExternalMetadataService : IExternalMetadataService var matchRequest = new MatchSeriesRequestDto() { - Format = series.Format == MangaFormat.Epub ? PlusMediaFormat.LightNovel : PlusMediaFormat.Manga, + Format = series.Library.Type.ConvertToPlusMediaFormat(series.Format), Query = dto.Query, SeriesName = series.Name, AlternativeNames = altNames.Where(s => !string.IsNullOrEmpty(s)).ToList(), @@ -312,8 +312,10 @@ public class ExternalMetadataService : IExternalMetadataService /// This will override any sort of matching that was done prior and force it to be what the user Selected /// /// - /// - public async Task FixSeriesMatch(int seriesId, int anilistId, long? malId) + /// + /// + /// + public async Task FixSeriesMatch(int seriesId, int? aniListId, long? malId, int? cbrId) { var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId, SeriesIncludes.Library); if (series == null) return; @@ -329,15 +331,17 @@ public class ExternalMetadataService : IExternalMetadataService var metadata = await FetchExternalMetadataForSeries(seriesId, series.Library.Type, new PlusSeriesRequestDto() { - AniListId = anilistId, + AniListId = aniListId, MalId = malId, + CbrId = cbrId, + MediaFormat = series.Library.Type.ConvertToPlusMediaFormat(series.Format), SeriesName = series.Name // Required field, not used since AniList/Mal Id are passed }); if (metadata.Series == null) { - _logger.LogError("Unable to Match {SeriesName} with Kavita+ Series AniList Id: {AniListId}", - series.Name, anilistId); + _logger.LogError("Unable to Match {SeriesName} with Kavita+ Series with Id: {AniListId}/{MalId}/{CbrId}", + series.Name, aniListId, malId, cbrId); return; } @@ -421,8 +425,7 @@ public class ExternalMetadataService : IExternalMetadataService result = await (Configuration.KavitaPlusApiUrl + "/api/metadata/v2/series-detail") .WithKavitaPlusHeaders(license, token) .PostJsonAsync(data) - .ReceiveJson< - SeriesDetailPlusApiDto>(); // This returns an AniListSeries and Match returns ExternalSeriesDto + .ReceiveJson(); // This returns an AniListSeries and Match returns ExternalSeriesDto } catch (FlurlHttpException ex) { @@ -438,8 +441,7 @@ public class ExternalMetadataService : IExternalMetadataService result = await (Configuration.KavitaPlusApiUrl + "/api/metadata/v2/series-detail") .WithKavitaPlusHeaders(license, token) .PostJsonAsync(data) - .ReceiveJson< - SeriesDetailPlusApiDto>(); + .ReceiveJson(); } } diff --git a/UI/Web/src/app/_models/series-detail/external-series-detail.ts b/UI/Web/src/app/_models/series-detail/external-series-detail.ts index aa62d3960..db25782ca 100644 --- a/UI/Web/src/app/_models/series-detail/external-series-detail.ts +++ b/UI/Web/src/app/_models/series-detail/external-series-detail.ts @@ -27,8 +27,9 @@ export interface MetadataTagDto { export interface ExternalSeriesDetail { name: string; - aniListId?: number; - malId?: number; + aniListId?: number | null; + malId?: number | null; + cbrId?: number | null; synonyms: Array; plusMediaFormat: PlusMediaFormat; siteUrl?: string; diff --git a/UI/Web/src/app/_services/series.service.ts b/UI/Web/src/app/_services/series.service.ts index f8644748b..f221b2f1a 100644 --- a/UI/Web/src/app/_services/series.service.ts +++ b/UI/Web/src/app/_services/series.service.ts @@ -242,7 +242,7 @@ export class SeriesService { } updateMatch(seriesId: number, series: ExternalSeriesDetail) { - return this.httpClient.post(this.baseUrl + `series/update-match?seriesId=${seriesId}&aniListId=${series.aniListId}${series.malId ? '&malId=' + series.malId : ''}`, {}, TextResonse); + return this.httpClient.post(this.baseUrl + `series/update-match?seriesId=${seriesId}&aniListId=${series.aniListId || 0}&malId=${series.malId || 0}&cbrId=${series.cbrId || 0}`, {}, TextResonse); } updateDontMatch(seriesId: number, dontMatch: boolean) { diff --git a/UI/Web/src/app/_single-module/match-series-modal/match-series-modal.component.ts b/UI/Web/src/app/_single-module/match-series-modal/match-series-modal.component.ts index be670684e..012fc5c14 100644 --- a/UI/Web/src/app/_single-module/match-series-modal/match-series-modal.component.ts +++ b/UI/Web/src/app/_single-module/match-series-modal/match-series-modal.component.ts @@ -92,7 +92,7 @@ export class MatchSeriesModalComponent implements OnInit { data.tags = data.tags || []; data.genres = data.genres || []; - this.seriesService.updateMatch(this.series.id, data).subscribe(_ => { + this.seriesService.updateMatch(this.series.id, item.series).subscribe(_ => { this.save(); }); } diff --git a/UI/Web/src/app/sidenav/_modals/library-settings-modal/library-settings-modal.component.ts b/UI/Web/src/app/sidenav/_modals/library-settings-modal/library-settings-modal.component.ts index c1b6871b7..1d8355dab 100644 --- a/UI/Web/src/app/sidenav/_modals/library-settings-modal/library-settings-modal.component.ts +++ b/UI/Web/src/app/sidenav/_modals/library-settings-modal/library-settings-modal.component.ts @@ -1,12 +1,4 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - DestroyRef, - inject, - Input, - OnInit -} from '@angular/core'; +import {ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, inject, Input, OnInit} from '@angular/core'; import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms'; import { NgbActiveModal, @@ -133,6 +125,11 @@ export class LibrarySettingsModalComponent implements OnInit { return libType === LibraryType.Manga || libType === LibraryType.LightNovel; } + get IsMetadataDownloadEligible() { + const libType = parseInt(this.libraryForm.get('type')?.value + '', 10) as LibraryType; + return libType === LibraryType.Manga || libType === LibraryType.LightNovel || libType === LibraryType.ComicVine; + } + ngOnInit(): void { this.settingService.getLibraryTypes().subscribe((types) => { this.libraryTypes = types; @@ -151,11 +148,19 @@ export class LibrarySettingsModalComponent implements OnInit { if (this.library && !(this.library.type === LibraryType.Manga || this.library.type === LibraryType.LightNovel) ) { this.libraryForm.get('allowScrobbling')?.setValue(false); - this.libraryForm.get('allowMetadataMatching')?.setValue(false); this.libraryForm.get('allowScrobbling')?.disable(); - this.libraryForm.get('allowMetadataMatching')?.disable(); + + if (this.IsMetadataDownloadEligible) { + this.libraryForm.get('allowMetadataMatching')?.setValue(this.library.allowMetadataMatching); + this.libraryForm.get('allowMetadataMatching')?.enable(); + } else { + this.libraryForm.get('allowMetadataMatching')?.setValue(false); + this.libraryForm.get('allowMetadataMatching')?.disable(); + } } + + this.libraryForm.get('name')?.valueChanges.pipe( debounceTime(100), distinctUntilChanged(), @@ -218,11 +223,16 @@ export class LibrarySettingsModalComponent implements OnInit { if (!this.IsKavitaPlusEligible) { this.libraryForm.get('allowScrobbling')?.disable(); - this.libraryForm.get('allowMetadataMatching')?.disable(); } else { this.libraryForm.get('allowScrobbling')?.enable(); - this.libraryForm.get('allowMetadataMatching')?.enable(); } + + if (this.IsMetadataDownloadEligible) { + this.libraryForm.get('allowMetadataMatching')?.enable(); + } else { + this.libraryForm.get('allowMetadataMatching')?.disable(); + } + this.cdRef.markForCheck(); }), takeUntilDestroyed(this.destroyRef) @@ -241,7 +251,7 @@ export class LibrarySettingsModalComponent implements OnInit { this.libraryForm.get('manageReadingLists')?.setValue(this.library.manageReadingLists); this.libraryForm.get('collapseSeriesRelationships')?.setValue(this.library.collapseSeriesRelationships); this.libraryForm.get('allowScrobbling')?.setValue(this.IsKavitaPlusEligible ? this.library.allowScrobbling : false); - this.libraryForm.get('allowMetadataMatching')?.setValue(this.IsKavitaPlusEligible ? this.library.allowMetadataMatching : false); + this.libraryForm.get('allowMetadataMatching')?.setValue(this.IsMetadataDownloadEligible ? this.library.allowMetadataMatching : false); this.selectedFolders = this.library.folders; this.madeChanges = false;