Skip to content

Commit 8d053f0

Browse files
Fesaa333fredmajora2007
authored
Hardcover Recommendations and OAuth (Hotfix) (#4890)
Co-authored-by: Fred Silberberg <fred@silberberg.xyz> Co-authored-by: Joe Milazzo <josephmajora@gmail.com>
1 parent 289bc39 commit 8d053f0

349 files changed

Lines changed: 7984 additions & 8153 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build-ui.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
- run: |
2222
cd UI/Web || exit
2323
echo 'Installing web dependencies'
24-
npm ci
24+
npm ci --legacy-peer-deps
2525
2626
echo 'Building UI'
2727
npm run prod

Kavita.API/Services/Plus/IExternalMetadataService.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,13 @@ public interface IExternalMetadataService
1919
/// <summary>
2020
/// Retrieves Metadata about a Recommended External Series
2121
/// </summary>
22-
/// <param name="aniListId"></param>
23-
/// <param name="malId"></param>
24-
/// <param name="mangaBakaId"></param>
2522
/// <param name="seriesId"></param>
23+
/// <param name="request"></param>
24+
/// /// <param name="recommendedSeriesId"></param>
2625
/// <param name="ct"></param>
2726
/// <returns></returns>
2827
/// <exception cref="KavitaException"></exception>
29-
Task<ExternalSeriesDetailDto?> GetExternalSeriesDetail(int? aniListId, long? malId, int? mangaBakaId, int? seriesId, CancellationToken ct = default);
28+
Task<ExternalSeriesDetailDto?> GetExternalSeriesDetail(int seriesId, MetadataRequest request, int? recommendedSeriesId, CancellationToken ct = default);
3029

3130
/// <summary>
3231
/// This is a task that runs on a schedule and slowly fetches data from Kavita+ to keep

Kavita.API/Services/Plus/IOAuthService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace Kavita.API.Services.Plus;
77

88
public interface IOAuthService
99
{
10-
Task HandleCallback(AppUser user, OAuthUpstream upstream, string token, string? refreshToken = null);
10+
Task HandleCallback(AppUser user, OAuthUpstream upstream, string token, string? refreshToken = null, int? expiresIn = null);
1111

1212
Task RefreshTokens(CancellationToken ct = default);
1313
}

Kavita.Common.Tests/Helpers/ExternalIdParserTests.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ public void CanParseWeblink_ComicVine(string link, string expectedId)
2929

3030
[Theory]
3131
[InlineData("https://mangabaka.org/3391", 3391)]
32+
[InlineData("https://mangabaka.org/3391/Some-Slug/", 3391)]
33+
[InlineData("https://mangabaka.org/3391?q=Something+Something", 3391)]
34+
[InlineData("https://mangabaka.org/novel/84752/Spice-Wolf", 84752)]
35+
[InlineData("https://mangabaka.org/manga/10803/Spice-Wolf", 10803)]
3236
public void CanParseWeblink_MangaBaka(string link, long expectedId)
3337
{
3438
Assert.Equal(ExternalIdParser.GetMangaBakaId(link), expectedId);

Kavita.Common/Helpers/ExternalIdParser.cs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Globalization;
44
using System.Linq;
5+
using System.Text.RegularExpressions;
56

67
namespace Kavita.Common.Helpers;
78
#nullable enable
@@ -14,11 +15,10 @@ public sealed record HardcoverUrlSlug(string Slug, bool IsStandAlone);
1415
/// <summary>
1516
/// Handles all things parsing of External Ids (weblinks, not set checks, anilist:X)
1617
/// </summary>
17-
public static class ExternalIdParser
18+
public static partial class ExternalIdParser
1819
{
1920
private const string AniListWeblinkWebsite = "https://anilist.co/manga/";
2021
private const string MalWeblinkWebsite = "https://myanimelist.net/manga/";
21-
private const string GoogleBooksWeblinkWebsite = "https://books.google.com/books?id=";
2222
private const string MangaDexWeblinkWebsite = "https://mangadex.org/title/";
2323
private const string AniListStaffWebsite = "https://anilist.co/staff/";
2424
private const string AniListCharacterWebsite = "https://anilist.co/character/";
@@ -53,7 +53,6 @@ public static class ExternalIdParser
5353
{
5454
{AniListWeblinkWebsite, 0},
5555
{MalWeblinkWebsite, 0},
56-
{GoogleBooksWeblinkWebsite, 0},
5756
{MangaDexWeblinkWebsite, 0},
5857
{AniListStaffWebsite, 0},
5958
{AniListCharacterWebsite, 0},
@@ -97,19 +96,14 @@ public static int GetAniListStaffId(string? url)
9796
return ExtractId<int?>(url, AniListStaffWebsite) ?? 0;
9897
}
9998

100-
public static string? GetGoogleBooksId(string? weblinks)
101-
{
102-
return ExtractId<string?>(weblinks, GoogleBooksWeblinkWebsite);
103-
}
104-
10599
public static string? GetMangaDexId(string? weblinks)
106100
{
107101
return ExtractId<string?>(weblinks, MangaDexWeblinkWebsite);
108102
}
109103

110104
public static int GetMangaBakaId(string? weblinks)
111105
{
112-
return ExtractId<int?>(weblinks, MangaBakaWebsite) ?? 0;
106+
return ExtractId<int?>(weblinks, MangaBakaWebsite) ?? ExtractId<int?>(weblinks, MangaBakaWebsite, 1) ?? 0;
113107
}
114108

115109
#region Header-based Parsing
@@ -200,19 +194,26 @@ public static string GetHardcoverStaffId(string? url)
200194
/// </summary>
201195
/// <param name="webLinks"></param>
202196
/// <param name="website"></param>
197+
/// <param name="indexOverride"></param>
203198
/// <returns></returns>
204-
private static T? ExtractId<T>(string? webLinks, string website)
199+
private static T? ExtractId<T>(string? webLinks, string website, int? indexOverride = null)
205200
{
206201
if (string.IsNullOrEmpty(webLinks)) return default;
207202

208-
var index = WeblinkExtractionMap[website];
203+
var index = indexOverride ?? WeblinkExtractionMap[website];
209204
foreach (var webLink in webLinks.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
210205
{
211206
if (!webLink.StartsWith(website)) continue;
212207

213208
var tokens = webLink.Split(website)[1].Split('/');
214209
var value = tokens[index];
215210

211+
// Clean any query params
212+
if (QueryParamsRegex().IsMatch(value))
213+
{
214+
value = value.Split('?')[0];
215+
}
216+
216217
if (typeof(T) == typeof(int?))
217218
{
218219
if (int.TryParse(value, CultureInfo.InvariantCulture, out var intValue)) return (T)(object)intValue;
@@ -264,4 +265,7 @@ public static string GetHardcoverStaffId(string? url)
264265

265266
throw new ArgumentException("Unsupported ID type. Supported types are int, long, and string.", nameof(id));
266267
}
268+
269+
[GeneratedRegex(".*?\\D+=.*")]
270+
private static partial Regex QueryParamsRegex();
267271
}

Kavita.Database/Extensions/RestrictByAgeExtensions.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,19 @@ public static IQueryable<ReadingList> RestrictAgainstAgeRestriction(this IQuerya
164164
return q;
165165
}
166166

167+
public static IQueryable<ReadingListItem> RestrictAgainstAgeRestriction(this IQueryable<ReadingListItem> queryable, AgeRestriction restriction)
168+
{
169+
if (restriction.AgeRating == AgeRating.NotApplicable) return queryable;
170+
var q = queryable.Where(rl => rl.Series.Metadata.AgeRating <= restriction.AgeRating);
171+
172+
if (!restriction.IncludeUnknowns)
173+
{
174+
return q.Where(rl => rl.Series.Metadata.AgeRating != AgeRating.Unknown);
175+
}
176+
177+
return q;
178+
}
179+
167180
public static IQueryable<AppUserRating> RestrictAgainstAgeRestriction(this IQueryable<AppUserRating> queryable, AgeRestriction restriction, int userId)
168181
{
169182
if (restriction.AgeRating == AgeRating.NotApplicable) return queryable;

Kavita.Database/Repositories/KavitaPlusAuditRepository.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Threading.Tasks;
77
using Kavita.API.Repositories;
88
using Kavita.API.Services.Plus;
9+
using Kavita.Common.Extensions;
910
using Kavita.Common.Helpers;
1011
using Kavita.Database.Extensions;
1112
using Kavita.Models.DTOs.KavitaPlus;
@@ -185,6 +186,8 @@ public async Task<KavitaPlusAuditSeriesInfoDto> GetSeriesInfoAsync(
185186

186187
private IQueryable<KavitaPlusAuditLog> BuildBaseQuery(KavitaPlusAuditFilterDto filter)
187188
{
189+
var normalizedSeriesName = filter.Search?.ToNormalized() ?? string.Empty;
190+
188191
return context.KavitaPlusAuditLogs
189192
.AsNoTracking()
190193
.WhereIf(filter.Category.HasValue, e => e.Category == filter.Category!.Value)
@@ -200,9 +203,9 @@ private IQueryable<KavitaPlusAuditLog> BuildBaseQuery(KavitaPlusAuditFilterDto f
200203
.WhereIf(filter.FromUtc.HasValue, e => e.CreatedUtc >= filter.FromUtc!.Value)
201204
.WhereIf(filter.ToUtc.HasValue, e => e.CreatedUtc <= filter.ToUtc!.Value)
202205
.WhereIf(!string.IsNullOrEmpty(filter.Search), e =>
203-
context.Series.Any(s => s.Id == e.SeriesId && s.Name.Contains(filter.Search!)) ||
204-
(e.User != null && e.User.UserName!.Contains(filter.Search!)) ||
205-
(e.ErrorMessage != null && e.ErrorMessage.Contains(filter.Search!)))
206+
context.Series.Any(s => s.Id == e.SeriesId && (s.NormalizedName.Contains(normalizedSeriesName) || s.NormalizedLocalizedName.Contains(normalizedSeriesName))) ||
207+
(e.User != null && e.User.UserName != null && EF.Functions.Like(e.User.UserName, $"%{filter.Search}%")) ||
208+
(e.ErrorMessage != null && EF.Functions.Like(e.ErrorMessage, $"%{filter.Search}%")))
206209
.OrderByDescending(e => e.CreatedUtc);
207210
}
208211

Kavita.Database/Repositories/ReadingListRepository.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,10 +466,12 @@ public async Task<IList<ReadingListItemDto>> GetReadingListItemDtosByIdAsync(int
466466
UserParams? userParams = null, CancellationToken ct = default)
467467
{
468468
var userLibraries = context.Library.GetUserLibraries(userId);
469+
var ageRating = await context.AppUser.GetUserAgeRestriction(userId, ct);
469470

470471
var query = context.ReadingListItem
471472
.Where(rli => rli.ReadingListId == readingListId)
472473
.Where(rli => userLibraries.Contains(rli.Series.LibraryId))
474+
.RestrictAgainstAgeRestriction(ageRating)
473475
.OrderBy(rli => rli.Order)
474476
.ProjectToWithProgress<ReadingListItem, ReadingListItemDto>(mapper, userId)
475477
.AsSplitQuery();

Kavita.Models/DTOs/KavitaPlus/OAuth/OAuthUpstream.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,5 @@ public enum OAuthUpstream
66
MangaBaka = 1,
77
AniList = 2,
88
MyAnimeList = 3,
9-
/// <summary>
10-
/// Hardcover has not yet implemented OAuth
11-
/// </summary>
12-
// Hardcover = 4,
9+
Hardcover = 4,
1310
}

Kavita.Models/DTOs/KavitaPlus/Recommendation/ExternalSeriesDto.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public sealed record ExternalSeriesDto
1616
public int? AniListId { get; set; }
1717
public int? MangaBakaId { get; set; }
1818
public long? MalId { get; set; }
19+
public int? HardcoverId { get; set; }
1920
[EnumDataType(typeof(ScrobbleProvider))]
2021
public ScrobbleProvider Provider { get; set; } = ScrobbleProvider.AniList;
2122
/// <summary>
@@ -35,4 +36,4 @@ public sealed record ExternalSeriesDto
3536
/// </summary>
3637
[EnumDataType(typeof(AgeRating))]
3738
public AgeRating AgeRating { get; set; } = AgeRating.Unknown;
38-
}
39+
}

0 commit comments

Comments
 (0)