mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-08-11 09:13:42 -04:00
More Polish (#2005)
* Implemented sort title extraction from epub 3 files. * Added link to wiki for media errors * Fixed the hack to reduce JWT refresh token expiration * Fixed up a case where favicon downloading wasn't correcting links that started with // correctly. Added a fallback for sites that just don't pngs available. * Implemented a mechanism to fallback to Kavita's website for favicons which can be dynamically added/updated by the community. * Reworked the logic for bookwalker which will fail to get the base html, so we have to rely on the fallback handler.
This commit is contained in:
parent
e34fcc10cf
commit
3c887f5377
@ -144,4 +144,14 @@ public static class Seed
|
|||||||
}
|
}
|
||||||
await context.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// /// <summary>
|
||||||
|
// /// Responsible to copy (not overwrite) a set of favicons that Kavita can't parse from websites.
|
||||||
|
// /// </summary>
|
||||||
|
// /// <param name="directoryService"></param>
|
||||||
|
// /// <returns></returns>
|
||||||
|
// public static Task SeedFavicons(IDirectoryService directoryService)
|
||||||
|
// {
|
||||||
|
//
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
@ -472,7 +472,10 @@ public class BookService : IBookService
|
|||||||
break;
|
break;
|
||||||
case "calibre:series":
|
case "calibre:series":
|
||||||
info.Series = metadataItem.Content;
|
info.Series = metadataItem.Content;
|
||||||
info.SeriesSort = metadataItem.Content;
|
if (string.IsNullOrEmpty(info.SeriesSort))
|
||||||
|
{
|
||||||
|
info.SeriesSort = metadataItem.Content;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case "calibre:series_index":
|
case "calibre:series_index":
|
||||||
info.Volume = metadataItem.Content;
|
info.Volume = metadataItem.Content;
|
||||||
@ -488,7 +491,10 @@ public class BookService : IBookService
|
|||||||
break;
|
break;
|
||||||
case "belongs-to-collection":
|
case "belongs-to-collection":
|
||||||
info.Series = metadataItem.Content;
|
info.Series = metadataItem.Content;
|
||||||
info.SeriesSort = metadataItem.Content;
|
if (string.IsNullOrEmpty(info.SeriesSort))
|
||||||
|
{
|
||||||
|
info.SeriesSort = metadataItem.Content;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case "collection-type":
|
case "collection-type":
|
||||||
// These look to be genres from https://manual.calibre-ebook.com/sub_groups.html or can be "series"
|
// These look to be genres from https://manual.calibre-ebook.com/sub_groups.html or can be "series"
|
||||||
@ -504,32 +510,24 @@ public class BookService : IBookService
|
|||||||
PopulatePerson(metadataItem, info, person);
|
PopulatePerson(metadataItem, info, person);
|
||||||
break;
|
break;
|
||||||
case "title-type":
|
case "title-type":
|
||||||
if (!metadataItem.Content.Equals("collection")) break;
|
if (metadataItem.Content.Equals("collection"))
|
||||||
var titleId = metadataItem.Refines?.Replace("#", string.Empty);
|
{
|
||||||
var readingListElem = epubBook.Schema.Package.Metadata.Titles
|
ExtractCollectionOrReadingList(metadataItem, epubBook, info);
|
||||||
.FirstOrDefault(item => item.Id == titleId);
|
}
|
||||||
if (readingListElem == null) break;
|
|
||||||
|
|
||||||
var count = epubBook.Schema.Package.Metadata.MetaItems
|
if (metadataItem.Content.Equals("main"))
|
||||||
.FirstOrDefault(item =>
|
|
||||||
item.Property == "display-seq" && item.Refines == metadataItem.Refines);
|
|
||||||
if (count == null || count.Content == "0")
|
|
||||||
{
|
{
|
||||||
// TODO: Rewrite this to use a StringBuilder
|
ExtractSortTitle(metadataItem, epubBook, info);
|
||||||
// Treat this as a Collection
|
|
||||||
info.SeriesGroup += (string.IsNullOrEmpty(info.StoryArc) ? string.Empty : ",") + readingListElem.Title.Replace(",", "_");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Treat as a reading list
|
|
||||||
info.AlternateSeries += (string.IsNullOrEmpty(info.AlternateSeries) ? string.Empty : ",") + readingListElem.Title.Replace(",", "_");
|
|
||||||
info.AlternateNumber += (string.IsNullOrEmpty(info.AlternateNumber) ? string.Empty : ",") + count.Content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if there is a SortTitle
|
||||||
|
|
||||||
|
|
||||||
// Include regular Writer as well, for cases where there is no special tag
|
// Include regular Writer as well, for cases where there is no special tag
|
||||||
info.Writer = string.Join(",",
|
info.Writer = string.Join(",",
|
||||||
epubBook.Schema.Package.Metadata.Creators.Select(c => Parser.CleanAuthor(c.Creator)));
|
epubBook.Schema.Package.Metadata.Creators.Select(c => Parser.CleanAuthor(c.Creator)));
|
||||||
@ -556,6 +554,46 @@ public class BookService : IBookService
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ExtractSortTitle(EpubMetadataMeta metadataItem, EpubBookRef epubBook, ComicInfo info)
|
||||||
|
{
|
||||||
|
var titleId = metadataItem.Refines?.Replace("#", string.Empty);
|
||||||
|
var titleElem = epubBook.Schema.Package.Metadata.Titles
|
||||||
|
.FirstOrDefault(item => item.Id == titleId);
|
||||||
|
if (titleElem == null) return;
|
||||||
|
|
||||||
|
var sortTitleElem = epubBook.Schema.Package.Metadata.MetaItems
|
||||||
|
.FirstOrDefault(item =>
|
||||||
|
item.Property == "file-as" && item.Refines == metadataItem.Refines);
|
||||||
|
if (sortTitleElem == null || string.IsNullOrWhiteSpace(sortTitleElem.Content)) return;
|
||||||
|
info.SeriesSort = sortTitleElem.Content;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ExtractCollectionOrReadingList(EpubMetadataMeta metadataItem, EpubBookRef epubBook, ComicInfo info)
|
||||||
|
{
|
||||||
|
var titleId = metadataItem.Refines?.Replace("#", string.Empty);
|
||||||
|
var readingListElem = epubBook.Schema.Package.Metadata.Titles
|
||||||
|
.FirstOrDefault(item => item.Id == titleId);
|
||||||
|
if (readingListElem == null) return;
|
||||||
|
|
||||||
|
var count = epubBook.Schema.Package.Metadata.MetaItems
|
||||||
|
.FirstOrDefault(item =>
|
||||||
|
item.Property == "display-seq" && item.Refines == metadataItem.Refines);
|
||||||
|
if (count == null || count.Content == "0")
|
||||||
|
{
|
||||||
|
// TODO: Rewrite this to use a StringBuilder
|
||||||
|
// Treat this as a Collection
|
||||||
|
info.SeriesGroup += (string.IsNullOrEmpty(info.StoryArc) ? string.Empty : ",") +
|
||||||
|
readingListElem.Title.Replace(",", "_");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Treat as a reading list
|
||||||
|
info.AlternateSeries += (string.IsNullOrEmpty(info.AlternateSeries) ? string.Empty : ",") +
|
||||||
|
readingListElem.Title.Replace(",", "_");
|
||||||
|
info.AlternateNumber += (string.IsNullOrEmpty(info.AlternateNumber) ? string.Empty : ",") + count.Content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void PopulatePerson(EpubMetadataMeta metadataItem, ComicInfo info, EpubMetadataCreator person)
|
private static void PopulatePerson(EpubMetadataMeta metadataItem, ComicInfo info, EpubMetadataCreator person)
|
||||||
{
|
{
|
||||||
switch (metadataItem.Content)
|
switch (metadataItem.Content)
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -227,6 +226,8 @@ public class ImageService : IImageService
|
|||||||
url = value;
|
url = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var correctSizeLink = string.Empty;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var htmlContent = url.GetStringAsync().Result;
|
var htmlContent = url.GetStringAsync().Result;
|
||||||
@ -238,11 +239,16 @@ public class ImageService : IImageService
|
|||||||
.Where(href => href.Split("?")[0].EndsWith(".png", StringComparison.InvariantCultureIgnoreCase))
|
.Where(href => href.Split("?")[0].EndsWith(".png", StringComparison.InvariantCultureIgnoreCase))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
if (pngLinks == null)
|
correctSizeLink = (pngLinks?.FirstOrDefault(pngLink => pngLink.Contains("32")) ?? pngLinks?.FirstOrDefault());
|
||||||
{
|
}
|
||||||
throw new KavitaException($"Could not grab favicon from {baseUrl}");
|
catch (Exception ex)
|
||||||
}
|
{
|
||||||
var correctSizeLink = pngLinks.FirstOrDefault(pngLink => pngLink.Contains("32")) ?? pngLinks.FirstOrDefault();
|
_logger.LogError(ex, "Error downloading favicon.png for {Domain}, will try fallback methods", domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
correctSizeLink = FallbackToKavitaReaderFavicon(baseUrl);
|
||||||
if (string.IsNullOrEmpty(correctSizeLink))
|
if (string.IsNullOrEmpty(correctSizeLink))
|
||||||
{
|
{
|
||||||
throw new KavitaException($"Could not grab favicon from {baseUrl}");
|
throw new KavitaException($"Could not grab favicon from {baseUrl}");
|
||||||
@ -253,7 +259,7 @@ public class ImageService : IImageService
|
|||||||
// If starts with //, it's coming usually from an offsite cdn
|
// If starts with //, it's coming usually from an offsite cdn
|
||||||
if (correctSizeLink.StartsWith("//"))
|
if (correctSizeLink.StartsWith("//"))
|
||||||
{
|
{
|
||||||
finalUrl = Url.Combine("https:", correctSizeLink);
|
finalUrl = "https:" + correctSizeLink;
|
||||||
}
|
}
|
||||||
else if (!correctSizeLink.StartsWith(uri.Scheme))
|
else if (!correctSizeLink.StartsWith(uri.Scheme))
|
||||||
{
|
{
|
||||||
@ -287,14 +293,37 @@ public class ImageService : IImageService
|
|||||||
|
|
||||||
_logger.LogDebug("Favicon.png for {Domain} downloaded and saved successfully", domain);
|
_logger.LogDebug("Favicon.png for {Domain} downloaded and saved successfully", domain);
|
||||||
return filename;
|
return filename;
|
||||||
}
|
}catch (Exception ex)
|
||||||
catch (Exception ex)
|
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error downloading favicon.png for {Domain}", domain);
|
_logger.LogError(ex, "Error downloading favicon.png for {Domain}", domain);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string FallbackToKavitaReaderFavicon(string baseUrl)
|
||||||
|
{
|
||||||
|
var correctSizeLink = string.Empty;
|
||||||
|
var allOverrides = "https://kavitareader.com/assets/favicons/urls.txt".GetStringAsync().Result;
|
||||||
|
if (!string.IsNullOrEmpty(allOverrides))
|
||||||
|
{
|
||||||
|
var cleanedBaseUrl = baseUrl.Replace("https://", string.Empty);
|
||||||
|
var externalFile = allOverrides
|
||||||
|
.Split("\n")
|
||||||
|
.FirstOrDefault(url =>
|
||||||
|
cleanedBaseUrl.Equals(url.Replace(".png", string.Empty)) ||
|
||||||
|
cleanedBaseUrl.Replace("www.", string.Empty).Equals(url.Replace(".png", string.Empty)
|
||||||
|
));
|
||||||
|
if (string.IsNullOrEmpty(externalFile))
|
||||||
|
{
|
||||||
|
throw new KavitaException($"Could not grab favicon from {baseUrl}");
|
||||||
|
}
|
||||||
|
|
||||||
|
correctSizeLink = "https://kavitareader.com/assets/favicons/" + externalFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
return correctSizeLink;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string CreateThumbnailFromBase64(string encodedImage, string fileName, EncodeFormat encodeFormat, int thumbnailWidth = ThumbnailWidth)
|
public string CreateThumbnailFromBase64(string encodedImage, string fileName, EncodeFormat encodeFormat, int thumbnailWidth = ThumbnailWidth)
|
||||||
{
|
{
|
||||||
|
@ -97,7 +97,7 @@ public class TokenService : ITokenService
|
|||||||
}
|
}
|
||||||
|
|
||||||
var validated = await _userManager.VerifyUserTokenAsync(user, TokenOptions.DefaultProvider, RefreshTokenName, request.RefreshToken);
|
var validated = await _userManager.VerifyUserTokenAsync(user, TokenOptions.DefaultProvider, RefreshTokenName, request.RefreshToken);
|
||||||
if (!validated && tokenContent.ValidTo > DateTime.UtcNow.Add(TimeSpan.FromHours(1)))
|
if (!validated && tokenContent.ValidTo <= DateTime.UtcNow.Add(TimeSpan.FromHours(1)))
|
||||||
{
|
{
|
||||||
_logger.LogDebug("[RefreshToken] failed to validate due to invalid refresh token");
|
_logger.LogDebug("[RefreshToken] failed to validate due to invalid refresh token");
|
||||||
return null;
|
return null;
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<p>This table contains issues found during scan or reading of your media. This list is non-managed. You can clear it at any time and use Library (Force) Scan to perform analysis.</p>
|
<p>This table contains issues found during scan or reading of your media. This list is non-managed.
|
||||||
|
You can clear it at any time and use Library (Force) Scan to perform analysis. A list of some common errors and what
|
||||||
|
they mean can be found on the <a rel="noopener noreferrer" target="_blank" href="https://wiki.kavitareader.com/en/guides/managing-your-files/scanner#media-errors">wiki</a>.</p>
|
||||||
|
|
||||||
<form [formGroup]="formGroup">
|
<form [formGroup]="formGroup">
|
||||||
<div class="row g-0 mb-3">
|
<div class="row g-0 mb-3">
|
||||||
@ -48,4 +50,4 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"name": "GPL-3.0",
|
"name": "GPL-3.0",
|
||||||
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
||||||
},
|
},
|
||||||
"version": "0.7.2.12"
|
"version": "0.7.2.13"
|
||||||
},
|
},
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user