using Microsoft.Extensions.Caching.Memory; using NetCord; using NetCord.Gateway; using NetCord.Hosting.Gateway; using NetCord.Rest; using SquadBot.Providers; namespace SquadBot.StateMangers; public class VoiceStateManager { // Key: GuildId Value: List of UserId for users in voice channel // TODO: We probably want to persist this somewhere else (Redis/Valkey) private Dictionary> _guildVoiceStates; private IMemoryCache _yapperNotificationTimeoutCache; private RestClient _restClient; private ILogger _logger; public VoiceStateManager(ILogger logger, RestClient restClient, IMemoryCache yapperNotificationTimeoutCache) { _guildVoiceStates = new Dictionary>(); _yapperNotificationTimeoutCache = yapperNotificationTimeoutCache; _restClient = restClient ?? throw new System.ArgumentNullException(nameof(restClient)); _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); } public async Task HandleVoiceStateChange(VoiceState voice_state) { if (voice_state.ChannelId is not null) { // Joining channel _logger.LogInformation($"New yapper detected: {voice_state.User.Username}"); if (!_guildVoiceStates.TryGetValue(voice_state.GuildId, out var yappers) || yappers.Count == 0) { yappers ??= new HashSet(); var channels = await _restClient.GetGuildChannelsAsync(voice_state.GuildId); var alertChannelId = channels.FirstOrDefault(channel => channel.Name == "bot-tinkering")?.Id; var guildRoles = await _restClient.GetGuildRolesAsync(voice_state.GuildId, new RestRequestProperties() { AuditLogReason = "Role lookup" }); var yapperRoleId = guildRoles.FirstOrDefault(role => role.Name.ToLower() == "yapper")?.Id; if (alertChannelId is null || yapperRoleId is null) { _logger.LogError($"Found incorrect state, alert channel or yapper role does not exist."); return; } if (!CheckIfTimeoutExpired(voice_state.GuildId)) return; // Notify that new yapper has arrived await SendYapperNotification(yapperRoleId.GetValueOrDefault(), alertChannelId.GetValueOrDefault()); } yappers.Add(voice_state.UserId); _guildVoiceStates[voice_state.GuildId] = yappers; } else { // Leaving channel _logger.LogInformation($"Yapper leaving: {voice_state.User.Username}"); if (_guildVoiceStates.TryGetValue(voice_state.GuildId, out var yappers)) { yappers.Remove(voice_state.UserId); _guildVoiceStates[voice_state.GuildId] = yappers; if (yappers.Count == 0) SetGuildTimeout(voice_state.GuildId); } } return; } private void SetGuildTimeout(ulong guild_id) { #if DEBUG var expiration = DateTimeOffset.UtcNow.AddMinutes(1); #else var expiration = DateTimeOffset.UtcNow.AddHours(2); #endif _yapperNotificationTimeoutCache.Set(guild_id, expiration, expiration); } private bool CheckIfTimeoutExpired(ulong guild_id) { var timeOutCached = _yapperNotificationTimeoutCache.TryGetValue(guild_id, out DateTimeOffset timeout); if (timeOutCached == false) return true; if (timeout < DateTimeOffset.UtcNow) return true; return false; } private async Task SendYapperNotification(ulong yapperRoleId, ulong alertChannelId) { await _restClient.SendMessageAsync( alertChannelId, new MessageProperties() { AllowedMentions = new AllowedMentionsProperties() {AllowedRoles = [yapperRoleId]}, Content = $"<@&{yapperRoleId}> " + PhraseProvider.GetYapperPhrase() }); } }