diff --git a/BirthdayBot/BirthdayBot.vb b/BirthdayBot/BirthdayBot.vb
index e76c903..6da6a2c 100644
--- a/BirthdayBot/BirthdayBot.vb
+++ b/BirthdayBot/BirthdayBot.vb
@@ -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
diff --git a/BirthdayBot/BirthdayBot.vbproj b/BirthdayBot/BirthdayBot.vbproj
index 1e95a0b..4cefe83 100644
--- a/BirthdayBot/BirthdayBot.vbproj
+++ b/BirthdayBot/BirthdayBot.vbproj
@@ -4,8 +4,8 @@
Exe
BirthdayBot
netcoreapp2.0
- 0.1.0
- 0.1.0.0
+ 0.3.0
+ 0.3.0.0
Noikoio
Discord bot for birthday reminders.
diff --git a/BirthdayBot/Data/GuildSettings.vb b/BirthdayBot/Data/GuildSettings.vb
index 3c780e5..9585639 100644
--- a/BirthdayBot/Data/GuildSettings.vb
+++ b/BirthdayBot/Data/GuildSettings.vb
@@ -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)
+
'''
''' 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.
'''
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
'''
''' 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.
'''
- 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
'''
- ''' 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.
'''
- 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
'''
- ''' Bans the specified user from issuing commands.
- ''' Does not check if the given user is already banned.
+ ''' Blocks the specified user from issuing commands.
'''
- 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
'''
- ''' 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.
'''
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"
diff --git a/BirthdayBot/UserInterface/CommandsCommon.vb b/BirthdayBot/UserInterface/CommandsCommon.vb
index 1d2082e..ae8282c 100644
--- a/BirthdayBot/UserInterface/CommandsCommon.vb
+++ b/BirthdayBot/UserInterface/CommandsCommon.vb
@@ -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
+ '''
+ ''' On command dispatcher initialization, it will retrieve all available commands through here.
+ '''
+ Public MustOverride ReadOnly Property Commands As IEnumerable(Of (String, CommandHandler))
+
'''
''' Checks given time zone input. Returns a valid string for use with NodaTime.
'''
@@ -59,7 +65,24 @@ Friend MustInherit Class CommandsCommon
End Function
'''
- ''' 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.
'''
- 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
diff --git a/BirthdayBot/UserInterface/HelpCommands.vb b/BirthdayBot/UserInterface/HelpInfoCommands.vb
similarity index 51%
rename from BirthdayBot/UserInterface/HelpCommands.vb
rename to BirthdayBot/UserInterface/HelpInfoCommands.vb
index 6adb731..9afd35b 100644
--- a/BirthdayBot/UserInterface/HelpCommands.vb
+++ b/BirthdayBot/UserInterface/HelpInfoCommands.vb
@@ -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
diff --git a/BirthdayBot/UserInterface/ManagerCommands.vb b/BirthdayBot/UserInterface/ManagerCommands.vb
index 4d430c3..794bf43 100644
--- a/BirthdayBot/UserInterface/ManagerCommands.vb
+++ b/BirthdayBot/UserInterface/ManagerCommands.vb
@@ -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
diff --git a/BirthdayBot/UserInterface/UserCommands.vb b/BirthdayBot/UserInterface/UserCommands.vb
index ad6b731..c581197 100644
--- a/BirthdayBot/UserInterface/UserCommands.vb
+++ b/BirthdayBot/UserInterface/UserCommands.vb
@@ -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