Vanguard media stack: ingest, copyparty, Syncthing, normalize sweep, Authentik, Jellyfin + Vanguard Metadata plugin, MariaDB, Gitea, bot runtime
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
using Jellyfin.Plugin.Vanguard.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Plugin.Vanguard.Services;
|
||||
|
||||
/// <summary>Folder name (streamer / submitter) -> Discord member, via the config map or Authentik.</summary>
|
||||
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<IdentityResolver> _log;
|
||||
|
||||
public IdentityResolver(DiscordClient discord, AuthentikClient authentik, EventStore store, ILogger<IdentityResolver> log)
|
||||
{ _discord = discord; _authentik = authentik; _store = store; _log = log; }
|
||||
|
||||
private static PluginConfiguration Cfg => Plugin.Instance?.Configuration ?? new PluginConfiguration();
|
||||
|
||||
public async Task<DiscordMember?> 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<string?> 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user