using System.Text.Json; using System.Text.RegularExpressions; using Backend.Contracts; using Backend.Domain; using System.Net; namespace Backend.Common; public static class SeasonMappings { private static readonly Regex HtmlBreakRegex = new(@"<\s*br\s*/?>", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex HtmlListItemOpenRegex = new(@"<\s*li\b[^>]*>", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex HtmlBlockCloseRegex = new(@"", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex HtmlTagRegex = new(@"<[^>]*>", RegexOptions.Compiled); private static readonly Regex MultiNewlineRegex = new(@"\n{3,}", RegexOptions.Compiled); public static bool IsSeasonScheduleValid( DateOnly nominationStartsAt, DateOnly nominationEndsAt, DateOnly votingStartsAt, DateOnly votingEndsAt, DateOnly reviewStartsAt, DateOnly reviewEndsAt, DateOnly showDate) { return nominationStartsAt <= nominationEndsAt && nominationEndsAt <= votingStartsAt && votingStartsAt <= votingEndsAt && votingEndsAt <= reviewStartsAt && reviewStartsAt <= reviewEndsAt && reviewEndsAt <= showDate; } public static string BuildProfileUrl(string platform, string channelSlug) { var normalizedPlatform = platform.Trim().ToLowerInvariant(); var platformKey = new string(normalizedPlatform.Where(char.IsLetterOrDigit).ToArray()); var slug = channelSlug.Trim(); var cleanSlug = slug.TrimStart('@'); if (slug.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || slug.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) { return slug; } if (string.IsNullOrWhiteSpace(cleanSlug)) { return "#"; } if (string.IsNullOrWhiteSpace(platformKey)) { return cleanSlug.Contains('.') ? $"https://{cleanSlug}" : "#"; } return platformKey switch { "artstation" => $"https://www.artstation.com/{cleanSlug}", "bilibili" => $"https://space.bilibili.com/{cleanSlug}", "bluesky" => $"https://bsky.app/profile/{cleanSlug}", "booth" => $"https://{cleanSlug}.booth.pm", "cake" => $"https://cake.gg/@{cleanSlug}", "discord" => cleanSlug.Contains("discord.", StringComparison.OrdinalIgnoreCase) ? $"https://{cleanSlug}" : $"https://discord.gg/{cleanSlug}", "deviantart" => $"https://www.deviantart.com/{cleanSlug}", "facebook" => $"https://facebook.com/{cleanSlug}", "fanbox" => $"https://{cleanSlug}.fanbox.cc", "github" => $"https://github.com/{cleanSlug}", "instagram" => $"https://instagram.com/{cleanSlug}", "kick" => $"https://kick.com/{cleanSlug}", "kofi" or "ko-fi" => $"https://ko-fi.com/{cleanSlug}", "linktree" => $"https://linktr.ee/{cleanSlug}", "mastodon" => cleanSlug.Contains('@') ? $"https://{cleanSlug.Split('@').Last()}/@{cleanSlug.Split('@').First()}" : $"https://mastodon.social/@{cleanSlug}", "patreon" => $"https://patreon.com/{cleanSlug}", "picarto" or "picartotv" => $"https://picarto.tv/{cleanSlug}", "pinterest" => $"https://pinterest.com/{cleanSlug}", "pixiv" => $"https://www.pixiv.net/users/{cleanSlug}", "reddit" => $"https://reddit.com/user/{cleanSlug}", "skeb" => $"https://skeb.jp/@{cleanSlug}", "soundcloud" => $"https://soundcloud.com/{cleanSlug}", "spotify" => $"https://open.spotify.com/user/{cleanSlug}", "telegram" => $"https://t.me/{cleanSlug}", "threads" => $"https://threads.net/@{cleanSlug}", "tiktok" => $"https://tiktok.com/@{cleanSlug}", "trovo" => $"https://trovo.live/s/{cleanSlug}", "twitch" => $"https://twitch.tv/{cleanSlug}", "tumblr" => $"https://{cleanSlug}.tumblr.com", "vimeo" => $"https://vimeo.com/{cleanSlug}", "website" or "link" => cleanSlug.Contains('.') ? $"https://{cleanSlug}" : $"https://{cleanSlug}.com", "youtube" => cleanSlug.StartsWith("@", StringComparison.Ordinal) ? $"https://youtube.com/{cleanSlug}" : $"https://youtube.com/@{cleanSlug}", "x" or "twitter" => $"https://x.com/{cleanSlug}", _ => $"https://{platformKey}.com/{cleanSlug}", }; } public static string NormalizeSeasonStreamUrl(string? value) { var trimmed = value?.Trim() ?? string.Empty; return string.IsNullOrWhiteSpace(trimmed) ? "https://twitch.tv/jayuhime" : trimmed; } public static string NormalizePlainTextContent(string? value) { var trimmed = value?.Trim() ?? string.Empty; if (string.IsNullOrWhiteSpace(trimmed)) { return string.Empty; } var withBreakHints = HtmlBreakRegex.Replace(trimmed, "\n"); withBreakHints = HtmlListItemOpenRegex.Replace(withBreakHints, "- "); withBreakHints = HtmlBlockCloseRegex.Replace(withBreakHints, "\n"); withBreakHints = HtmlTagRegex.Replace(withBreakHints, " "); withBreakHints = WebUtility.HtmlDecode(withBreakHints).Replace("\r\n", "\n").Replace('\r', '\n'); var normalizedLines = withBreakHints .Split('\n') .Select(line => line.Trim()) .ToArray(); return MultiNewlineRegex.Replace(string.Join('\n', normalizedLines), "\n\n").Trim(); } public static string NormalizePhaseKey(string? currentPhase) { var value = currentPhase?.Trim().ToLowerInvariant() ?? string.Empty; if (value.Contains("abgeschlossen") || value.Contains("archiv") || value.Contains("complete") || value.Contains("ended")) { return "completed"; } if (value.Contains("show")) { return "show"; } if (value.Contains("aufbereit") || value.Contains("vorbereit") || value.Contains("pause") || value.Contains("review") || value.Contains("auswert")) { return "preparation"; } if (value.Contains("vot")) { return "voting"; } if (value.Contains("nomin")) { return "nomination"; } return "nomination"; } public static string ResolveTimelineState(string itemKey, string currentPhaseKey) { string[] phaseOrder = ["nomination", "voting", "preparation", "show"]; if (string.Equals(currentPhaseKey, "completed", StringComparison.OrdinalIgnoreCase)) { return phaseOrder.Contains(itemKey) ? "done" : "upcoming"; } var itemIndex = Array.IndexOf(phaseOrder, itemKey); var currentIndex = Array.IndexOf(phaseOrder, currentPhaseKey); if (itemIndex < 0) { return "upcoming"; } if (currentIndex < 0) { currentIndex = 0; } if (itemIndex < currentIndex) { return "done"; } if (itemIndex == currentIndex) { return "active"; } return "upcoming"; } public static T[] DeserializeSiteArray(string? json) { if (string.IsNullOrWhiteSpace(json)) { return []; } try { return JsonSerializer.Deserialize( json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? []; } catch { return []; } } public static PublicSocialLinkDto[] ReadSocialLinks(SiteSettings settings) => DeserializeSiteArray(settings.SocialLinksJson); public static FaqItemDto[] ReadFaqItems(SiteSettings settings) => DeserializeSiteArray(settings.FaqJson); public static FooterLinkDto[] BuildFooterLinks(SiteSettings settings) => [ new FooterLinkDto("imprint", "Impressum", settings.ImprintUrl, settings.ImprintContent), new FooterLinkDto("contact", "Kontakt", settings.ContactUrl, settings.ContactContent), new FooterLinkDto("sponsors", "Sponsoren & Partner", string.Empty, settings.SponsorsContent), new FooterLinkDto("showacts", "Showacts", settings.ShowactsUrl, settings.ShowactsContent), ]; }