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
Imports BirthdayBot.CommandsCommon
Imports Discord
Imports Discord.Net
Imports Discord.WebSocket
Class BirthdayBot
Const RoleWarningMsg As String =
"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 _cmdsUser As UserCommands
Private ReadOnly _cmdsHelp As HelpCommands
Private ReadOnly _cmdsHelp As HelpInfoCommands
Private ReadOnly _cmdsMods As ManagerCommands
Private WithEvents _client As DiscordSocketClient
@ -40,7 +41,7 @@ Class BirthdayBot
For Each item In _cmdsUser.Commands
_dispatchCommands.Add(item.Item1, item.Item2)
Next
_cmdsHelp = New HelpCommands(Me, conf)
_cmdsHelp = New HelpInfoCommands(Me, conf)
For Each item In _cmdsHelp.Commands
_dispatchCommands.Add(item.Item1, item.Item2)
Next
@ -102,15 +103,22 @@ Class BirthdayBot
Dim channel = CType(msg.Channel, SocketTextChannel)
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
Dim roleWarning As Boolean
Dim isManager = author.GuildPermissions.ManageGuild
SyncLock KnownGuilds
Dim gi = KnownGuilds(channel.Guild.Id)
' Skip ban check if user is a manager
If Not author.GuildPermissions.ManageGuild Then
If gi.IsUserBannedAsync(author.Id).GetAwaiter().GetResult() Then
If Not isManager Then
If gi.IsUserBlockedAsync(author.Id).GetAwaiter().GetResult() Then
Return
End If
End If
@ -118,18 +126,24 @@ Class BirthdayBot
roleWarning = gi.RoleWarning
End SyncLock
Dim h As CommandHandler = Nothing
If _dispatchCommands.TryGetValue(csplit(0).Substring(CommandPrefix.Length), h) Then
Try
Await h(csplit, channel, author)
If roleWarning Then
Try
If roleWarning Then
Try
Await channel.SendMessageAsync(RoleWarningMsg)
End If
Catch ex As Exception
Catch ex As HttpException
' 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()
Log("Error", ex.ToString())
Catch ex2 As HttpException
' Fail silently.
End Try
End If
End Try
End If
End If
End Function

View file

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

View file

@ -18,10 +18,31 @@ Friend Class GuildSettings
Private _modded As Boolean
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>
''' 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>
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>
''' 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.
''' Updating this value updates the database.
''' </summary>
Public Property IsModerated As Boolean
Public ReadOnly Property IsModerated As Boolean
Get
Return _modded
End Get
Set(value As Boolean)
_modded = value
UpdateDatabaseAsync()
End Set
End Property
' Called by LoadSettingsAsync. Double-check ordinals when changes are made.
@ -132,10 +149,11 @@ Friend Class GuildSettings
End Function
''' <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.
''' Does not check if the user is a manager.
''' </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
Using db = Await _db.OpenConnectionAsync()
@ -154,10 +172,9 @@ Friend Class GuildSettings
End Function
''' <summary>
''' Bans the specified user from issuing commands.
''' Does not check if the given user is already banned.
''' Blocks the specified user from issuing commands.
''' </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 c = db.CreateCommand()
c.CommandText = $"insert into {BackingTableBans} (guild_id, user_id) " +
@ -172,8 +189,7 @@ Friend Class GuildSettings
End Function
''' <summary>
''' Removes the specified user from the ban list.
''' Does not check if the given user was not banned to begin with.
''' Removes the specified user from the block list.
''' </summary>
Public Async Function UnbanUserAsync(userId As ULong) As Task
Using db = Await _db.OpenConnectionAsync()
@ -204,6 +220,11 @@ Friend Class GuildSettings
Await UpdateDatabaseAsync()
End Function
Public Async Function UpdateModeratedModeAsync(isModerated As Boolean) As Task
_modded = isModerated
Await UpdateDatabaseAsync()
End Function
#Region "Database"
Public Const BackingTable = "settings"
Public Const BackingTableBans = "banned_users"

View file

@ -10,6 +10,7 @@ Imports NodaTime
Friend MustInherit Class CommandsCommon
Public Const CommandPrefix = "bb."
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?"
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
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>
''' Checks given time zone input. Returns a valid string for use with NodaTime.
''' </summary>
@ -59,7 +65,24 @@ Friend MustInherit Class CommandsCommon
End Function
''' <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>
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

View file

