Added bot moderator role option

This commit is contained in:
Noikoio 2019-05-03 20:34:04 -07:00
parent 11ceadfb36
commit 2c8a283c5b
5 changed files with 130 additions and 65 deletions

View file

@ -110,12 +110,11 @@ Class BirthdayBot
' Ban and role warning check ' Ban and role warning check
Dim roleWarning As Boolean 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 isManager Then If Not gi.IsUserModerator(author) Then
If gi.IsUserBlockedAsync(author.Id).GetAwaiter().GetResult() Then If gi.IsUserBlockedAsync(author.Id).GetAwaiter().GetResult() Then
Return Return
End If End If

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.5.3</Version> <Version>0.6.0</Version>
<AssemblyVersion>0.5.3.0</AssemblyVersion> <AssemblyVersion>0.6.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

@ -1,4 +1,5 @@
Imports System.Data.Common Imports System.Data.Common
Imports Discord.WebSocket
Imports Npgsql Imports Npgsql
Imports NpgsqlTypes Imports NpgsqlTypes
@ -12,6 +13,7 @@ Friend Class GuildSettings
Private ReadOnly _db As Database Private ReadOnly _db As Database
Private _bdayRole As ULong? Private _bdayRole As ULong?
Private _announceCh As ULong? Private _announceCh As ULong?
Private _modRole As ULong?
Private _tz As String Private _tz As String
Private _moderated As Boolean Private _moderated As Boolean
Private _userCache As Dictionary(Of ULong, GuildUserSettings) Private _userCache As Dictionary(Of ULong, GuildUserSettings)
@ -84,8 +86,7 @@ Friend Class GuildSettings
End Property End Property
''' <summary> ''' <summary>
''' Gets or sets if the server is in moderated mode. ''' Gets whether the guild is in moderated mode.
''' Updating this value updates the database.
''' </summary> ''' </summary>
Public ReadOnly Property IsModerated As Boolean Public ReadOnly Property IsModerated As Boolean
Get Get
@ -93,6 +94,15 @@ Friend Class GuildSettings
End Get End Get
End Property End Property
''' <summary>
''' Gets the designated moderator role ID.
''' </summary>
Public ReadOnly Property ModeratorRole As ULong?
Get
Return _modRole
End Get
End Property
' Called by LoadSettingsAsync. Double-check ordinals when changes are made. ' Called by LoadSettingsAsync. Double-check ordinals when changes are made.
Private Sub New(reader As DbDataReader, dbconfig As Database) Private Sub New(reader As DbDataReader, dbconfig As Database)
_db = dbconfig _db = dbconfig
@ -107,6 +117,7 @@ Friend Class GuildSettings
If Not reader.IsDBNull(2) Then _announceCh = CULng(reader.GetInt64(2)) If Not reader.IsDBNull(2) Then _announceCh = CULng(reader.GetInt64(2))
_tz = If(reader.IsDBNull(3), Nothing, reader.GetString(3)) _tz = If(reader.IsDBNull(3), Nothing, reader.GetString(3))
_moderated = reader.GetBoolean(4) _moderated = reader.GetBoolean(4)
If Not reader.IsDBNull(5) Then _modRole = CULng(reader.GetInt64(5))
' Get user information loaded up. ' Get user information loaded up.
Dim userresult = GuildUserSettings.GetGuildUsersAsync(dbconfig, GuildId) Dim userresult = GuildUserSettings.GetGuildUsersAsync(dbconfig, GuildId)
@ -169,6 +180,19 @@ Friend Class GuildSettings
End Using End Using
End Function End Function
''' <summary>
''' Checks if the given user is a moderator either by having the Manage Server permission or
''' being in the designated modeartor role.
''' </summary>
Public Function IsUserModerator(user As SocketGuildUser) As Boolean
If user.GuildPermissions.ManageGuild Then Return True
If ModeratorRole.HasValue Then
If user.Roles.Where(Function(r) r.Id = ModeratorRole.Value).Count > 0 Then Return True
End If
IsUserModerator = False
End Function
''' <summary> ''' <summary>
''' Adds the specified user to the block list, preventing them from issuing commands. ''' Adds the specified user to the block list, preventing them from issuing commands.
''' </summary> ''' </summary>
@ -224,6 +248,11 @@ Friend Class GuildSettings
Await UpdateDatabaseAsync() Await UpdateDatabaseAsync()
End Function End Function
Public Async Function UpdateModeratorRoleAsync(roleId As ULong?) As Task
_modRole = roleId
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"
@ -235,7 +264,8 @@ Friend Class GuildSettings
"role_id bigint null, " + "role_id bigint null, " +
"channel_announce_id bigint null, " + "channel_announce_id bigint null, " +
"time_zone text null, " + "time_zone text null, " +
"moderated boolean not null default FALSE" + "moderated boolean not null default FALSE, " +
"moderator_role bigint null" +
")" ")"
c.ExecuteNonQuery() c.ExecuteNonQuery()
End Using End Using
@ -257,7 +287,7 @@ Friend Class GuildSettings
Using db = Await dbsettings.OpenConnectionAsync() Using db = Await dbsettings.OpenConnectionAsync()
Using c = db.CreateCommand() Using c = db.CreateCommand()
' Take note of ordinals for use in the constructor ' Take note of ordinals for use in the constructor
c.CommandText = "select guild_id, role_id, channel_announce_id, time_zone, moderated " + c.CommandText = "select guild_id, role_id, channel_announce_id, time_zone, moderated, moderator_role " +
$"from {BackingTable} where guild_id = @Gid" $"from {BackingTable} where guild_id = @Gid"
c.Parameters.Add("@Gid", NpgsqlDbType.Bigint).Value = guild c.Parameters.Add("@Gid", NpgsqlDbType.Bigint).Value = guild
c.Prepare() c.Prepare()
@ -291,7 +321,8 @@ Friend Class GuildSettings
"role_id = @RoleId, " + "role_id = @RoleId, " +
"channel_announce_id = @ChannelId, " + "channel_announce_id = @ChannelId, " +
"time_zone = @TimeZone, " + "time_zone = @TimeZone, " +
"moderated = @Moderated " + "moderated = @Moderated, " +
"moderator_role = @ModRole " +
"where guild_id = @Gid" "where guild_id = @Gid"
c.Parameters.Add("@Gid", NpgsqlDbType.Bigint).Value = GuildId c.Parameters.Add("@Gid", NpgsqlDbType.Bigint).Value = GuildId
With c.Parameters.Add("@RoleId", NpgsqlDbType.Bigint) With c.Parameters.Add("@RoleId", NpgsqlDbType.Bigint)
@ -316,6 +347,13 @@ Friend Class GuildSettings
End If End If
End With End With
c.Parameters.Add("@Moderated", NpgsqlDbType.Boolean).Value = _moderated c.Parameters.Add("@Moderated", NpgsqlDbType.Boolean).Value = _moderated
With c.Parameters.Add("@ModRole", NpgsqlDbType.Bigint)
If ModeratorRole.HasValue Then
.Value = ModeratorRole.Value
Else
.Value = DBNull.Value
End If
End With
c.Prepare() c.Prepare()
Await c.ExecuteNonQueryAsync() Await c.ExecuteNonQueryAsync()
End Using End Using

