using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.RegularExpressions; using API.MangaDownloadClients; using API.Schema.MangaContext; using log4net; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Processing; namespace API.MangaConnectors; [PrimaryKey("Name")] public abstract class MangaConnector(string name, string[] supportedLanguages, string[] baseUris, string iconUrl) { [NotMapped] internal DownloadClient downloadClient { get; init; } = null!; [NotMapped] protected ILog Log { get; init; } = LogManager.GetLogger(name); [StringLength(32)] public string Name { get; init; } = name; [StringLength(8)] public string[] SupportedLanguages { get; init; } = supportedLanguages; [StringLength(2048)] public string IconUrl { get; init; } = iconUrl; [StringLength(256)] public string[] BaseUris { get; init; } = baseUris; public bool Enabled { get; internal set; } = true; public abstract (Manga, MangaConnectorId)[] SearchManga(string mangaSearchName); public abstract (Manga, MangaConnectorId)? GetMangaFromUrl(string url); public abstract (Manga, MangaConnectorId)? GetMangaFromId(string mangaIdOnSite); public abstract (Chapter, MangaConnectorId)[] GetChapters(MangaConnectorId mangaId, string? language = null); internal abstract string[] GetChapterImageUrls(MangaConnectorId chapterId); public bool UrlMatchesConnector(string url) => BaseUris.Any(baseUri => Regex.IsMatch(url, "https?://" + baseUri + "/.*")); internal string? SaveCoverImageToCache(MangaConnectorId mangaId, int retries = 3) { if(retries < 0) return null; Regex urlRex = new (@"https?:\/\/((?:[a-zA-Z0-9-]+\.)+[a-zA-Z0-9]+)\/(?:.+\/)*(.+\.([a-zA-Z]+))"); //https?:\/\/[a-zA-Z0-9-]+\.([a-zA-Z0-9-]+\.[a-zA-Z0-9]+)\/(?:.+\/)*(.+\.([a-zA-Z]+)) for only second level domains Match match = urlRex.Match(mangaId.Obj.CoverUrl); string filename = $"{match.Groups[1].Value}-{mangaId.ObjId}.{mangaId.MangaConnectorName}.{match.Groups[3].Value}"; string saveImagePath = Path.Join(TrangaSettings.coverImageCacheOriginal, filename); if (File.Exists(saveImagePath)) return saveImagePath; RequestResult coverResult = downloadClient.MakeRequest(mangaId.Obj.CoverUrl, RequestType.MangaCover, $"https://{match.Groups[1].Value}"); if ((int)coverResult.statusCode < 200 || (int)coverResult.statusCode >= 300) return SaveCoverImageToCache(mangaId, --retries); try { using MemoryStream ms = new(); coverResult.result.CopyTo(ms); byte[] imageBytes = ms.ToArray(); Directory.CreateDirectory(TrangaSettings.coverImageCacheOriginal); File.WriteAllBytes(saveImagePath, imageBytes); using Image image = Image.Load(imageBytes); Directory.CreateDirectory(TrangaSettings.coverImageCacheLarge); using Image large = image.Clone(x => x.Resize(new ResizeOptions { Size = Constants.ImageLgSize, Mode = ResizeMode.Max })); large.SaveAsJpeg(Path.Join(TrangaSettings.coverImageCacheLarge, filename), new (){ Quality = 40 }); Directory.CreateDirectory(TrangaSettings.coverImageCacheMedium); using Image medium = image.Clone(x => x.Resize(new ResizeOptions { Size = Constants.ImageMdSize, Mode = ResizeMode.Max })); medium.SaveAsJpeg(Path.Join(TrangaSettings.coverImageCacheMedium, filename), new (){ Quality = 40 }); Directory.CreateDirectory(TrangaSettings.coverImageCacheSmall); using Image small = image.Clone(x => x.Resize(new ResizeOptions { Size = Constants.ImageSmSize, Mode = ResizeMode.Max })); small.SaveAsJpeg(Path.Join(TrangaSettings.coverImageCacheSmall, filename), new (){ Quality = 40 }); } catch (Exception e) { Log.Error(e); } return filename; } }