using Discord; using Discord.WebSocket; using NLog; using NLog.Fluent; using PetaPoco; using Torch.API; using Torch.Managers; namespace LightPerms.Discord; public class DiscordManager : Manager { private static readonly ILogger Log = LogManager.GetCurrentClassLogger(); private readonly Config _config; [Dependency] private readonly IPermissionsManager _permissionsManager = null!; private readonly DiscordSocketClient _client = new(new() { GatewayIntents = GatewayIntents.Guilds | GatewayIntents.GuildMessages | GatewayIntents.GuildMembers }); private IDatabase _db = null!; public Dictionary WaitingUsers { get; } = new(); public DiscordManager(ITorchBase torchInstance, Config config) : base(torchInstance) { _config = config; _client.SlashCommandExecuted += ClientOnSlashCommandExecutedAsync; _client.GuildMemberUpdated += ClientOnGuildMemberUpdatedAsync; _client.Ready += ClientOnReady; _client.Log += message => { NLog.Fluent.Log.Level(message.Severity switch { LogSeverity.Critical => LogLevel.Fatal, LogSeverity.Error => LogLevel.Error, LogSeverity.Warning => LogLevel.Warn, LogSeverity.Info => LogLevel.Info, LogSeverity.Debug => LogLevel.Debug, LogSeverity.Verbose => LogLevel.Trace, _ => throw new ArgumentOutOfRangeException() }).LoggerName(message.Source).Exception(message.Exception).Message(message.Message).Write(); return Task.CompletedTask; }; } private async Task ClientOnReady() { var guild = _client.GetGuild(_config.GuildId); if (_config.RoleConfigs.Any(b => guild.Roles.All(c => c.Id != b.RoleId))) throw new InvalidOperationException("Some roles are not exists in the guild"); await AddSlashCommandsAsync(guild); await guild.DownloadUsersAsync(); await Task.WhenAll(guild.Users.Select(b => ClientOnGuildMemberUpdatedAsync(default, b))); } public override void Attach() { base.Attach(); _db = _permissionsManager.Db; _db.Execute(LinkedUser.CreateTableSql); Task.Run(AttachAsync); } private async Task AttachAsync() { await _client.LoginAsync(TokenType.Bot, _config.Token); await _client.StartAsync(); } private async Task ClientOnGuildMemberUpdatedAsync(Cacheable cacheable, SocketGuildUser user) { if (await _db.SingleOrDefaultAsync(Sql.Builder.Where("discord_id = @0", user.Id.ToString())) is not { } linkedUser || await _db.SingleOrDefaultAsync(Sql.Builder.Where("client_id = @0", linkedUser.ClientId)) is not { } groupMember) return; bool HasRole(ulong id) => user.Roles.Any(b => b.Id == id); foreach (var (role, group) in _config.RoleConfigs.Select(b => (b, _permissionsManager.GetGroup(b.GroupName)!))) { if (!HasRole(role.RoleId) && groupMember.GroupUid == group.Uid) { _permissionsManager.AssignGroup(linkedUser.ClientIdNumber, "player"); Log.Info($"Removed group {role.GroupName} from {linkedUser.ClientId}"); } else if (HasRole(role.RoleId) && groupMember.GroupUid != group.Uid) { _permissionsManager.AssignGroup(linkedUser.ClientIdNumber, group.Name); groupMember.GroupUid = group.Uid; Log.Info($"Assigned group {role.GroupName} to {linkedUser.ClientId}"); } } } private async Task ClientOnSlashCommandExecutedAsync(SocketSlashCommand arg) { if (arg.CommandName != "lp-link") return; if (GetClientIdByDiscordId(arg.User.Id) is { }) { await arg.RespondAsync("You are already linked with lp."); return; } var username = Format.UsernameAndDiscriminator(arg.User, false); if (!WaitingUsers.ContainsKey(username)) { await arg.RespondAsync("Type `!lp link` in-game first."); return; } var clientId = WaitingUsers[username]; WaitingUsers.Remove(username); await _db.InsertAsync(new LinkedUser { DiscordId = arg.User.Id.ToString(), ClientId = clientId.ToString() }); await arg.RespondAsync("Linked successfully."); } private Task AddSlashCommandsAsync(SocketGuild guild) { return guild.CreateApplicationCommandAsync(new SlashCommandBuilder() .WithName("lp-link") .WithDescription("Light perms link with game") .Build()); } public ulong? GetClientIdByDiscordId(ulong discordId) { if (_db.SingleOrDefault(Sql.Builder.Where("discord_id = @0", discordId.ToString())) is not { } user) return null; return ulong.Parse(user.ClientId); } }