diff --git a/BirthdayBot/BackgroundWorker.vb b/BirthdayBot/BackgroundWorker.vb index 1c22e78..0fb653a 100644 --- a/BirthdayBot/BackgroundWorker.vb +++ b/BirthdayBot/BackgroundWorker.vb @@ -120,29 +120,56 @@ Class BackgroundWorker Dim birthdays = BirthdayCalculate(users, tz) ' Note: Don't quit here if zero people are having birthdays. Roles may still need to be removed by BirthdayApply. - ' Set birthday role, get list of users now having birthdays + ' 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) - Try - announceNames = Await BirthdayApplyAsync(guild, role, birthdays) - Catch ex As Discord.Net.HttpException - If ex.HttpCode = HttpStatusCode.Forbidden Then - SyncLock _bot.KnownGuilds - ' Failed to apply role. Set the warning. - _bot.KnownGuilds(guild.Id).RoleWarning = True - End SyncLock - Return 0 - End If + If BirthdayHasGoodRolePermissions(guild, role) Then + Try + announceNames = Await BirthdayApplyAsync(guild, role, birthdays) + Catch ex As Discord.Net.HttpException + If ex.HttpCode = HttpStatusCode.Forbidden Then + announceNames = Nothing + Else + Throw + End If + End Try + Else + announceNames = Nothing + End If + + If announceNames Is Nothing Then + SyncLock _bot.KnownGuilds + ' Nothing on announceNAmes signals failure to apply roles. Set the warning message. + _bot.KnownGuilds(guild.Id).RoleWarning = True + End SyncLock + Return 0 + End If - Throw - End Try If announceNames.Count <> 0 Then ' Send out announcement message Await BirthdayAnnounceAsync(announce, announceping, channel, announceNames) End If - Return announceNames.Count End Function + ''' + ''' Checks if the bot may be allowed to alter roles. + ''' + Private Function BirthdayHasGoodRolePermissions(guild As SocketGuild, role As SocketRole) As Boolean + If Not guild.CurrentUser.GuildPermissions.ManageRoles Then + ' Bot user cannot manage roles + Return False + End If + + ' Check potential role order conflict + If role.Position >= guild.CurrentUser.Hierarchy Then + ' Target role is at or above bot's highest role. + Return False + End If + + Return True + End Function + ''' ''' Gets all known users from the given guild and returns a list including only those who are ''' currently experiencing a birthday in the respective time zone. @@ -187,21 +214,37 @@ Class BackgroundWorker ''' Sets the birthday role to all applicable users. Unsets it from all others who may have it. ''' ''' A list of users who had the birthday role applied. Use for the announcement message. - Private Async Function BirthdayApplyAsync(g As SocketGuild, r As SocketRole, names As HashSet(Of ULong)) As Task(Of IEnumerable(Of SocketGuildUser)) - If Not g.HasAllMembers Then Await g.DownloadUsersAsync() - Dim newBirthdays As New List(Of SocketGuildUser) - For Each user In g.Users - If names.Contains(user.Id) Then - ' User's in the list. Should have the role. Add and make note of if user does not. - If Not user.Roles.Contains(r) Then - Await user.AddRoleAsync(r) - newBirthdays.Add(user) - End If + Private Async Function BirthdayApplyAsync(g As SocketGuild, + r As SocketRole, + names As HashSet(Of ULong)) As Task(Of IEnumerable(Of SocketGuildUser)) + ' Check members currently with the role. Figure out which users to remove it from. + Dim roleRemoves As New List(Of SocketGuildUser) + Dim roleKeeps As New HashSet(Of ULong) + Dim q = 0 + For Each member In r.Members + If Not names.Contains(member.Id) Then + roleRemoves.Add(member) Else - ' User's not in the list. Should remove the role. - If user.Roles.Contains(r) Then Await user.RemoveRoleAsync(r) + roleKeeps.Add(member.Id) End If + q += 1 Next + + ' TODO Can we remove during the iteration instead of after? investigate later... + For Each user In roleRemoves + Await user.RemoveRoleAsync(r) + Next + + ' Apply role to members not already having it. Prepare announcement list. + Dim newBirthdays As New List(Of SocketGuildUser) + For Each target In names + Dim member = g.GetUser(target) + If member Is Nothing Then Continue For + If roleKeeps.Contains(member.Id) Then Continue For ' already has role - do nothing + Await member.AddRoleAsync(r) + newBirthdays.Add(member) + Next + Return newBirthdays End Function diff --git a/BirthdayBot/BirthdayBot.vbproj b/BirthdayBot/BirthdayBot.vbproj index ea26e40..e0f2411 100644 --- a/BirthdayBot/BirthdayBot.vbproj +++ b/BirthdayBot/BirthdayBot.vbproj @@ -4,8 +4,8 @@ Exe BirthdayBot netcoreapp2.0 - 1.0.0 - 1.0.0.0 + 1.0.4 + 1.0.4.0 Noiiko Discord bot for birthday reminders.