//https://discord.com/oauth2/authorize?client_id=913003037348491264&permissions=274877942784&scope=bot%20messages.read using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; using Discord; using Discord.WebSocket; using vassago.Models; using vassago.Behavior; using Discord.Rest; namespace vassago.DiscordInterface; public class DiscordInterface { internal const string PROTOCOL = "discord"; internal DiscordSocketClient client; private bool eventsSignedUp = false; private ChattingContext _db; public DiscordInterface() { _db = Shared.dbContext; } public async Task Init(string token) { //var c = _db.Channels.FirstOrDefault(ci => ci.ExternalId == channel.Id); //Todo: find protocol reference in DB. //TODO: should protocol be shared across mutliple accounts on that protocol? should protocol be a per-vassago-account thing? //TODO: should protocol be associated with a connection token? how would that be updated? client = new DiscordSocketClient(new DiscordSocketConfig() { GatewayIntents = GatewayIntents.All }); client.Log += (msg) => { Console.WriteLine(msg.ToString()); return Task.CompletedTask; }; client.Ready += () => Task.Run(() => { if (!eventsSignedUp) { eventsSignedUp = true; Console.WriteLine("Bot is connected! going to sign up for message received and user joined in client ready"); client.MessageReceived += MessageReceived; // _client.MessageUpdated += client.UserJoined += UserJoined; client.SlashCommandExecuted += SlashCommandHandler; // _client.ChannelCreated += // _client.ChannelDestroyed += // _client.ChannelUpdated += // _client.GuildMemberUpdated += // _client.UserBanned += // _client.UserLeft += // _client.ThreadCreated += // _client.ThreadUpdated += // _client.ThreadDeleted += // _client.JoinedGuild += // _client.GuildUpdated += // _client.LeftGuild += SlashCommandsHelper.Register(client).GetAwaiter().GetResult(); } else { Console.WriteLine("bot appears to be RE connected, so I'm not going to sign up twice"); } }); await client.LoginAsync(TokenType.Bot, token); await client.StartAsync(); } #pragma warning disable 4014 //the "you're not awaiting this" warning. yeah I know, that's the beauty of an async method lol #pragma warning disable 1998 //the "it's async but you're not awaiting anything". private async Task MessageReceived(SocketMessage messageParam) #pragma warning restore 1998 { var suMessage = messageParam as SocketUserMessage; if (suMessage == null) { Console.WriteLine($"{messageParam.Content}, but not a user message"); return; } Console.WriteLine($"#{suMessage.Channel}[{DateTime.Now}][{suMessage.Author.Username} [id={suMessage.Author.Id}]][msg id: {suMessage.Id}] {suMessage.Content}"); var m = UpsertMessage(suMessage); if (suMessage.MentionedUsers?.FirstOrDefault(muid => muid.Id == client.CurrentUser.Id) != null) { var mentionOfMe = "<@" + client.CurrentUser.Id + ">"; m.MentionsMe = true; } if ((suMessage.Author.Id != client.CurrentUser.Id)) { if (await thingmanagementdoer.Instance.ActOn(m)) { m.ActedOn = true; Console.WriteLine("survived a savechanges: 103"); } } _db.SaveChanges(); } private Task UserJoined(SocketGuildUser arg) { var guild = UpsertChannel(arg.Guild); var defaultChannel = UpsertChannel(arg.Guild.DefaultChannel); defaultChannel.ParentChannel = guild; var u = UpsertUser(arg); //TODO: seen in channels // if (u.SeenInChannels == null) u.SeenInChannels = new List(); // var sighting = u.SeenInChannels?.FirstOrDefault(c => c.ExternalId == arg.Guild.Id); // if (sighting == null) // { // var seenIn = u.SeenInChannels as List; // seenIn.Add(guild); // seenIn.Add(defaultChannel); // u.SeenInChannels = seenIn; // _db.SaveChanges(); Console.WriteLine("survived a savechanges: 123"); // } return thingmanagementdoer.Instance.OnJoin(u, defaultChannel); // Console.WriteLine($"user joined: {arg.Nickname}. Guid: {arg.Guild.Id}. Channel: {arg.Guild.DefaultChannel}"); // var abbreviatedNickname = arg.Nickname; // if (arg.Nickname.Length > 3) // { // abbreviatedNickname = arg.Nickname.Substring(0, arg.Nickname.Length / 3); // } // Console.WriteLine($"imma call him {abbreviatedNickname}"); // return arg.Guild.DefaultChannel.SendMessageAsync($"oh hey {abbreviatedNickname}- IPLAYTHESEALOFORICHALCOS <:ORICHALCOS:852749196633309194>"); } private async Task ButtonHandler(SocketMessageComponent component) { switch (component.Data.CustomId) { case "custom-id": await component.RespondAsync($"{component.User.Mention}, it's been here the whole time!"); break; } } internal static async Task SlashCommandHandler(SocketSlashCommand command) { switch (command.CommandName) { case "freedomunits": try { var amt = Convert.ToDecimal((double)(command.Data.Options.First(o => o.Name == "amount").Value)); var src = (string)command.Data.Options.First(o => o.Name == "src-unit").Value; var dest = (string)command.Data.Options.First(o => o.Name == "dest-unit").Value; var conversionResult = Conversion.Converter.Convert(amt, src, dest); await command.RespondAsync($"> {amt} {src} -> {dest}\n{conversionResult}"); } catch (Exception e) { await command.RespondAsync($"error: {e.Message}. aaadam!"); } break; default: await command.RespondAsync($"\\*smiles and nods*\n"); await command.Channel.SendFileAsync($"assets/loud sweating.gif"); Console.Error.WriteLine($"can't understand command name: {command.CommandName}"); break; } } internal vassago.Models.Attachment UpsertAttachment(IAttachment dAttachment) { var addPlease = false; var a = _db.Attachments.FirstOrDefault(ai => ai.ExternalId == dAttachment.Id); if (a == null) { addPlease = true; a = new vassago.Models.Attachment(); } a.ContentType = dAttachment.ContentType; a.Description = dAttachment.Description; a.Filename = dAttachment.Filename; a.Size = dAttachment.Size; a.Source = new Uri(dAttachment.Url); if (addPlease) { _db.Attachments.Add(a); } return a; } internal Message UpsertMessage(IUserMessage dMessage) { var addPlease = false; var m = _db.Messages.FirstOrDefault(mi => mi.ExternalId == dMessage.Id); if (m == null) { addPlease = true; m = new Message(); } m.Attachments = m.Attachments ?? new List(); if (dMessage.Attachments?.Any() == true) { m.Attachments = new List(); foreach (var da in dMessage.Attachments) { m.Attachments.Add(UpsertAttachment(da)); } } m.Author = UpsertUser(dMessage.Author); m.Channel = UpsertChannel(dMessage.Channel); m.Content = dMessage.Content; m.ExternalId = dMessage.Id; m.Timestamp = dMessage.EditedTimestamp ?? dMessage.CreatedAt; if (dMessage.MentionedUserIds?.FirstOrDefault(muid => muid == client.CurrentUser.Id) != null) { m.MentionsMe = true; } if (addPlease) { _db.Messages.Add(m); } m.Reply = (t) => { return dMessage.ReplyAsync(t); }; m.React = (e) => { return dMessage.AddReactionAsync(Emote.Parse(e)); }; return m; } internal Channel UpsertChannel(IMessageChannel channel) { var addPlease = false; Channel c = _db.Channels.FirstOrDefault(ci => ci.ExternalId == channel.Id); if (c == null) { addPlease = true; c = new Channel(); } c.DisplayName = channel.Name; c.ExternalId = channel.Id; c.IsDM = channel is IPrivateChannel; c.Messages = c.Messages ?? new List(); //c.Messages = await channel.GetMessagesAsync(); //TODO: this, but only on startup or channel join //c.OtherUsers = c.OtherUsers ?? new List(); //c.OtherUsers = await channel.GetUsersAsync(); //TODO: this, but only on startup or channel join c.Protocol = PROTOCOL; if (channel is IGuildChannel) { c.ParentChannel = UpsertChannel((channel as IGuildChannel).Guild); } else if (channel is IPrivateChannel) { c.ParentChannel = null; } else { c.ParentChannel = null; Console.Error.WriteLine($"trying to upsert channel {channel.Id}/{channel.Name}, but it's neither guildchannel nor private channel. shrug.jpg"); } c.ParentChannel.SubChannels.Add(c); c.SubChannels = c.SubChannels ?? new List(); if (addPlease) { _db.Channels.Add(c); } c.SendMessage = (t) => { return channel.SendMessageAsync(t); }; c.SendFile = (f, t) => { return channel.SendFileAsync(f, t); }; return c; } internal Channel UpsertChannel(IGuild channel) { var addPlease = false; Channel c = _db.Channels.FirstOrDefault(ci => ci.ExternalId == channel.Id); if (c == null) { addPlease = true; c = new Channel(); } c.DisplayName = channel.Name; c.ExternalId = channel.Id; c.IsDM = false; c.Messages = c.Messages ?? new List(); //c.Messages = await channel.GetMessagesAsync(); //TODO: this, but only on startup or channel join //c.OtherUsers = c.OtherUsers ?? new List(); //c.OtherUsers = await channel.GetUsersAsync(); //TODO: this, but only on startup or channel join c.Protocol = PROTOCOL; c.ParentChannel = null; c.SubChannels = c.SubChannels ?? new List(); if (addPlease) { _db.Channels.Add(c); } c.SendMessage = (t) => { throw new InvalidOperationException($"channel {channel.Name} is guild; cannot accept text"); }; c.SendFile = (f, t) => { throw new InvalidOperationException($"channel {channel.Name} is guild; send file"); }; return c; } internal User UpsertUser(IUser user) { var addPlease = false; var u = _db.Users.FirstOrDefault(ui => ui.ExternalId == user.Id); if (u == null) { addPlease = true; u = new User(); } u.Username = user.Username; u.ExternalId = user.Id; u.IsBot = user.IsBot || user.IsWebhook; u.Protocol = PROTOCOL; if (addPlease) { _db.Users.Add(u); } return u; } }