Various other improvements

-Revised help command text
-Added case when using bb.config without parameters
-Added message if regular user attempts to use bb.config
-Role set warning now only is sent out periodically
-Performance improvements in initial command parsing
-Implemented moderated mode setting
-Renamed "ban" to "block"
-Bumped up version number
This commit is contained in:
Noikoio 2018-07-27 15:47:25 -07:00
parent 756a63c954
commit 0df0fb5a64
7 changed files with 170 additions and 98 deletions

View file

@ -2,16 +2,17 @@
Option Explicit On Option Explicit On
Imports BirthdayBot.CommandsCommon Imports BirthdayBot.CommandsCommon
Imports Discord Imports Discord
Imports Discord.Net
Imports Discord.WebSocket Imports Discord.WebSocket
Class BirthdayBot Class BirthdayBot
Const RoleWarningMsg As String = Const RoleWarningMsg As String =
"Note: This bot does not have a role set or is unable to use the role specified. " + "Note: This bot does not have a role set or is unable to use the role specified. " +
"Update the designated role with `bb.config role (role name/ID). This bot cannot function without it." "Update the designated role with `bb.config role (role name/ID)`. This bot cannot function without it."
Private ReadOnly _dispatchCommands As Dictionary(Of String, CommandHandler) Private ReadOnly _dispatchCommands As Dictionary(Of String, CommandHandler)
Private ReadOnly _cmdsUser As UserCommands Private ReadOnly _cmdsUser As UserCommands
Private ReadOnly _cmdsHelp As HelpCommands Private ReadOnly _cmdsHelp As HelpInfoCommands
Private ReadOnly _cmdsMods As ManagerCommands Private ReadOnly _cmdsMods As ManagerCommands
Private WithEvents _client As DiscordSocketClient Private WithEvents _client As DiscordSocketClient
@ -40,7 +41,7 @@ Class BirthdayBot
For Each item In _cmdsUser.Commands For Each item In _cmdsUser.Commands
_dispatchCommands.Add(item.Item1, item.Item2) _dispatchCommands.Add(item.Item1, item.Item2)
Next Next
_cmdsHelp = New HelpCommands(Me, conf) _cmdsHelp = New HelpInfoCommands(Me, conf)
For Each item In _cmdsHelp.Commands For Each item In _cmdsHelp.Commands
_dispatchCommands.Add(item.Item1, item.Item2) _dispatchCommands.Add(item.Item1, item.Item2)
Next Next
@ -102,15 +103,22 @@ Class BirthdayBot
Dim channel = CType(msg.Channel, SocketTextChannel) Dim channel = CType(msg.Channel, SocketTextChannel)
Dim author = CType(msg.Author, SocketGuildUser) Dim author = CType(msg.Author, SocketGuildUser)
Dim roleWarning As Boolean ' Determine if it's something we're listening for.
' Doing this first before the block check because a block check triggers a database query.
Dim command As CommandHandler = Nothing
If Not _dispatchCommands.TryGetValue(csplit(0).Substring(CommandPrefix.Length), command) Then
Return
End If
' Ban and role warning check ' Ban and role warning check
Dim roleWarning As Boolean
Dim isManager = author.GuildPermissions.ManageGuild
SyncLock KnownGuilds SyncLock KnownGuilds
Dim gi = KnownGuilds(channel.Guild.Id) Dim gi = KnownGuilds(channel.Guild.Id)
' Skip ban check if user is a manager ' Skip ban check if user is a manager
If Not author.GuildPermissions.ManageGuild Then If Not isManager Then
If gi.IsUserBannedAsync(author.Id).GetAwaiter().GetResult() Then If gi.IsUserBlockedAsync(author.Id).GetAwaiter().GetResult() Then
Return Return
End If End If
End If End If
@ -118,18 +126,24 @@ Class BirthdayBot
roleWarning = gi.RoleWarning roleWarning = gi.RoleWarning
End SyncLock End SyncLock
Dim h As CommandHandler = Nothing Try
If _dispatchCommands.TryGetValue(csplit(0).Substring(CommandPrefix.Length), h) Then If roleWarning Then
Try Try
Await h(csplit, channel, author)
If roleWarning Then
Await channel.SendMessageAsync(RoleWarningMsg) Await channel.SendMessageAsync(RoleWarningMsg)
End If Catch ex As HttpException
Catch ex As Exception ' Don't let this prevent the bot from continuing command execution.
End Try
End If
Await command(csplit, channel, author)
Catch ex As Exception
If TypeOf ex Is HttpException Then Return
Log("Error", ex.ToString())
Try
channel.SendMessageAsync(":x: An unknown error occurred. It has been reported to the bot owner.").Wait() channel.SendMessageAsync(":x: An unknown error occurred. It has been reported to the bot owner.").Wait()
Log("Error", ex.ToString()) Catch ex2 As HttpException
' Fail silently.
End Try End Try
End If End Try
End If End If
End If End If
End Function End Function