View file

@ -43,13 +43,15 @@ Friend Class HelpInfoCommands
' Manager section ' Manager section
Dim mpfx = cpfx + "config " Dim mpfx = cpfx + "config "
Dim managerField As New EmbedFieldBuilder With { Dim moderatorField As New EmbedFieldBuilder With {
.Name = "Commands for server managers", .Name = "Commands for server managers and bot moderators",
.Value = .Value =
$"{mpfx}role (role name or ID)`" + vbLf + $"{mpfx}role (role name or ID)`" + vbLf +
" » Configures the 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 +
" » Configures the channel to use for announcements. Leave blank to disable." + vbLf + " » Configures the channel to use for announcements. Leave blank to disable." + vbLf +
$"{mpfx}modrole (role name or ID)`" + vbLf +
" » Sets the designated role for bot moderators. Moderators can access `bb.config` and `bb.override`." + vbLf +
$"{mpfx}zone (time zone name)`" + vbLf + $"{mpfx}zone (time zone name)`" + vbLf +
" » Sets the default time zone for all dates that don't have their own zone set." + 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 + $" »» See `{CommandPrefix}help-tzdata`. Leave blank to set to UTC." + vbLf +
@ -65,28 +67,21 @@ Friend Class HelpInfoCommands
Dim helpNoManager As New EmbedBuilder Dim helpNoManager As New EmbedBuilder
helpNoManager.AddField(cmdField) helpNoManager.AddField(cmdField)
Dim helpManager As New EmbedBuilder Dim helpModerator As New EmbedBuilder
helpManager.AddField(cmdField) helpModerator.AddField(cmdField)
helpManager.AddField(managerField) helpModerator.AddField(moderatorField)
Return (helpNoManager.Build(), helpManager.Build()) Return (helpNoManager.Build(), helpModerator.Build())
End Function End Function
Private Async Function CmdHelp(param As String(), reqChannel As SocketTextChannel, reqUser As SocketGuildUser) As Task Private Async Function CmdHelp(param As String(), reqChannel As SocketTextChannel, reqUser As SocketGuildUser) As Task
' Determine if an additional message about an invalid role should be added. ' Determine if the user asking is a moderator
Dim useFunctionMessage = False Dim showManagerCommands As Boolean
Dim gs As GuildSettings
SyncLock Instance.KnownGuilds SyncLock Instance.KnownGuilds
gs = Instance.KnownGuilds(reqChannel.Guild.Id) showManagerCommands = Instance.KnownGuilds(reqChannel.Guild.Id).IsUserModerator(reqUser)
End SyncLock End SyncLock
If Not gs.RoleId.HasValue Then
useFunctionMessage = True
End If
' Determine if the user asking is a manager Await reqChannel.SendMessageAsync(embed:=If(showManagerCommands, _helpEmbedManager, _helpEmbed))
Dim showManagerCommands = reqUser.GuildPermissions.ManageGuild
Await reqChannel.SendMessageAsync("", embed:=If(showManagerCommands, _helpEmbedManager, _helpEmbed))
End Function End Function
Private Async Function CmdHelpTzdata(param As String(), reqChannel As SocketTextChannel, reqUser As SocketGuildUser) As Task Private Async Function CmdHelpTzdata(param As String(), reqChannel As SocketTextChannel, reqUser As SocketGuildUser) As Task

View file

@ -22,6 +22,7 @@ Friend Class ManagerCommands
_subcommands = New Dictionary(Of String, ConfigSubcommand)(StringComparer.InvariantCultureIgnoreCase) From { _subcommands = New Dictionary(Of String, ConfigSubcommand)(StringComparer.InvariantCultureIgnoreCase) From {
{"role", AddressOf ScmdRole}, {"role", AddressOf ScmdRole},
{"channel", AddressOf ScmdChannel}, {"channel", AddressOf ScmdChannel},
{"modrole", AddressOf ScmdModRole},
{"zone", AddressOf ScmdZone}, {"zone", AddressOf ScmdZone},
{"block", AddressOf ScmdBlock}, {"block", AddressOf ScmdBlock},
{"unblock", AddressOf ScmdBlock}, {"unblock", AddressOf ScmdBlock},
@ -36,9 +37,14 @@ Friend Class ManagerCommands
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. ' Ignore those without the proper permissions.
If Not reqUser.GuildPermissions.ManageGuild Then ' Requires either the manage guild permission or to be in the moderators role
Await reqChannel.SendMessageAsync(":x: This command may only be used by those with the `Manage Server` permission.") Dim allowed As Boolean
SyncLock Instance.KnownGuilds
allowed = Instance.KnownGuilds(reqUser.Guild.Id).IsUserModerator(reqUser)
End SyncLock
If Not allowed Then
Await reqChannel.SendMessageAsync(":x: This command may only be used by bot moderators.")
Return Return
End If End If
@ -47,6 +53,12 @@ Friend Class ManagerCommands
Return Return
End If End If
' Special case: Restrict 'modrole' to only guild managers
If param(1).Equals("modrole", StringComparison.OrdinalIgnoreCase) And 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
' 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 one extra??? Dim confparam(param.Length - 2) As String ' subtract one extra???
Array.Copy(param, 1, confparam, 0, param.Length - 1) Array.Copy(param, 1, confparam, 0, param.Length - 1)
@ -59,47 +71,17 @@ Friend Class ManagerCommands
End Function End Function
#Region "Configuration sub-commands" #Region "Configuration sub-commands"
Private Shared ReadOnly RoleMention As New Regex("<@?&(?<snowflake>\d+)>", RegexOptions.Compiled)
' Birthday role set ' Birthday role set
Private Async Function ScmdRole(param As String(), reqChannel As SocketTextChannel) As Task Private Async Function ScmdRole(param As String(), reqChannel As SocketTextChannel) As Task
If param.Length <> 2 Then If param.Length <> 2 Then
Await reqChannel.SendMessageAsync(":x: A role name, role mention, or ID value must be specified.") Await reqChannel.SendMessageAsync(":x: A role name, role mention, or ID value must be specified.")
Return Return
End If End If
Dim guild = reqChannel.Guild Dim guild = reqChannel.Guild
Dim input = param(1) Dim role = FindUserInputRole(param(1), guild)
Dim role As SocketRole = Nothing
' Resembles a role mention? Strip it to the pure number
Dim rmatch = RoleMention.Match(input)
If rmatch.Success Then
input = rmatch.Groups("snowflake").Value
End If
' Attempt to get role by ID
Dim rid As ULong
If ULong.TryParse(input, rid) Then
role = guild.GetRole(rid)
Else
' Reset the search value on the off chance there's a role name actually starting with "<&" and ending with ">"
input = param(1)
End If
' If not already found, attempt to search role by string name
If role Is Nothing Then If role Is Nothing Then
For Each search In guild.Roles Await reqChannel.SendMessageAsync(RoleInputError)
If String.Equals(search.Name, input, StringComparison.InvariantCultureIgnoreCase) Then
role = search
Exit For
End If
Next
End If
' Final result
If role Is Nothing Then
Await reqChannel.SendMessageAsync(":x: Unable to determine the given role.")
Else Else
SyncLock Instance.KnownGuilds SyncLock Instance.KnownGuilds
Instance.KnownGuilds(guild.Id).UpdateRoleAsync(role.Id).Wait() Instance.KnownGuilds(guild.Id).UpdateRoleAsync(role.Id).Wait()
@ -152,6 +134,25 @@ Friend Class ManagerCommands
End If End If
End Function End Function
' Moderator role set
Private Async Function ScmdModRole(param As String(), reqChannel As SocketTextChannel) As Task
If param.Length <> 2 Then
Await reqChannel.SendMessageAsync(":x: A role name, role mention, or ID value must be specified.")
Return
End If
Dim guild = reqChannel.Guild
Dim role = FindUserInputRole(param(1), guild)
If role Is Nothing Then
Await reqChannel.SendMessageAsync(RoleInputError)
Else
SyncLock Instance.KnownGuilds
Instance.KnownGuilds(guild.Id).UpdateModeratorRoleAsync(role.Id).Wait()
End SyncLock
Await reqChannel.SendMessageAsync($":white_check_mark: The moderator role is now **{role.Name}**.")
End If
End Function
' Guild default time zone set/unset ' Guild default time zone set/unset
Private Async Function ScmdZone(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
@ -263,10 +264,10 @@ Friend Class ManagerCommands
' Execute command as another user ' Execute command as another user
Private Async Function CmdOverride(param As String(), reqChannel As SocketTextChannel, reqUser As SocketGuildUser) As Task Private Async Function CmdOverride(param As String(), reqChannel As SocketTextChannel, reqUser As SocketGuildUser) As Task
' Managers only. Silently drop if the check fails. ' Moderators only. As with config, silently drop if this check fails.
If Not reqUser.GuildPermissions.ManageGuild Then SyncLock Instance.KnownGuilds
Return If Not Instance.KnownGuilds(reqUser.Guild.Id).IsUserModerator(reqUser) Then Return
End If End SyncLock
If param.Length <> 3 Then If param.Length <> 3 Then
Await reqChannel.SendMessageAsync(GenericError) Await reqChannel.SendMessageAsync(GenericError)
@ -302,4 +303,36 @@ Friend Class ManagerCommands
Await reqChannel.SendMessageAsync($"Executing `{cmdsearch.ToLower()}` on behalf of {If(overuser.Nickname, overuser.Username)}:") Await reqChannel.SendMessageAsync($"Executing `{cmdsearch.ToLower()}` on behalf of {If(overuser.Nickname, overuser.Username)}:")
Await action.Invoke(overparam, reqChannel, overuser) Await action.Invoke(overparam, reqChannel, overuser)
End Function End Function
#Region "Common/helper methods"
Private Const RoleInputError = ":x: Unable to determine the given role."
Private Shared ReadOnly RoleMention As New Regex("<@?&(?<snowflake>\d+)>", RegexOptions.Compiled)
Private Function FindUserInputRole(inputStr As String, guild As SocketGuild) As SocketRole
' Resembles a role mention? Strip it to the pure number
Dim input = inputStr
Dim rmatch = RoleMention.Match(input)
If rmatch.Success Then
input = rmatch.Groups("snowflake").Value
End If
' Attempt to get role by ID, or Nothing
Dim rid As ULong
If ULong.TryParse(input, rid) Then
Return guild.GetRole(rid)
Else
' Reset the search value on the off chance there's a role name that actually resembles a role ping.
input = inputStr
End If
' If not already found, attempt to search role by string name
For Each search In guild.Roles
If String.Equals(search.Name, input, StringComparison.InvariantCultureIgnoreCase) Then
Return search
End If
Next
Return Nothing
End Function
#End Region
End Class End Class