From bbfc33bddbada21b39774a9ebad89bf704f28942 Mon Sep 17 00:00:00 2001 From: AridTag Date: Sat, 7 Apr 2018 03:37:05 -0400 Subject: [PATCH 1/9] Added most of a discord implementation --- src/DevChatter.Bot.Core/Util/CommandParser.cs | 107 +++++++++++++ .../DevChatter.Bot.Infra.Discord.csproj | 15 ++ .../DiscordChatClient.cs | 147 ++++++++++++++++++ .../DiscordClientSettings.cs | 12 ++ .../Extensions/ModelExtensions.cs | 40 +++++ src/DevChatter.Bot/BotConfiguration.cs | 2 + src/DevChatter.Bot/DevChatter.Bot.csproj | 1 + src/DevChatter.Bot/Startup/SetUpBot.cs | 8 +- src/DevChatter.Bot/appsettings.json | 9 ++ src/DevChatterBot.sln | 6 + 10 files changed, 343 insertions(+), 4 deletions(-) create mode 100644 src/DevChatter.Bot.Core/Util/CommandParser.cs create mode 100644 src/DevChatter.Bot.Infra.Discord/DevChatter.Bot.Infra.Discord.csproj create mode 100644 src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs create mode 100644 src/DevChatter.Bot.Infra.Discord/DiscordClientSettings.cs create mode 100644 src/DevChatter.Bot.Infra.Discord/Extensions/ModelExtensions.cs diff --git a/src/DevChatter.Bot.Core/Util/CommandParser.cs b/src/DevChatter.Bot.Core/Util/CommandParser.cs new file mode 100644 index 00000000..8ac2f4da --- /dev/null +++ b/src/DevChatter.Bot.Core/Util/CommandParser.cs @@ -0,0 +1,107 @@ +using System.Collections.Generic; + +namespace DevChatter.Bot.Core.Util +{ + public static class CommandParser + { + public static (string commandWord, List arguments) Parse(string commandString, int startIndex = 0) + { + if (startIndex < 0 || string.IsNullOrWhiteSpace(commandString)) + { + return (string.Empty, new List()); + } + + int commandWordEndIndex = commandString.IndexOf(' ', startIndex); + if (commandWordEndIndex == -1) + { + commandWordEndIndex = commandString.Length - 1; + } + + int commandWordLength = commandWordEndIndex - startIndex + 1; + string commandWord = commandString.Substring(startIndex, commandWordLength).Trim(); + if (commandWordEndIndex == commandString.Length - 1) + { + // return early because we have no arguments + return (commandWord, new List()); + } + + string remainingCommand = commandString.Substring(commandWordLength).Trim(); + var arguments = SplitArguments(remainingCommand); + + return (commandWord, arguments); + } + + private static List SplitArguments(string arguments) + { + const char argumentDelimiter = ' '; + const char quoteCharacter = '"'; + + if (string.IsNullOrWhiteSpace(arguments)) + return new List(); + + List splitArguments = new List(); + bool wasInQuotedSection = false; + bool inQuotedSection = false; + int argumentStart = -1; + + for (int i = 0; i < arguments.Length; ++i) + { + if (arguments[i] == quoteCharacter) + { + if (!inQuotedSection) + { + if (i == 0 || arguments[i - 1] == argumentDelimiter) + { + inQuotedSection = true; + wasInQuotedSection = false; + } + } + else + { + if (i == arguments.Length - 1 || arguments[i + 1] == argumentDelimiter) + { + wasInQuotedSection = true; + inQuotedSection = false; + } + } + } + else if(arguments[i] == argumentDelimiter && !inQuotedSection) + { + int argumentLength = i - argumentStart; + if (wasInQuotedSection) + { + // If we were previously in a quoted section we + // need to offset our length by -1 otherwise we get the closing quote + argumentLength -= 1; + } + + var argument = arguments.Substring(argumentStart, argumentLength); + splitArguments.Add(argument); + argumentStart = -1; + wasInQuotedSection = false; + inQuotedSection = false; + } + else if(arguments[i] != argumentDelimiter && argumentStart == -1) + { + // we only want to record the start index + // if the current character is NOT the delimiter + argumentStart = i; + } + } + + if(argumentStart != -1) + { + int argumentLength = arguments.Length - argumentStart; + if(wasInQuotedSection) + { + argumentLength -= 1; + } + + var argument = arguments.Substring(argumentStart, argumentLength); + splitArguments.Add(argument); + } + + return splitArguments; + } + } +} diff --git a/src/DevChatter.Bot.Infra.Discord/DevChatter.Bot.Infra.Discord.csproj b/src/DevChatter.Bot.Infra.Discord/DevChatter.Bot.Infra.Discord.csproj new file mode 100644 index 00000000..fae9e734 --- /dev/null +++ b/src/DevChatter.Bot.Infra.Discord/DevChatter.Bot.Infra.Discord.csproj @@ -0,0 +1,15 @@ + + + + netstandard2.0 + + + + + + + + + + + diff --git a/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs b/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs new file mode 100644 index 00000000..829f5bbf --- /dev/null +++ b/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using DevChatter.Bot.Core.Data.Model; +using DevChatter.Bot.Core.Events; +using DevChatter.Bot.Core.Systems.Chat; +using DevChatter.Bot.Core.Util; +using DevChatter.Bot.Infra.Discord.Extensions; +using Discord; +using Discord.Commands; +using Discord.WebSocket; + +namespace DevChatter.Bot.Infra.Discord +{ + public class DiscordChatClient : IChatClient + { + private readonly DiscordClientSettings _settings; + private readonly DiscordSocketClient _discordClient; + private TaskCompletionSource _connectionCompletionTask = new TaskCompletionSource(); + private TaskCompletionSource _disconnectionCompletionTask = new TaskCompletionSource(); + private SocketGuild _Guild; + private ISocketMessageChannel _TextChannel; + private bool _isReady; + + public DiscordChatClient(DiscordClientSettings settings) + { + _settings = settings; + _discordClient = new DiscordSocketClient(); + _discordClient.MessageReceived += DiscordClientMessageReceived; + _discordClient.GuildAvailable += DiscordClientGuildAvailable; + _discordClient.GuildUnavailable += DiscordClientGuildUnavailable; + } + + private async Task DiscordClientGuildAvailable(SocketGuild arg) + { + _Guild = arg; + _TextChannel = _Guild.Channels.FirstOrDefault(channel => channel.Id == _settings.DiscordTextChannelId) as ISocketMessageChannel; + _isReady = true; + } + + private async Task DiscordClientGuildUnavailable(SocketGuild arg) + { + _Guild = null; + _isReady = false; + } + + private async Task DiscordClientMessageReceived(SocketMessage arg) + { + var message = arg as SocketUserMessage; + if (message == null) + { + return; + } + + // TODO: I'm not sure when this would ever NOT be a SocketGuildUser...direct message maybe? + // should we handle direct messages? + var user = arg.Author as SocketGuildUser; + if (user == null) + { + return; + } + + int commandStartIndex = 0; + if(message.HasCharPrefix(_settings.CommandPrefix, ref commandStartIndex)) + { + var commandInfo = CommandParser.Parse(message.Content, commandStartIndex); + + if (!string.IsNullOrWhiteSpace(commandInfo.commandWord)) + { + SendMessage(commandInfo.commandWord); + RaiseOnCommandReceived(user, commandInfo.commandWord, commandInfo.arguments); + } + } + } + + private void RaiseOnCommandReceived(SocketGuildUser user, string commandWord, List arguments) + { + var eventArgs = new CommandReceivedEventArgs + { + CommandWord = commandWord, + Arguments = arguments ?? new List(), + ChatUser = user.ToChatUser(_settings) + }; + + OnCommandReceived?.Invoke(this, eventArgs); + } + + public async Task Connect() + { + _discordClient.Connected += DiscordClientConnected; + await _discordClient.LoginAsync(TokenType.Bot, _settings.DiscordToken).ConfigureAwait(false); + await _discordClient.StartAsync().ConfigureAwait(false); + + await _connectionCompletionTask.Task; + } + + private async Task DiscordClientConnected() + { + _discordClient.Connected -= DiscordClientConnected; + + _connectionCompletionTask?.SetResult(true); + _disconnectionCompletionTask = new TaskCompletionSource(); + } + + public async Task Disconnect() + { + _discordClient.Disconnected += DiscordClientDisconnected; + await _discordClient.LogoutAsync().ConfigureAwait(false); + await _discordClient.StopAsync().ConfigureAwait(false); + + await _disconnectionCompletionTask.Task; + } + + private async Task DiscordClientDisconnected(Exception arg) + { + _discordClient.Disconnected -= DiscordClientDisconnected; + + _disconnectionCompletionTask.SetResult(true); + _connectionCompletionTask = new TaskCompletionSource(); + } + + public List GetAllChatters() + { + if(!_isReady) + return new List(); + + var chatUsers = _Guild.Users.Select(user => user.ToChatUser(_settings)).ToList(); + return chatUsers; + } + + public void SendMessage(string message) + { + if (!_isReady) + { + return; + } + + _TextChannel.SendMessageAsync(message).Wait(); + } + + public event EventHandler OnCommandReceived; + public event EventHandler OnNewSubscriber; + public event EventHandler OnUserNoticed; + public event EventHandler OnUserLeft; + } +} diff --git a/src/DevChatter.Bot.Infra.Discord/DiscordClientSettings.cs b/src/DevChatter.Bot.Infra.Discord/DiscordClientSettings.cs new file mode 100644 index 00000000..868eb043 --- /dev/null +++ b/src/DevChatter.Bot.Infra.Discord/DiscordClientSettings.cs @@ -0,0 +1,12 @@ +namespace DevChatter.Bot.Infra.Discord +{ + public class DiscordClientSettings + { + public string DiscordToken { get; set; } + public ulong DiscordStreamerRoleId { get; set; } + public ulong DiscordModeratorRoleId { get; set; } + public ulong DiscordSubscriberRoleId { get; set; } + public ulong DiscordTextChannelId { get; set; } + public char CommandPrefix { get; set; } + } +} \ No newline at end of file diff --git a/src/DevChatter.Bot.Infra.Discord/Extensions/ModelExtensions.cs b/src/DevChatter.Bot.Infra.Discord/Extensions/ModelExtensions.cs new file mode 100644 index 00000000..35093ef6 --- /dev/null +++ b/src/DevChatter.Bot.Infra.Discord/Extensions/ModelExtensions.cs @@ -0,0 +1,40 @@ +using System.Linq; +using DevChatter.Bot.Core.Data.Model; +using Discord.WebSocket; + +namespace DevChatter.Bot.Infra.Discord.Extensions +{ + public static class ModelExtensions + { + public static ChatUser ToChatUser(this SocketGuildUser discordUser, DiscordClientSettings settings) + { + var chatUser = new ChatUser + { + DisplayName = discordUser.Username, + Role = discordUser.ToUserRole(settings) + }; + + return chatUser; + } + + public static UserRole ToUserRole(this SocketGuildUser discordUser, DiscordClientSettings settings) + { + if (discordUser.Roles.Any(role => role.Id == settings.DiscordStreamerRoleId)) + { + return UserRole.Streamer; + } + + if (discordUser.Roles.Any(role => role.Id == settings.DiscordModeratorRoleId)) + { + return UserRole.Mod; + } + + if (discordUser.Roles.Any(role => role.Id == settings.DiscordSubscriberRoleId)) + { + return UserRole.Subscriber; + } + + return UserRole.Everyone; + } + } +} diff --git a/src/DevChatter.Bot/BotConfiguration.cs b/src/DevChatter.Bot/BotConfiguration.cs index 52ef454f..35ca6c64 100644 --- a/src/DevChatter.Bot/BotConfiguration.cs +++ b/src/DevChatter.Bot/BotConfiguration.cs @@ -1,4 +1,5 @@ using DevChatter.Bot.Core.Events; +using DevChatter.Bot.Infra.Discord; using DevChatter.Bot.Infra.Twitch; namespace DevChatter.Bot @@ -7,6 +8,7 @@ public class BotConfiguration { public string DatabaseConnectionString { get; set; } public TwitchClientSettings TwitchClientSettings { get; set; } + public DiscordClientSettings DiscordClientSettings { get; set; } public CommandHandlerSettings CommandHandlerSettings { get; set; } } } diff --git a/src/DevChatter.Bot/DevChatter.Bot.csproj b/src/DevChatter.Bot/DevChatter.Bot.csproj index dbbdde89..0988dba9 100644 --- a/src/DevChatter.Bot/DevChatter.Bot.csproj +++ b/src/DevChatter.Bot/DevChatter.Bot.csproj @@ -20,6 +20,7 @@ + diff --git a/src/DevChatter.Bot/Startup/SetUpBot.cs b/src/DevChatter.Bot/Startup/SetUpBot.cs index bc48708c..f3e4ace9 100644 --- a/src/DevChatter.Bot/Startup/SetUpBot.cs +++ b/src/DevChatter.Bot/Startup/SetUpBot.cs @@ -21,11 +21,11 @@ public static IContainer NewBotDependencyContainer(BotConfiguration botConfigura builder.Register(ctx => botConfiguration.CommandHandlerSettings).AsSelf().SingleInstance(); builder.Register(ctx => botConfiguration.TwitchClientSettings).AsSelf().SingleInstance(); + builder.Register(ctx => botConfiguration.DiscordClientSettings).AsSelf().SingleInstance(); - builder.Register(ctx => repository) - .As().SingleInstance(); - - + + builder.RegisterType().AsImplementedInterfaces().SingleInstance(); + var simpleCommands = repository.List(); foreach (var command in simpleCommands) { diff --git a/src/DevChatter.Bot/appsettings.json b/src/DevChatter.Bot/appsettings.json index 2f8c6be6..67f2ebf7 100644 --- a/src/DevChatter.Bot/appsettings.json +++ b/src/DevChatter.Bot/appsettings.json @@ -10,6 +10,15 @@ "TwitchClientId": "secret" }, + "DiscordClientSettings": { + "DiscordToken": "secret", + "DiscordStreamerRoleId": "0", + "DiscordModeratorRoleId": "0", + "DiscordSubscriberRoleId": "0", + "DiscordTextChannelId": "0", + "CommandPrefix": "!" + }, + "CommandHandlerSettings": { "GlobalCommandCooldown": "0.5" } diff --git a/src/DevChatterBot.sln b/src/DevChatterBot.sln index 7dba3b56..f5709bd7 100644 --- a/src/DevChatterBot.sln +++ b/src/DevChatterBot.sln @@ -18,6 +18,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DevChatter.Bot.Infra.Discord", "DevChatter.Bot.Infra.Discord\DevChatter.Bot.Infra.Discord.csproj", "{F76F8BD9-3CFB-47EC-8115-29186E4165D9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -44,6 +46,10 @@ Global {EB61DAF6-52D8-4203-AACA-A896D6DB02CB}.Debug|Any CPU.Build.0 = Debug|Any CPU {EB61DAF6-52D8-4203-AACA-A896D6DB02CB}.Release|Any CPU.ActiveCfg = Release|Any CPU {EB61DAF6-52D8-4203-AACA-A896D6DB02CB}.Release|Any CPU.Build.0 = Release|Any CPU + {F76F8BD9-3CFB-47EC-8115-29186E4165D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F76F8BD9-3CFB-47EC-8115-29186E4165D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F76F8BD9-3CFB-47EC-8115-29186E4165D9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F76F8BD9-3CFB-47EC-8115-29186E4165D9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 551e5969aa8dd7535684397e6aa06feda978681c Mon Sep 17 00:00:00 2001 From: AridTag Date: Sat, 7 Apr 2018 04:15:01 -0400 Subject: [PATCH 2/9] woops forgot to add this change --- src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs b/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs index 829f5bbf..2aa8259d 100644 --- a/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs +++ b/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs @@ -136,7 +136,7 @@ public void SendMessage(string message) return; } - _TextChannel.SendMessageAsync(message).Wait(); + _TextChannel.SendMessageAsync($"`{message}`").Wait(); } public event EventHandler OnCommandReceived; From b9424d8b823b6f5018f0f1f4a14beee3c885ae47 Mon Sep 17 00:00:00 2001 From: AridTag Date: Sat, 7 Apr 2018 04:46:12 -0400 Subject: [PATCH 3/9] Left some debug code in by accident --- src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs b/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs index 2aa8259d..f6753f09 100644 --- a/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs +++ b/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs @@ -68,7 +68,6 @@ private async Task DiscordClientMessageReceived(SocketMessage arg) if (!string.IsNullOrWhiteSpace(commandInfo.commandWord)) { - SendMessage(commandInfo.commandWord); RaiseOnCommandReceived(user, commandInfo.commandWord, commandInfo.arguments); } } From ed72f8923d4e0a20a661101f4abf3ffbb5319cd1 Mon Sep 17 00:00:00 2001 From: AridTag Date: Sat, 7 Apr 2018 14:14:05 -0400 Subject: [PATCH 4/9] Raise UserNoticed/Left --- .../DiscordChatClient.cs | 58 +++++++++++++++---- 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs b/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs index f6753f09..9000d21b 100644 --- a/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs +++ b/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs @@ -30,6 +30,8 @@ public DiscordChatClient(DiscordClientSettings settings) _discordClient.MessageReceived += DiscordClientMessageReceived; _discordClient.GuildAvailable += DiscordClientGuildAvailable; _discordClient.GuildUnavailable += DiscordClientGuildUnavailable; + _discordClient.UserJoined += DiscordClientUserJoined; + _discordClient.UserLeft += DiscordClientUserLeft; } private async Task DiscordClientGuildAvailable(SocketGuild arg) @@ -73,18 +75,6 @@ private async Task DiscordClientMessageReceived(SocketMessage arg) } } - private void RaiseOnCommandReceived(SocketGuildUser user, string commandWord, List arguments) - { - var eventArgs = new CommandReceivedEventArgs - { - CommandWord = commandWord, - Arguments = arguments ?? new List(), - ChatUser = user.ToChatUser(_settings) - }; - - OnCommandReceived?.Invoke(this, eventArgs); - } - public async Task Connect() { _discordClient.Connected += DiscordClientConnected; @@ -119,6 +109,16 @@ private async Task DiscordClientDisconnected(Exception arg) _connectionCompletionTask = new TaskCompletionSource(); } + private async Task DiscordClientUserJoined(SocketGuildUser arg) + { + RaiseOnUserNoticed(arg); + } + + private async Task DiscordClientUserLeft(SocketGuildUser arg) + { + RaiseOnUserLeft(arg); + } + public List GetAllChatters() { if(!_isReady) @@ -138,6 +138,40 @@ public void SendMessage(string message) _TextChannel.SendMessageAsync($"`{message}`").Wait(); } + private void RaiseOnCommandReceived(SocketGuildUser user, string commandWord, List arguments) + { + var eventArgs = new CommandReceivedEventArgs + { + CommandWord = commandWord, + Arguments = arguments ?? new List(), + ChatUser = user.ToChatUser(_settings) + }; + + OnCommandReceived?.Invoke(this, eventArgs); + } + + private void RaiseOnUserNoticed(SocketGuildUser user) + { + var eventArgs = new UserStatusEventArgs + { + DisplayName = user.Username, + Role = user.ToUserRole(_settings) + }; + + OnUserNoticed?.Invoke(this, eventArgs); + } + + private void RaiseOnUserLeft(SocketGuildUser user) + { + var eventArgs = new UserStatusEventArgs + { + DisplayName = user.Username, + Role = user.ToUserRole(_settings) + }; + + OnUserLeft?.Invoke(this, eventArgs); + } + public event EventHandler OnCommandReceived; public event EventHandler OnNewSubscriber; public event EventHandler OnUserNoticed; From 4791a6a4140516d374b176b0e1f90a02d21be3d5 Mon Sep 17 00:00:00 2001 From: AridTag Date: Sat, 7 Apr 2018 15:08:23 -0400 Subject: [PATCH 5/9] Moved some stuff around --- .../DiscordChatClient.cs | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs b/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs index 9000d21b..909b3c6f 100644 --- a/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs +++ b/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs @@ -54,24 +54,26 @@ private async Task DiscordClientMessageReceived(SocketMessage arg) { return; } - - // TODO: I'm not sure when this would ever NOT be a SocketGuildUser...direct message maybe? - // should we handle direct messages? - var user = arg.Author as SocketGuildUser; - if (user == null) - { - return; - } - + int commandStartIndex = 0; - if(message.HasCharPrefix(_settings.CommandPrefix, ref commandStartIndex)) + if (message.HasCharPrefix(_settings.CommandPrefix, ref commandStartIndex)) { - var commandInfo = CommandParser.Parse(message.Content, commandStartIndex); - - if (!string.IsNullOrWhiteSpace(commandInfo.commandWord)) + if (arg.Author is SocketGuildUser guildUser) { - RaiseOnCommandReceived(user, commandInfo.commandWord, commandInfo.arguments); + GuildMessageReceived(guildUser, commandStartIndex, arg.Content); } + + // TODO: arg.Author could be of type SocketGlobalUser (but the type is internal...) which means we got a direct message. + // I'm not sure how else I can detect the difference I'm not seeing anything obvious in the API + } + } + + private void GuildMessageReceived(SocketGuildUser user, int commandStartIndex, string message) + { + var commandInfo = CommandParser.Parse(message, commandStartIndex); + if (!string.IsNullOrWhiteSpace(commandInfo.commandWord)) + { + RaiseOnCommandReceived(user, commandInfo.commandWord, commandInfo.arguments); } } From b9d103e10e5399d1dbe0a534f8eb97913b8823bc Mon Sep 17 00:00:00 2001 From: AridTag Date: Sat, 7 Apr 2018 17:57:33 -0400 Subject: [PATCH 6/9] Resolve knowing if it's a direct message or a message in a guild channel --- .../DiscordChatClient.cs | 36 +++++++++++++------ .../Extensions/ModelExtensions.cs | 12 +++---- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs b/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs index 909b3c6f..4c2c79b1 100644 --- a/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs +++ b/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs @@ -20,6 +20,7 @@ public class DiscordChatClient : IChatClient private TaskCompletionSource _connectionCompletionTask = new TaskCompletionSource(); private TaskCompletionSource _disconnectionCompletionTask = new TaskCompletionSource(); private SocketGuild _Guild; + private readonly List _GuildChannelIds = new List(); private ISocketMessageChannel _TextChannel; private bool _isReady; @@ -37,6 +38,7 @@ public DiscordChatClient(DiscordClientSettings settings) private async Task DiscordClientGuildAvailable(SocketGuild arg) { _Guild = arg; + _GuildChannelIds.AddRange(_Guild.Channels.Select(channel => channel.Id)); _TextChannel = _Guild.Channels.FirstOrDefault(channel => channel.Id == _settings.DiscordTextChannelId) as ISocketMessageChannel; _isReady = true; } @@ -44,6 +46,7 @@ private async Task DiscordClientGuildAvailable(SocketGuild arg) private async Task DiscordClientGuildUnavailable(SocketGuild arg) { _Guild = null; + _GuildChannelIds.Clear(); _isReady = false; } @@ -58,23 +61,34 @@ private async Task DiscordClientMessageReceived(SocketMessage arg) int commandStartIndex = 0; if (message.HasCharPrefix(_settings.CommandPrefix, ref commandStartIndex)) { - if (arg.Author is SocketGuildUser guildUser) + if (_GuildChannelIds.Contains(message.Channel.Id)) { - GuildMessageReceived(guildUser, commandStartIndex, arg.Content); + if (arg.Author is IGuildUser guildUser) + { + GuildCommandReceived(guildUser, commandStartIndex, arg.Content); + } + } + else + { + DirectCommandReceieved(arg.Author, commandStartIndex, arg.Content); } - - // TODO: arg.Author could be of type SocketGlobalUser (but the type is internal...) which means we got a direct message. - // I'm not sure how else I can detect the difference I'm not seeing anything obvious in the API } } - private void GuildMessageReceived(SocketGuildUser user, int commandStartIndex, string message) + private void GuildCommandReceived(IGuildUser user, int commandStartIndex, string message) { var commandInfo = CommandParser.Parse(message, commandStartIndex); - if (!string.IsNullOrWhiteSpace(commandInfo.commandWord)) - { - RaiseOnCommandReceived(user, commandInfo.commandWord, commandInfo.arguments); - } + if (string.IsNullOrWhiteSpace(commandInfo.commandWord)) return; + + RaiseOnCommandReceived(user, commandInfo.commandWord, commandInfo.arguments); + } + + private void DirectCommandReceieved(IUser user, int commandStartIndex, string message) + { + var commandInfo = CommandParser.Parse(message, commandStartIndex); + if (string.IsNullOrWhiteSpace(commandInfo.commandWord)) return; + + // TODO: Do we want to handle direct message commands? } public async Task Connect() @@ -140,7 +154,7 @@ public void SendMessage(string message) _TextChannel.SendMessageAsync($"`{message}`").Wait(); } - private void RaiseOnCommandReceived(SocketGuildUser user, string commandWord, List arguments) + private void RaiseOnCommandReceived(IGuildUser user, string commandWord, List arguments) { var eventArgs = new CommandReceivedEventArgs { diff --git a/src/DevChatter.Bot.Infra.Discord/Extensions/ModelExtensions.cs b/src/DevChatter.Bot.Infra.Discord/Extensions/ModelExtensions.cs index 35093ef6..b846afae 100644 --- a/src/DevChatter.Bot.Infra.Discord/Extensions/ModelExtensions.cs +++ b/src/DevChatter.Bot.Infra.Discord/Extensions/ModelExtensions.cs @@ -1,12 +1,12 @@ using System.Linq; using DevChatter.Bot.Core.Data.Model; -using Discord.WebSocket; +using Discord; namespace DevChatter.Bot.Infra.Discord.Extensions { public static class ModelExtensions { - public static ChatUser ToChatUser(this SocketGuildUser discordUser, DiscordClientSettings settings) + public static ChatUser ToChatUser(this IGuildUser discordUser, DiscordClientSettings settings) { var chatUser = new ChatUser { @@ -17,19 +17,19 @@ public static ChatUser ToChatUser(this SocketGuildUser discordUser, DiscordClien return chatUser; } - public static UserRole ToUserRole(this SocketGuildUser discordUser, DiscordClientSettings settings) + public static UserRole ToUserRole(this IGuildUser discordUser, DiscordClientSettings settings) { - if (discordUser.Roles.Any(role => role.Id == settings.DiscordStreamerRoleId)) + if (discordUser.RoleIds.Any(role => role == settings.DiscordStreamerRoleId)) { return UserRole.Streamer; } - if (discordUser.Roles.Any(role => role.Id == settings.DiscordModeratorRoleId)) + if (discordUser.RoleIds.Any(role => role == settings.DiscordModeratorRoleId)) { return UserRole.Mod; } - if (discordUser.Roles.Any(role => role.Id == settings.DiscordSubscriberRoleId)) + if (discordUser.RoleIds.Any(role => role == settings.DiscordSubscriberRoleId)) { return UserRole.Subscriber; } From 0fd3415a860ef653db522b4c9b783f710a96f75e Mon Sep 17 00:00:00 2001 From: AridTag Date: Sat, 7 Apr 2018 18:18:32 -0400 Subject: [PATCH 7/9] Handle channels being created or destroyed --- .../DiscordChatClient.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs b/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs index 4c2c79b1..bd9833a6 100644 --- a/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs +++ b/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs @@ -31,25 +31,37 @@ public DiscordChatClient(DiscordClientSettings settings) _discordClient.MessageReceived += DiscordClientMessageReceived; _discordClient.GuildAvailable += DiscordClientGuildAvailable; _discordClient.GuildUnavailable += DiscordClientGuildUnavailable; + _discordClient.ChannelCreated += DiscordClientChannelCreated; + _discordClient.ChannelDestroyed += DiscordClientChannelDestroyed; _discordClient.UserJoined += DiscordClientUserJoined; _discordClient.UserLeft += DiscordClientUserLeft; } - private async Task DiscordClientGuildAvailable(SocketGuild arg) + private async Task DiscordClientGuildAvailable(SocketGuild guild) { - _Guild = arg; + _Guild = guild; _GuildChannelIds.AddRange(_Guild.Channels.Select(channel => channel.Id)); _TextChannel = _Guild.Channels.FirstOrDefault(channel => channel.Id == _settings.DiscordTextChannelId) as ISocketMessageChannel; _isReady = true; } - private async Task DiscordClientGuildUnavailable(SocketGuild arg) + private async Task DiscordClientGuildUnavailable(SocketGuild guild) { _Guild = null; _GuildChannelIds.Clear(); _isReady = false; } + private async Task DiscordClientChannelCreated(SocketChannel newChannel) + { + _GuildChannelIds.Add(newChannel.Id); + } + + private async Task DiscordClientChannelDestroyed(SocketChannel oldChannel) + { + _GuildChannelIds.Remove(oldChannel.Id); + } + private async Task DiscordClientMessageReceived(SocketMessage arg) { var message = arg as SocketUserMessage; From 0014d42e993cb455c72d2f85fc97a588a5b0d70a Mon Sep 17 00:00:00 2001 From: AridTag Date: Mon, 9 Apr 2018 22:51:02 -0400 Subject: [PATCH 8/9] Fix up rebase changes and correct naming for a couple fields --- .../DiscordChatClient.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs b/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs index bd9833a6..375c4f91 100644 --- a/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs +++ b/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs @@ -19,9 +19,9 @@ public class DiscordChatClient : IChatClient private readonly DiscordSocketClient _discordClient; private TaskCompletionSource _connectionCompletionTask = new TaskCompletionSource(); private TaskCompletionSource _disconnectionCompletionTask = new TaskCompletionSource(); - private SocketGuild _Guild; - private readonly List _GuildChannelIds = new List(); - private ISocketMessageChannel _TextChannel; + private SocketGuild _guild; + private readonly List _guildChannels = new List(); + private ISocketMessageChannel _textChannel; private bool _isReady; public DiscordChatClient(DiscordClientSettings settings) @@ -39,27 +39,27 @@ public DiscordChatClient(DiscordClientSettings settings) private async Task DiscordClientGuildAvailable(SocketGuild guild) { - _Guild = guild; - _GuildChannelIds.AddRange(_Guild.Channels.Select(channel => channel.Id)); - _TextChannel = _Guild.Channels.FirstOrDefault(channel => channel.Id == _settings.DiscordTextChannelId) as ISocketMessageChannel; + _guild = guild; + _guildChannels.AddRange(_guild.Channels.Select(channel => channel.Id)); + _textChannel = _guild.Channels.FirstOrDefault(channel => channel.Id == _settings.DiscordTextChannelId) as ISocketMessageChannel; _isReady = true; } private async Task DiscordClientGuildUnavailable(SocketGuild guild) { - _Guild = null; - _GuildChannelIds.Clear(); + _guild = null; + _guildChannels.Clear(); _isReady = false; } private async Task DiscordClientChannelCreated(SocketChannel newChannel) { - _GuildChannelIds.Add(newChannel.Id); + _guildChannels.Add(newChannel.Id); } private async Task DiscordClientChannelDestroyed(SocketChannel oldChannel) { - _GuildChannelIds.Remove(oldChannel.Id); + _guildChannels.Remove(oldChannel.Id); } private async Task DiscordClientMessageReceived(SocketMessage arg) @@ -73,7 +73,7 @@ private async Task DiscordClientMessageReceived(SocketMessage arg) int commandStartIndex = 0; if (message.HasCharPrefix(_settings.CommandPrefix, ref commandStartIndex)) { - if (_GuildChannelIds.Contains(message.Channel.Id)) + if (_guildChannels.Contains(message.Channel.Id)) { if (arg.Author is IGuildUser guildUser) { @@ -147,12 +147,12 @@ private async Task DiscordClientUserLeft(SocketGuildUser arg) RaiseOnUserLeft(arg); } - public List GetAllChatters() + public IList GetAllChatters() { if(!_isReady) return new List(); - var chatUsers = _Guild.Users.Select(user => user.ToChatUser(_settings)).ToList(); + var chatUsers = _guild.Users.Select(user => user.ToChatUser(_settings)).ToList(); return chatUsers; } @@ -163,7 +163,7 @@ public void SendMessage(string message) return; } - _TextChannel.SendMessageAsync($"`{message}`").Wait(); + _textChannel.SendMessageAsync($"`{message}`").Wait(); } private void RaiseOnCommandReceived(IGuildUser user, string commandWord, List arguments) From ee36553e4e7bd662daf4bc5cd05d4b371e89c88a Mon Sep 17 00:00:00 2001 From: AridTag Date: Thu, 10 May 2018 21:17:02 -0400 Subject: [PATCH 9/9] Fix build errors after rebase --- .../DevChatter.Bot.Infra.Discord.csproj | 1 + .../DevChatterBotDiscordModule.cs | 12 +++++++ .../DiscordChatClient.cs | 34 +++++++++++++++---- src/DevChatter.Bot/Startup/SetUpBot.cs | 6 ++-- 4 files changed, 43 insertions(+), 10 deletions(-) create mode 100644 src/DevChatter.Bot.Infra.Discord/DevChatterBotDiscordModule.cs diff --git a/src/DevChatter.Bot.Infra.Discord/DevChatter.Bot.Infra.Discord.csproj b/src/DevChatter.Bot.Infra.Discord/DevChatter.Bot.Infra.Discord.csproj index fae9e734..90594152 100644 --- a/src/DevChatter.Bot.Infra.Discord/DevChatter.Bot.Infra.Discord.csproj +++ b/src/DevChatter.Bot.Infra.Discord/DevChatter.Bot.Infra.Discord.csproj @@ -5,6 +5,7 @@ + diff --git a/src/DevChatter.Bot.Infra.Discord/DevChatterBotDiscordModule.cs b/src/DevChatter.Bot.Infra.Discord/DevChatterBotDiscordModule.cs new file mode 100644 index 00000000..ba9b3bf3 --- /dev/null +++ b/src/DevChatter.Bot.Infra.Discord/DevChatterBotDiscordModule.cs @@ -0,0 +1,12 @@ +using Autofac; + +namespace DevChatter.Bot.Infra.Discord +{ + public class DevChatterBotDiscordModule : Module + { + protected override void Load(ContainerBuilder builder) + { + builder.RegisterType().AsImplementedInterfaces().SingleInstance(); + } + } +} diff --git a/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs b/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs index 375c4f91..93c993f7 100644 --- a/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs +++ b/src/DevChatter.Bot.Infra.Discord/DiscordChatClient.cs @@ -1,9 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using DevChatter.Bot.Core.Data.Model; -using DevChatter.Bot.Core.Events; +using DevChatter.Bot.Core.Events.Args; using DevChatter.Bot.Core.Systems.Chat; using DevChatter.Bot.Core.Util; using DevChatter.Bot.Infra.Discord.Extensions; @@ -90,7 +90,10 @@ private async Task DiscordClientMessageReceived(SocketMessage arg) private void GuildCommandReceived(IGuildUser user, int commandStartIndex, string message) { var commandInfo = CommandParser.Parse(message, commandStartIndex); - if (string.IsNullOrWhiteSpace(commandInfo.commandWord)) return; + if(string.IsNullOrWhiteSpace(commandInfo.commandWord)) + { + return; + } RaiseOnCommandReceived(user, commandInfo.commandWord, commandInfo.arguments); } @@ -98,7 +101,10 @@ private void GuildCommandReceived(IGuildUser user, int commandStartIndex, string private void DirectCommandReceieved(IUser user, int commandStartIndex, string message) { var commandInfo = CommandParser.Parse(message, commandStartIndex); - if (string.IsNullOrWhiteSpace(commandInfo.commandWord)) return; + if(string.IsNullOrWhiteSpace(commandInfo.commandWord)) + { + return; + } // TODO: Do we want to handle direct message commands? } @@ -147,10 +153,15 @@ private async Task DiscordClientUserLeft(SocketGuildUser arg) RaiseOnUserLeft(arg); } + /// + /// If not connected to a guild + /// public IList GetAllChatters() { - if(!_isReady) + if (!_isReady) + { return new List(); + } var chatUsers = _guild.Users.Select(user => user.ToChatUser(_settings)).ToList(); return chatUsers; @@ -163,7 +174,18 @@ public void SendMessage(string message) return; } - _textChannel.SendMessageAsync($"`{message}`").Wait(); + _textChannel?.SendMessageAsync($"`{message}`").Wait(); + } + + public void SendDirectMessage(string username, string message) + { + if (!_isReady) + { + return; + } + + var discordUser = _guild.Users.FirstOrDefault(u => u.Username == username); + discordUser?.SendMessageAsync(message); } private void RaiseOnCommandReceived(IGuildUser user, string commandWord, List arguments) diff --git a/src/DevChatter.Bot/Startup/SetUpBot.cs b/src/DevChatter.Bot/Startup/SetUpBot.cs index f3e4ace9..130498fd 100644 --- a/src/DevChatter.Bot/Startup/SetUpBot.cs +++ b/src/DevChatter.Bot/Startup/SetUpBot.cs @@ -2,8 +2,8 @@ using DevChatter.Bot.Core; using DevChatter.Bot.Core.Commands; using DevChatter.Bot.Core.Commands.Trackers; -using DevChatter.Bot.Core.Data; using DevChatter.Bot.Infra.Twitch; +using DevChatter.Bot.Infra.Discord; using System.Linq; namespace DevChatter.Bot.Startup @@ -18,14 +18,12 @@ public static IContainer NewBotDependencyContainer(BotConfiguration botConfigura builder.RegisterModule(); builder.RegisterModule(); + builder.RegisterModule(); builder.Register(ctx => botConfiguration.CommandHandlerSettings).AsSelf().SingleInstance(); builder.Register(ctx => botConfiguration.TwitchClientSettings).AsSelf().SingleInstance(); builder.Register(ctx => botConfiguration.DiscordClientSettings).AsSelf().SingleInstance(); - - builder.RegisterType().AsImplementedInterfaces().SingleInstance(); - var simpleCommands = repository.List(); foreach (var command in simpleCommands) {