View file

@ -4,8 +4,8 @@
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<RootNamespace>BirthdayBot</RootNamespace> <RootNamespace>BirthdayBot</RootNamespace>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netcoreapp2.0</TargetFramework>
<Version>0.1.0</Version> <Version>0.3.0</Version>
<AssemblyVersion>0.1.0.0</AssemblyVersion> <AssemblyVersion>0.3.0.0</AssemblyVersion>
<Authors>Noikoio</Authors> <Authors>Noikoio</Authors>
<Company /> <Company />
<Description>Discord bot for birthday reminders.</Description> <Description>Discord bot for birthday reminders.</Description>

View file

@ -18,10 +18,31 @@ Friend Class GuildSettings
Private _modded As Boolean Private _modded As Boolean
Private _userCache As Dictionary(Of ULong, GuildUserSettings) Private _userCache As Dictionary(Of ULong, GuildUserSettings)
Private _roleWarning As Boolean
Private _roleLastWarning As New DateTimeOffset(DateTime.MinValue, TimeSpan.Zero)
Private Shared ReadOnly RoleWarningInterval As New TimeSpan(0, 10, 0)
''' <summary> ''' <summary>
''' Flag for notifying servers that the bot is unable to manipulate its role. ''' Flag for notifying servers that the bot is unable to manipulate its role.
''' Can be set at any time. Reading this will only return True once every 10 minutes, if at all.
''' </summary> ''' </summary>
Public Property RoleWarning As Boolean Public Property RoleWarning As Boolean
Get
If _roleWarning = True Then
' Only report a warning every so often.
If DateTimeOffset.UtcNow - _roleLastWarning > RoleWarningInterval Then
_roleLastWarning = DateTimeOffset.UtcNow
Return True
Else
Return False
End If
End If
Return False
End Get
Set(value As Boolean)
_roleWarning = value
End Set
End Property
''' <summary> ''' <summary>
''' Gets a list of cached users. Use sparingly. ''' Gets a list of cached users. Use sparingly.
@ -67,14 +88,10 @@ Friend Class GuildSettings
''' Gets or sets if the server is in moderated mode. ''' Gets or sets if the server is in moderated mode.
''' Updating this value updates the database. ''' Updating this value updates the database.
''' </summary> ''' </summary>
Public Property IsModerated As Boolean Public ReadOnly Property IsModerated As Boolean
Get Get
Return _modded Return _modded
End Get End Get
Set(value As Boolean)
_modded = value
UpdateDatabaseAsync()
End Set
End Property End Property
' Called by LoadSettingsAsync. Double-check ordinals when changes are made. ' Called by LoadSettingsAsync. Double-check ordinals when changes are made.
@ -132,10 +149,11 @@ Friend Class GuildSettings
End Function End Function
''' <summary> ''' <summary>
''' Checks if the given user is banned from issuing commands. ''' Checks if the given user is blocked from issuing commands.
''' If the server is in moderated mode, this always returns True. ''' If the server is in moderated mode, this always returns True.
''' Does not check if the user is a manager.
''' </summary> ''' </summary>
Public Async Function IsUserBannedAsync(userId As ULong) As Task(Of Boolean) Public Async Function IsUserBlockedAsync(userId As ULong) As Task(Of Boolean)
If IsModerated Then Return True If IsModerated Then Return True
Using db = Await _db.OpenConnectionAsync() Using db = Await _db.OpenConnectionAsync()
@ -154,10 +172,9 @@ Friend Class GuildSettings
End Function End Function
''' <summary> ''' <summary>
''' Bans the specified user from issuing commands. ''' Blocks the specified user from issuing commands.
''' Does not check if the given user is already banned.
''' </summary> ''' </summary>
Public Async Function BanUserAsync(userId As ULong) As Task Public Async Function BlockUserAsync(userId As ULong) As Task
Using db = Await _db.OpenConnectionAsync() Using db = Await _db.OpenConnectionAsync()
Using c = db.CreateCommand() Using c = db.CreateCommand()
c.CommandText = $"insert into {BackingTableBans} (guild_id, user_id) " + c.CommandText = $"insert into {BackingTableBans} (guild_id, user_id) " +
@ -172,8 +189,7 @@ Friend Class GuildSettings
End Function End Function
''' <summary> ''' <summary>
''' Removes the specified user from the ban list. ''' Removes the specified user from the block list.
''' Does not check if the given user was not banned to begin with.
''' </summary> ''' </summary>
Public Async Function UnbanUserAsync(userId As ULong) As Task Public Async Function UnbanUserAsync(userId As ULong) As Task
Using db = Await _db.OpenConnectionAsync() Using db = Await _db.OpenConnectionAsync()
@ -204,6 +220,11 @@ Friend Class GuildSettings
Await UpdateDatabaseAsync() Await UpdateDatabaseAsync()
End Function End Function
Public Async Function UpdateModeratedModeAsync(isModerated As Boolean) As Task
_modded = isModerated
Await UpdateDatabaseAsync()
End Function
#Region "Database" #Region "Database"
Public Const BackingTable = "settings" Public Const BackingTable = "settings"
Public Const BackingTableBans = "banned_users" Public Const BackingTableBans = "banned_users"