@ -3,7 +3,7 @@ Option Explicit On
Imports Discord
Imports Discord.WebSocket
Friend Class HelpCommands
Friend Class HelpInfoCommands
Inherits CommandsCommon
Private ReadOnly _helpEmbed As Embed
@ -25,28 +25,20 @@ Friend Class HelpCommands
End Property
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}"
' Normal section
Dim cmdField As New EmbedFieldBuilder With {
.Name = "Commands",
.Value =
$"{cpfx}help`, `{CommandPrefix}info`, `{CommandPrefix}tzdata`" + vbLf +
$" » Various help messages." + vbLf +
$"{cpfx}help`, `{CommandPrefix}info`, `{CommandPrefix}help-tzdata`" + vbLf +
$" » Various help and informational messages." + vbLf +
$"{cpfx}set (date) [zone]`" + vbLf +
$" » Registers your birth date, with optional time zone." + vbLf +
$" »» Examples: `{CommandPrefix}set jan-31 America/New_York`, `{CommandPrefix}set 15-aug Europe/Stockholm`." + vbLf +
$"{cpfx}set-tz (zone)`" + vbLf +
$" » Sets your local time zone. Only accepts certain values. See `{CommandPrefix}tzdata`." + vbLf +
$" » Registers your birth date. Time zone is optional." + vbLf +
$" »» Examples: `{CommandPrefix}set jan-31`, `{CommandPrefix}set 15-aug America/Los_Angeles`." + vbLf +
$"{cpfx}zone (zone)`" + vbLf +
$" » Sets your local time zone. See `{CommandPrefix}help-tzdata`." + vbLf +
$"{cpfx}remove`" + vbLf +
$" » Removes all your information from this bot."
$" » Removes your information from this bot."
}
' Manager section
@ -55,36 +47,27 @@ Friend Class HelpCommands
.Name = "Commands for server managers",
.Value =
$"{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 +
" » Sets the birthday and event announcement channel. Leave blank to disable announcements." + vbLf +
$"{mpfx}set-tz (time zone name)`" + vbLf +
" » Sets the default time zone to use with all dates. Leave blank to revert to default." + vbLf +
$" » Only accepts certain values. See `{CommandPrefix}tzdata`." + vbLf +
$"{mpfx}ban/unban (user mention or ID)`" + vbLf +
" » Restricts or reallows access to this bot for the given user." + vbLf +
$"{mpfx}ban-all/unban-all`" + vbLf +
" » Restricts or reallows access to this bot for all users. Server managers are exempt." + vbLf +
$"{cpfx}override (user ID) (regular command)`" + vbLf +
" » Performs a command on behalf of the given user."
" » Configures the channel to use for announcements. Leave blank to disable." + vbLf +
$"{mpfx}zone (time zone name)`" + vbLf +
" » Sets the default time zone for all dates that don't have their own zone set." + vbLf +
$" »» See `{CommandPrefix}help-tzdata`. Leave blank to set to UTC." + vbLf +
$"{mpfx}block/unblock (user mention or ID)`" + vbLf +
" » Prevents or allows usage of bot commands to the given user." + vbLf +
$"{mpfx}moderated on/off`" + vbLf +
" » Prevents or allows usage of bot commands to all users excluding managers." + vbLf +
$"{cpfx}override (user mention or ID) (command)`" + vbLf +
" » 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
With helpNoManager
.Footer = footer
.Title = title
.Description = description
.AddField(cmdField)
End With
helpNoManager.AddField(cmdField)
Dim helpManager As New EmbedBuilder
With helpManager
.Footer = footer
.Title = title
.Description = description
.AddField(cmdField)
.AddField(managerField)
End With
helpManager.AddField(cmdField)
helpManager.AddField(managerField)
Return (helpNoManager, helpManager)
End Function

View file

@ -22,17 +22,24 @@ Friend Class ManagerCommands
_subcommands = New Dictionary(Of String, ConfigSubcommand) From {
{"role", AddressOf ScmdRole},
{"channel", AddressOf ScmdChannel},
{"set-tz", AddressOf ScmdSetTz},
{"ban", AddressOf ScmdBanUnban},
{"unban", AddressOf ScmdBanUnban},
{"ban-all", AddressOf ScmdSetModerated},
{"unban-all", AddressOf ScmdSetModerated}
{"zone", AddressOf ScmdZone},
{"block", AddressOf ScmdBlock},
{"unblock", AddressOf ScmdBlock},
{"moderated", AddressOf ScmdModerated}
}
End Sub
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.)
If Not reqUser.GuildPermissions.ManageGuild Then Return
' Managers only past this point.
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.
Dim confparam(param.Length - 2) As String ' subtract 2???
@ -137,7 +144,7 @@ Friend Class ManagerCommands
End Function
' 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
' No extra parameter. Unset guild default time zone.
SyncLock Instance.KnownGuilds
@ -175,49 +182,73 @@ Friend Class ManagerCommands
End Function
' 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
Await reqChannel.SendMessageAsync(GenericError)
Return
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
If Not ULong.TryParse(input, inputId) Then
Await reqChannel.SendMessageAsync(":x: Unable to find user. Specify their `@` mention or their ID.")
If Not TryGetUserId(param(1), inputId) Then
Await reqChannel.SendMessageAsync(BadUserError)
Return
End If
SyncLock Instance.KnownGuilds
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 Not isBanned Then
gi.BanUserAsync(inputId).Wait()
reqChannel.SendMessageAsync(":white_check_mark: User has been banned from using the bot").Wait()
gi.BlockUserAsync(inputId).Wait()
reqChannel.SendMessageAsync(":white_check_mark: User has been blocked.").Wait()
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
Else
If isBanned Then
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
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 SyncLock
End Function
' "ban/unban all" - Sets/unsets moderated mode.
Private Async Function ScmdSetModerated(param As String(), reqChannel As SocketTextChannel) As Task
Throw New NotImplementedException()
' "moderated on/off" - Sets/unsets moderated mode.
Private Async Function ScmdModerated(param As String(), reqChannel As SocketTextChannel) As Task
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 Region

View file

@ -11,7 +11,7 @@ Class UserCommands
Get
Return New List(Of (String, CommandHandler)) From {
("set", AddressOf CmdSet),
("set-tz", AddressOf CmdSetTz),
("zone", AddressOf CmdZone),
("remove", AddressOf CmdRemove)
}
End Get
@ -124,7 +124,7 @@ Class UserCommands
End If
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
Await reqChannel.SendMessageAsync(GenericError)
Return