using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; using System.Net.Http; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using LanMountainDesktop.RecommendationBackend.Models; namespace LanMountainDesktop.RecommendationBackend.Services; public sealed record RecommendationApiOptions { public string DailyQuoteUrl { get; init; } = "https://v1.hitokoto.cn/?encode=json&charset=utf-8"; public string DailyPoetryUrl { get; init; } = "https://v1.jinrishici.com/all.json"; public string DoubanHotMovieUrlTemplate { get; init; } = "https://movie.douban.com/j/search_subjects?type=movie&tag=%E7%83%AD%E9%97%A8&page_limit={0}&page_start=0"; public string BaiduHotSearchUrl { get; init; } = "https://top.baidu.com/board?tab=realtime"; public string ArtInstituteArtworkApiTemplate { get; init; } = "https://api.artic.edu/api/v1/artworks?page={0}&limit={1}&fields=id,title,artist_title,artist_display,date_display,image_id,api_link"; public string ArtInstituteImageUrlTemplate { get; init; } = "https://www.artic.edu/iiif/2/{0}/full/843,/0/default.jpg"; public TimeSpan CacheDuration { get; init; } = TimeSpan.FromMinutes(15); public TimeSpan RequestTimeout { get; init; } = TimeSpan.FromSeconds(8); public int DefaultMovieCandidateCount { get; init; } = 20; public int DefaultHotSearchLimit { get; init; } = 10; public int DefaultArtworkCandidateCount { get; init; } = 50; } public sealed class RecommendationDataService : IRecommendationDataService, IDisposable { private sealed record CacheEntry(object Value, DateTimeOffset ExpireAt); private sealed record MovieCandidate( string Title, string? Rating, string? Url, string? CoverUrl); private sealed record ArtworkCandidate( string Title, string? Artist, string? Year, string? ArtworkUrl, string? ImageId); private static readonly Regex HtmlTagRegex = new("<[^>]+>", RegexOptions.Compiled); private static readonly Regex HotSearchSplitRegex = new("