View file

@ -10,6 +10,7 @@ Imports NodaTime
Friend MustInherit Class CommandsCommon Friend MustInherit Class CommandsCommon
Public Const CommandPrefix = "bb." Public Const CommandPrefix = "bb."
Public Const GenericError = ":x: Invalid usage. Consult the help command." Public Const GenericError = ":x: Invalid usage. Consult the help command."
Public Const BadUserError = ":x: Unable to find user. Specify their `@` mention or their ID."
Public Const ExpectedNoParametersError = ":x: This command does not take parameters. Did you mean to use another?" Public Const ExpectedNoParametersError = ":x: This command does not take parameters. Did you mean to use another?"
Delegate Function CommandHandler(param As String(), reqChannel As SocketTextChannel, reqUser As SocketGuildUser) As Task Delegate Function CommandHandler(param As String(), reqChannel As SocketTextChannel, reqUser As SocketGuildUser) As Task
@ -42,6 +43,11 @@ Friend MustInherit Class CommandsCommon
Discord = inst.DiscordClient Discord = inst.DiscordClient
End Sub End Sub
''' <summary>
''' On command dispatcher initialization, it will retrieve all available commands through here.
''' </summary>
Public MustOverride ReadOnly Property Commands As IEnumerable(Of (String, CommandHandler))
''' <summary> ''' <summary>
''' Checks given time zone input. Returns a valid string for use with NodaTime. ''' Checks given time zone input. Returns a valid string for use with NodaTime.
''' </summary> ''' </summary>
@ -59,7 +65,24 @@ Friend MustInherit Class CommandsCommon
End Function End Function
''' <summary> ''' <summary>
''' On command dispatcher initialization, it will retrieve all available commands through here. ''' Given user input where a user-like parameter is expected, attempts to resolve to an ID value.
''' Input must be a mention or explicit ID. No name resolution is done here.
''' </summary> ''' </summary>
Public MustOverride ReadOnly Property Commands As IEnumerable(Of (String, CommandHandler)) Protected Function TryGetUserId(input As String, ByRef result As ULong) As Boolean
Dim doParse As String
Dim m = UserMention.Match(input)
If m.Success Then
doParse = m.Groups(1).Value
Else
doParse = input
End If
Dim resultVal As ULong
If ULong.TryParse(doParse, resultVal) Then
result = resultVal
Return True
End If
Return False
End Function
End Class End Class

View file

