diff --git a/BirthdayBot/BackgroundServices/BirthdayRoleUpdate.vb b/BirthdayBot/BackgroundServices/BirthdayRoleUpdate.vb index 3afcd45..fac7736 100644 --- a/BirthdayBot/BackgroundServices/BirthdayRoleUpdate.vb +++ b/BirthdayBot/BackgroundServices/BirthdayRoleUpdate.vb @@ -69,10 +69,11 @@ Class BirthdayRoleUpdate If gs.AnnounceChannelId.HasValue Then channel = guild.GetTextChannel(gs.AnnounceChannelId.Value) If gs.RoleId.HasValue Then role = guild.GetRole(gs.RoleId.Value) If role Is Nothing Then - gs.RoleWarning = True + gs.RoleWarningNonexist = True Return 0 + Else + gs.RoleWarningNonexist = False End If - gs.RoleWarning = False End SyncLock ' Determine who's currently having a birthday @@ -81,28 +82,29 @@ Class BirthdayRoleUpdate ' Set birthday roles, get list of users that had the role added ' But first check if we are able to do so. Letting all requests fail instead will lead to rate limiting. - Dim announceNames As IEnumerable(Of SocketGuildUser) - If HasCorrectRolePermissions(guild, role) Then + Dim correctRolePermissions = HasCorrectRolePermissions(guild, role) + Dim gotForbidden = False + + Dim announceNames As IEnumerable(Of SocketGuildUser) = Nothing + If correctRolePermissions Then Try announceNames = Await UpdateGuildBirthdayRoles(guild, role, birthdays) Catch ex As Discord.Net.HttpException If ex.HttpCode = HttpStatusCode.Forbidden Then - announceNames = Nothing + gotForbidden = True Else Throw End If End Try - Else - announceNames = Nothing End If - If announceNames Is Nothing Then - SyncLock BotInstance.KnownGuilds - ' Nothing on announceNAmes signals failure to apply roles. Set the warning message. - BotInstance.KnownGuilds(guild.Id).RoleWarning = True - End SyncLock - Return 0 - End If + ' Update warning flag + Dim updateError = Not correctRolePermissions Or gotForbidden + SyncLock BotInstance.KnownGuilds + BotInstance.KnownGuilds(guild.Id).RoleWarningPermission = updateError + End SyncLock + ' Quit now if the warning flag was set. Announcement data is not available. + If updateError Then Return 0 If announceNames.Count <> 0 Then ' Send out announcement message diff --git a/BirthdayBot/BirthdayBot.vb b/BirthdayBot/BirthdayBot.vb index a6a45aa..47685bd 100644 --- a/BirthdayBot/BirthdayBot.vb +++ b/BirthdayBot/BirthdayBot.vb @@ -26,12 +26,12 @@ Class BirthdayBot End Property ''' SyncLock when using. The lock object is itself. - Friend ReadOnly Property KnownGuilds As Dictionary(Of ULong, GuildSettings) + Friend ReadOnly Property KnownGuilds As Dictionary(Of ULong, GuildStateInformation) Public Sub New(conf As Configuration, dc As DiscordSocketClient) Config = conf _client = dc - KnownGuilds = New Dictionary(Of ULong, GuildSettings) + KnownGuilds = New Dictionary(Of ULong, GuildStateInformation) _worker = New BackgroundServiceRunner(Me) @@ -75,7 +75,7 @@ Class BirthdayBot Private Function LoadGuild(g As SocketGuild) As Task Handles _client.JoinedGuild, _client.GuildAvailable SyncLock KnownGuilds If Not KnownGuilds.ContainsKey(g.Id) Then - Dim gi = GuildSettings.LoadSettingsAsync(Config.DatabaseSettings, g.Id).GetAwaiter().GetResult() + Dim gi = GuildStateInformation.LoadSettingsAsync(Config.DatabaseSettings, g.Id).GetAwaiter().GetResult() KnownGuilds.Add(g.Id, gi) End If End SyncLock @@ -114,7 +114,7 @@ Class BirthdayBot End If ' Ban and role warning check - Dim roleWarning As Boolean + Dim roleWarningText As String SyncLock KnownGuilds Dim gi = KnownGuilds(channel.Guild.Id) @@ -125,13 +125,13 @@ Class BirthdayBot End If End If - roleWarning = gi.RoleWarning + roleWarningText = gi.IssueRoleWarning End SyncLock Try - If roleWarning Then + If roleWarningText IsNot Nothing Then Try - Await channel.SendMessageAsync(RoleWarningMsg) + Await channel.SendMessageAsync(roleWarningText) Catch ex As HttpException ' Don't let this prevent the bot from continuing command execution. End Try diff --git a/BirthdayBot/Data/Database.vb b/BirthdayBot/Data/Database.vb index 1300e47..17c4a90 100644 --- a/BirthdayBot/Data/Database.vb +++ b/BirthdayBot/Data/Database.vb @@ -26,7 +26,7 @@ Class Database Private Sub SetupTables() Using db = OpenConnectionAsync().GetAwaiter().GetResult() - GuildSettings.SetUpDatabaseTable(db) ' Note: Call this first. (Foreign reference constraints.) + GuildStateInformation.SetUpDatabaseTable(db) ' Note: Call this first. (Foreign reference constraints.) GuildUserSettings.SetUpDatabaseTable(db) End Using End Sub diff --git a/BirthdayBot/Data/GuildSettings.vb b/BirthdayBot/Data/GuildStateInformation.vb similarity index 86% rename from BirthdayBot/Data/GuildSettings.vb rename to BirthdayBot/Data/GuildStateInformation.vb index 7dccbef..0eeeb6a 100644 --- a/BirthdayBot/Data/GuildSettings.vb +++ b/BirthdayBot/Data/GuildStateInformation.vb @@ -4,11 +4,10 @@ Imports Npgsql Imports NpgsqlTypes ''' -''' Collection of GuildUserSettings instances. Holds cached information on guild users and overall -''' guild options, and provides some database abstractions regarding them all. -''' Object instances are loaded when entering a guild and discarded when the bot leaves the guild. +''' Holds various pieces of state information for a guild the bot is operating in. +''' Includes, among other things, a copy of the guild's settings and a list of all known users with birthdays. ''' -Friend Class GuildSettings +Friend Class GuildStateInformation Public ReadOnly Property GuildId As ULong Private ReadOnly _db As Database Private _bdayRole As ULong? @@ -19,33 +18,55 @@ Friend Class GuildSettings Private _announceMsg As String Private _announceMsgPl As String Private _announcePing As Boolean - Private _userCache As Dictionary(Of ULong, GuildUserSettings) + Private ReadOnly _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(1, 0, 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 if it's been set as such, - ''' and it is only returned after a set time has passed in order to not constantly show the message. + ''' Message for notifying servers that the bot is unable to manipulate its designated role for various reasons. + ''' The returned value is dependent on certain warning flags accessible in this class. To avoid bombarding users + ''' with the same message, this property only returns a non-Nothing value at most once per hour. + ''' Otherwise, it shall always return Nothing if there is no warning to be issued. ''' - Public Property RoleWarning As Boolean + Public ReadOnly Property IssueRoleWarning As String 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 + If DateTimeOffset.UtcNow - _roleLastWarning > RoleWarningInterval Then + _roleLastWarning = DateTimeOffset.UtcNow + Else + Return Nothing End If - Return False + + If RoleWarningUnset Or RoleWarningNonexist Then + Return "Warning: A birthday role must be configured before this bot can function properly. " + + "Update the designated role with `bb.config role (role name/ID)`." + End If + If RoleWarningPermission Then + Return "Warning: This bot is unable to set the birthday role onto users. " + + "Make sure that this bot has the Manage Roles permission and is not placed below the birthday role." + End If + + Return Nothing + End Get + End Property + + ''' + ''' Role warning message: The birthday role cannot be accessed. + ''' + Friend Property RoleWarningPermission As Boolean = False + + ''' + ''' Role warning message: The birthday role no longer exists. + ''' + Friend Property RoleWarningNonexist As Boolean = False + + ''' + ''' Role warning message: The birthday role is not set. + ''' + Friend ReadOnly Property RoleWarningUnset As Boolean + Get + Return _bdayRole Is Nothing End Get - Set(value As Boolean) - _roleWarning = value - End Set End Property ''' @@ -132,9 +153,6 @@ Friend Class GuildSettings ' Weird: if using a ternary operator with a ULong?, Nothing resolves to 0 despite Option Strict On. If Not reader.IsDBNull(1) Then _bdayRole = CULng(reader.GetInt64(1)) - RoleWarning = False - Else - RoleWarning = True End If If Not reader.IsDBNull(2) Then _announceCh = CULng(reader.GetInt64(2)) _tz = If(reader.IsDBNull(3), Nothing, reader.GetString(3)) @@ -253,7 +271,6 @@ Friend Class GuildSettings Public Async Function UpdateRoleAsync(roleId As ULong) As Task _bdayRole = roleId - _roleWarning = False _roleLastWarning = New DateTimeOffset Await UpdateDatabaseAsync() End Function @@ -326,7 +343,7 @@ Friend Class GuildSettings ''' Retrieves an object instance representative of guild settings for the specified guild. ''' If settings for the given guild do not yet exist, a new value is created. ''' - Friend Shared Async Function LoadSettingsAsync(dbsettings As Database, guild As ULong) As Task(Of GuildSettings) + Friend Shared Async Function LoadSettingsAsync(dbsettings As Database, guild As ULong) As Task(Of GuildStateInformation) Using db = Await dbsettings.OpenConnectionAsync() Using c = db.CreateCommand() ' Take note of ordinals for use in the constructor @@ -336,7 +353,7 @@ Friend Class GuildSettings c.Prepare() Using r = Await c.ExecuteReaderAsync() If Await r.ReadAsync() Then - Return New GuildSettings(r, dbsettings) + Return New GuildStateInformation(r, dbsettings) End If End Using End Using diff --git a/BirthdayBot/Data/GuildUserSettings.vb b/BirthdayBot/Data/GuildUserSettings.vb index 257f1d0..d86d573 100644 --- a/BirthdayBot/Data/GuildUserSettings.vb +++ b/BirthdayBot/Data/GuildUserSettings.vb @@ -4,7 +4,7 @@ Imports NpgsqlTypes ''' ''' Representation of a user's birthday settings within a guild. -''' Instances are held and managed by . +''' Instances are held and managed by . ''' Class GuildUserSettings Private _month As Integer @@ -125,7 +125,7 @@ Class GuildUserSettings Friend Shared Sub SetUpDatabaseTable(db As NpgsqlConnection) Using c = db.CreateCommand() c.CommandText = $"create table if not exists {BackingTable} (" + - $"guild_id bigint not null references {GuildSettings.BackingTable}, " + + $"guild_id bigint not null references {GuildStateInformation.BackingTable}, " + "user_id bigint not null, " + "birth_month integer not null, " + "birth_day integer not null, " +