using Jellyfin.Plugin.Vanguard.Configuration; using Microsoft.Extensions.Logging; namespace Jellyfin.Plugin.Vanguard.Services; /// Folder name (streamer / submitter) -> Discord member, via the config map or Authentik. public sealed class IdentityResolver { private static readonly TimeSpan MemberTtl = TimeSpan.FromHours(6); private static readonly TimeSpan IdentityTtl = TimeSpan.FromHours(24); private readonly DiscordClient _discord; private readonly AuthentikClient _authentik; private readonly EventStore _store; private readonly ILogger _log; public IdentityResolver(DiscordClient discord, AuthentikClient authentik, EventStore store, ILogger log) { _discord = discord; _authentik = authentik; _store = store; _log = log; } private static PluginConfiguration Cfg => Plugin.Instance?.Configuration ?? new PluginConfiguration(); public async Task ResolveFolderAsync(string folderName, CancellationToken ct) { var cfg = Cfg; if (string.IsNullOrWhiteSpace(cfg.DiscordBotToken) || string.IsNullOrWhiteSpace(cfg.DiscordGuildId)) return null; if (!_store.TryGetCachedIdentity(folderName, IdentityTtl, out var discordId)) { discordId = await LookupDiscordIdAsync(cfg, folderName, ct).ConfigureAwait(false); _store.CacheIdentity(folderName, discordId); } if (discordId is null) return null; var cached = _store.GetCachedMember(discordId, MemberTtl); if (cached is not null) return cached; var m = await _discord.GetGuildMemberAsync(cfg.DiscordBotToken, cfg.DiscordGuildId, discordId, ct).ConfigureAwait(false); if (m is not null) _store.CacheMember(m); return m; } private async Task LookupDiscordIdAsync(PluginConfiguration cfg, string folder, CancellationToken ct) { string authentikUser = folder; foreach (var raw in (cfg.FolderIdentityMap ?? string.Empty).Split('\n')) { var line = raw.Trim(); var eq = line.IndexOf('='); if (eq <= 0 || !line[..eq].Trim().Equals(folder, StringComparison.OrdinalIgnoreCase)) continue; var val = line[(eq + 1)..].Trim(); if (val.StartsWith("authentik:", StringComparison.OrdinalIgnoreCase)) { authentikUser = val["authentik:".Length..].Trim(); break; } if (val.All(char.IsDigit) && val.Length >= 15) return val; // a Discord snowflake } var id = await _authentik.GetDiscordIdForUsernameAsync(cfg.AuthentikUrl, cfg.AuthentikApiToken, authentikUser, ct).ConfigureAwait(false); if (id is null) _log.LogInformation("No Discord identity for folder {Folder} (authentik user {User})", folder, authentikUser); return id; } }