@ -3,7 +3,7 @@ Option Explicit On
Imports Discord Imports Discord
Imports Discord.WebSocket Imports Discord.WebSocket
Friend Class HelpCommands Friend Class HelpInfoCommands
Inherits CommandsCommon Inherits CommandsCommon
Private ReadOnly _helpEmbed As Embed Private ReadOnly _helpEmbed As Embed
@ -25,28 +25,20 @@ Friend Class HelpCommands
End Property End Property
Private Function CreateHelpEmbed() As (EmbedBuilder, EmbedBuilder) Private Function CreateHelpEmbed() As (EmbedBuilder, EmbedBuilder)
Dim title = "Help & About"
Dim description = "Birthday Bot: A utility to assist with acknowledging birthdays and other annual events." + vbLf +
"**Currently a work in progress. There will be bugs. Features may change or be removed.**"
Dim footer As New EmbedFooterBuilder With {
.Text = Discord.CurrentUser.Username,
.IconUrl = Discord.CurrentUser.GetAvatarUrl()
}
Dim cpfx = $"●`{CommandPrefix}" Dim cpfx = $"●`{CommandPrefix}"
' Normal section ' Normal section
Dim cmdField As New EmbedFieldBuilder With { Dim cmdField As New EmbedFieldBuilder With {
.Name = "Commands", .Name = "Commands",
.Value = .Value =
$"{cpfx}help`, `{CommandPrefix}info`, `{CommandPrefix}tzdata`" + vbLf + $"{cpfx}help`, `{CommandPrefix}info`, `{CommandPrefix}help-tzdata`" + vbLf +
$" » Various help messages." + vbLf + $" » Various help and informational messages." + vbLf +
$"{cpfx}set (date) [zone]`" + vbLf + $"{cpfx}set (date) [zone]`" + vbLf +
$" » Registers your birth date, with optional time zone." + vbLf + $" » Registers your birth date. Time zone is optional." + vbLf +
$" »» Examples: `{CommandPrefix}set jan-31 America/New_York`, `{CommandPrefix}set 15-aug Europe/Stockholm`." + vbLf + $" »» Examples: `{CommandPrefix}set jan-31`, `{CommandPrefix}set 15-aug America/Los_Angeles`." + vbLf +
$"{cpfx}set-tz (zone)`" + vbLf + $"{cpfx}zone (zone)`" + vbLf +
$" » Sets your local time zone. Only accepts certain values. See `{CommandPrefix}tzdata`." + vbLf + $" » Sets your local time zone. See `{CommandPrefix}help-tzdata`." + vbLf +
$"{cpfx}remove`" + vbLf + $"{cpfx}remove`" + vbLf +
$" » Removes all your information from this bot." $" » Removes your information from this bot."
} }
' Manager section ' Manager section
@ -55,36 +47,27 @@ Friend Class HelpCommands
.Name = "Commands for server managers", .Name = "Commands for server managers",
.Value = .Value =
$"{mpfx}role (role name or ID)`" + vbLf + $"{mpfx}role (role name or ID)`" + vbLf +
" » Specifies which role to apply to users having birthdays." + vbLf + " » Configures the role to apply to users having birthdays." + vbLf +
$"{mpfx}channel (channel name or ID)`" + vbLf + $"{mpfx}channel (channel name or ID)`" + vbLf +
" » Sets the birthday and event announcement channel. Leave blank to disable announcements." + vbLf + " » Configures the channel to use for announcements. Leave blank to disable." + vbLf +
$"{mpfx}set-tz (time zone name)`" + vbLf + $"{mpfx}zone (time zone name)`" + vbLf +
" » Sets the default time zone to use with all dates. Leave blank to revert to default." + vbLf + " » Sets the default time zone for all dates that don't have their own zone set." + vbLf +
$" » Only accepts certain values. See `{CommandPrefix}tzdata`." + vbLf + $" »» See `{CommandPrefix}help-tzdata`. Leave blank to set to UTC." + vbLf +
$"{mpfx}ban/unban (user mention or ID)`" + vbLf + $"{mpfx}block/unblock (user mention or ID)`" + vbLf +
" » Restricts or reallows access to this bot for the given user." + vbLf + " » Prevents or allows usage of bot commands to the given user." + vbLf +
$"{mpfx}ban-all/unban-all`" + vbLf + $"{mpfx}moderated on/off`" + vbLf +
" » Restricts or reallows access to this bot for all users. Server managers are exempt." + vbLf + " » Prevents or allows usage of bot commands to all users excluding managers." + vbLf +
$"{cpfx}override (user ID) (regular command)`" + vbLf + $"{cpfx}override (user mention or ID) (command)`" + vbLf +
" » Performs a command on behalf of the given user." " » Performs a command on behalf of the given user." + vbLf +
" »» Command may be either `set`, `zone`, or `remove` plus appropriate parameters."
} }
Dim helpNoManager As New EmbedBuilder Dim helpNoManager As New EmbedBuilder
With helpNoManager helpNoManager.AddField(cmdField)
.Footer = footer
.Title = title
.Description = description
.AddField(cmdField)
End With
Dim helpManager As New EmbedBuilder Dim helpManager As New EmbedBuilder
With helpManager helpManager.AddField(cmdField)
.Footer = footer helpManager.AddField(managerField)
.Title = title
.Description = description
.AddField(cmdField)
.AddField(managerField)
End With
Return (helpNoManager, helpManager) Return (helpNoManager, helpManager)
End Function End Function

View file

@ -22,17 +22,24 @@ Friend Class ManagerCommands
_subcommands = New Dictionary(Of String, ConfigSubcommand) From { _subcommands = New Dictionary(Of String, ConfigSubcommand) From {
{"role", AddressOf ScmdRole}, {"role", AddressOf ScmdRole},
{"channel", AddressOf ScmdChannel}, {"channel", AddressOf ScmdChannel},
{"set-tz", AddressOf ScmdSetTz}, {"zone", AddressOf ScmdZone},
{"ban", AddressOf ScmdBanUnban}, {"block", AddressOf ScmdBlock},
{"unban", AddressOf ScmdBanUnban}, {"unblock", AddressOf ScmdBlock},
{"ban-all", AddressOf ScmdSetModerated}, {"moderated", AddressOf ScmdModerated}
{"unban-all", AddressOf ScmdSetModerated}
} }
End Sub End Sub
Private Async Function CmdConfigDispatch(param As String(), reqChannel As SocketTextChannel, reqUser As SocketGuildUser) As Task Private Async Function CmdConfigDispatch(param As String(), reqChannel As SocketTextChannel, reqUser As SocketGuildUser) As Task
' Managers only past this point. (This may have already been checked.) ' Managers only past this point.
If Not reqUser.GuildPermissions.ManageGuild Then Return If Not reqUser.GuildPermissions.ManageGuild Then
Await reqChannel.SendMessageAsync(":x: This command may only be used by those with the `Manage Server` permission.")
Return
End If
If param.Length <> 3 Then
Await reqChannel.SendMessageAsync(GenericError)
Return
End If
' Subcommands get a subset of the parameters, to make things a little easier. ' Subcommands get a subset of the parameters, to make things a little easier.
Dim confparam(param.Length - 2) As String ' subtract 2??? Dim confparam(param.Length - 2) As String ' subtract 2???
@ -137,7 +144,7 @@ Friend Class ManagerCommands
End Function End Function
' Guild default time zone set/unset ' Guild default time zone set/unset
Private Async Function ScmdSetTz(param As String(), reqChannel As SocketTextChannel) As Task Private Async Function ScmdZone(param As String(), reqChannel As SocketTextChannel) As Task
If param.Length = 1 Then If param.Length = 1 Then
' No extra parameter. Unset guild default time zone. ' No extra parameter. Unset guild default time zone.
SyncLock Instance.KnownGuilds SyncLock Instance.KnownGuilds
@ -175,49 +182,73 @@ Friend Class ManagerCommands
End Function End Function
' Block/unblock individual non-manager users from using commands. ' Block/unblock individual non-manager users from using commands.
Private Async Function ScmdBanUnban(param As String(), reqChannel As SocketTextChannel) As Task Private Async Function ScmdBlock(param As String(), reqChannel As SocketTextChannel) As Task
If param.Length <> 2 Then If param.Length <> 2 Then
Await reqChannel.SendMessageAsync(GenericError) Await reqChannel.SendMessageAsync(GenericError)
Return Return
End If End If
Dim doBan As Boolean = param(0).ToLower() = "ban" ' True = ban, False = unban Dim doBan As Boolean = param(0).ToLower() = "block" ' True = block, False = unblock
' Parameter must be a mention or explicit ID. No name resolution.
Dim input = param(1)
Dim m = UserMention.Match(param(1))
If m.Success Then input = m.Groups(1).Value
Dim inputId As ULong Dim inputId As ULong
If Not ULong.TryParse(input, inputId) Then If Not TryGetUserId(param(1), inputId) Then
Await reqChannel.SendMessageAsync(":x: Unable to find user. Specify their `@` mention or their ID.") Await reqChannel.SendMessageAsync(BadUserError)
Return Return
End If End If
SyncLock Instance.KnownGuilds SyncLock Instance.KnownGuilds
Dim gi = Instance.KnownGuilds(reqChannel.Guild.Id) Dim gi = Instance.KnownGuilds(reqChannel.Guild.Id)
Dim isBanned = gi.IsUserBannedAsync(inputId).GetAwaiter().GetResult() Dim isBanned = gi.IsUserBlockedAsync(inputId).GetAwaiter().GetResult()
If doBan Then If doBan Then
If Not isBanned Then If Not isBanned Then
gi.BanUserAsync(inputId).Wait() gi.BlockUserAsync(inputId).Wait()
reqChannel.SendMessageAsync(":white_check_mark: User has been banned from using the bot").Wait() reqChannel.SendMessageAsync(":white_check_mark: User has been blocked.").Wait()
Else Else
reqChannel.SendMessageAsync(":white_check_mark: The specified user is already banned.").Wait() reqChannel.SendMessageAsync(":white_check_mark: User is already blocked.").Wait()
End If End If
Else Else
If isBanned Then If isBanned Then
gi.UnbanUserAsync(inputId).Wait() gi.UnbanUserAsync(inputId).Wait()
reqChannel.SendMessageAsync(":white_check_mark: User may now use the bot").Wait() reqChannel.SendMessageAsync(":white_check_mark: User is now unblocked.").Wait()
Else Else
reqChannel.SendMessageAsync(":white_check_mark: The specified user is not banned.").Wait() reqChannel.SendMessageAsync(":white_check_mark: The specified user has not been blocked.").Wait()
End If End If
End If End If
End SyncLock End SyncLock
End Function End Function
' "ban/unban all" - Sets/unsets moderated mode. ' "moderated on/off" - Sets/unsets moderated mode.
Private Async Function ScmdSetModerated(param As String(), reqChannel As SocketTextChannel) As Task Private Async Function ScmdModerated(param As String(), reqChannel As SocketTextChannel) As Task
Throw New NotImplementedException() If param.Length <> 2 Then
Await reqChannel.SendMessageAsync(GenericError)
Return
End If
Dim parameter = param(1).ToLower()
Dim modSet As Boolean
If parameter = "on" Then
modSet = True
ElseIf parameter = "off" Then
modSet = False
Else
Await reqChannel.SendMessageAsync(GenericError)
Return
End If
Dim currentSet As Boolean
SyncLock Instance.KnownGuilds
Dim gi = Instance.KnownGuilds(reqChannel.Guild.Id)
currentSet = gi.IsModerated
gi.UpdateModeratedModeAsync(modSet).Wait()
End SyncLock
If currentSet = modSet Then
Await reqChannel.SendMessageAsync($":white_check_mark: Moderated mode is already {parameter}.")
Else
Await reqChannel.SendMessageAsync($":white_check_mark: Moderated mode has been turned {parameter}.")
End If
End Function End Function
#End Region #End Region

View file

@ -11,7 +11,7 @@ Class UserCommands
Get Get
Return New List(Of (String, CommandHandler)) From { Return New List(Of (String, CommandHandler)) From {
("set", AddressOf CmdSet), ("set", AddressOf CmdSet),
("set-tz", AddressOf CmdSetTz), ("zone", AddressOf CmdZone),
("remove", AddressOf CmdRemove) ("remove", AddressOf CmdRemove)
} }
End Get End Get
@ -124,7 +124,7 @@ Class UserCommands
End If End If
End Function End Function
Private Async Function CmdSetTz(param As String(), reqChannel As SocketTextChannel, reqUser As SocketGuildUser) As Task Private Async Function CmdZone(param As String(), reqChannel As SocketTextChannel, reqUser As SocketGuildUser) As Task
If param.Count <> 2 Then If param.Count <> 2 Then
Await reqChannel.SendMessageAsync(GenericError) Await reqChannel.SendMessageAsync(GenericError)
Return Return