mirror of
https://github.com/NoiTheCat/BirthdayBot.git
synced 2024-11-21 21:54:36 +00:00
Make diagnostic data user-accessible
This commit is contained in:
parent
3fc13efc57
commit
5012834073
11 changed files with 127 additions and 211 deletions
|
@ -38,6 +38,9 @@ Class BirthdayRoleUpdate
|
||||||
End Try
|
End Try
|
||||||
|
|
||||||
' TODO metrics for role sets, unsets, announcements - and how to do that for singles too?
|
' TODO metrics for role sets, unsets, announcements - and how to do that for singles too?
|
||||||
|
|
||||||
|
' Running GC now. Many long-lasting items have likely been discarded by now.
|
||||||
|
GC.Collect()
|
||||||
End Function
|
End Function
|
||||||
|
|
||||||
''' <summary>
|
''' <summary>
|
||||||
|
@ -65,7 +68,6 @@ Class BirthdayRoleUpdate
|
||||||
Dim channel As SocketTextChannel = Nothing
|
Dim channel As SocketTextChannel = Nothing
|
||||||
Dim announce As (String, String)
|
Dim announce As (String, String)
|
||||||
Dim announceping As Boolean
|
Dim announceping As Boolean
|
||||||
Dim op As OperationStatus
|
|
||||||
|
|
||||||
' Skip processing of guild if local info has not yet been loaded
|
' Skip processing of guild if local info has not yet been loaded
|
||||||
If Not BotInstance.GuildCache.ContainsKey(guild.Id) Then Return
|
If Not BotInstance.GuildCache.ContainsKey(guild.Id) Then Return
|
||||||
|
@ -77,7 +79,6 @@ Class BirthdayRoleUpdate
|
||||||
users = .Users
|
users = .Users
|
||||||
announce = .AnnounceMessages
|
announce = .AnnounceMessages
|
||||||
announceping = .AnnouncePing
|
announceping = .AnnouncePing
|
||||||
op = .OperationLog
|
|
||||||
|
|
||||||
If .AnnounceChannelId.HasValue Then channel = guild.GetTextChannel(gs.AnnounceChannelId.Value)
|
If .AnnounceChannelId.HasValue Then channel = guild.GetTextChannel(gs.AnnounceChannelId.Value)
|
||||||
If .RoleId.HasValue Then role = guild.GetRole(gs.RoleId.Value)
|
If .RoleId.HasValue Then role = guild.GetRole(gs.RoleId.Value)
|
||||||
|
@ -91,24 +92,22 @@ Class BirthdayRoleUpdate
|
||||||
' But first check if we are able to do so. Letting all requests fail instead will lead to rate limiting.
|
' But first check if we are able to do so. Letting all requests fail instead will lead to rate limiting.
|
||||||
Dim roleCheck = CheckCorrectRoleSettings(guild, role)
|
Dim roleCheck = CheckCorrectRoleSettings(guild, role)
|
||||||
If Not roleCheck.Item1 Then
|
If Not roleCheck.Item1 Then
|
||||||
SyncLock op
|
SyncLock gs
|
||||||
op(OperationType.BirthdayRole) = New OperationInfo(New Exception(roleCheck.Item2))
|
gs.OperationLog = New OperationStatus((OperationStatus.OperationType.UpdateBirthdayRoleMembership, roleCheck.Item2))
|
||||||
op(OperationType.BirthdayAnnounce) = Nothing
|
|
||||||
End SyncLock
|
End SyncLock
|
||||||
Return
|
Return
|
||||||
End If
|
End If
|
||||||
|
|
||||||
Dim announcementList As IEnumerable(Of SocketGuildUser)
|
Dim announcementList As IEnumerable(Of SocketGuildUser)
|
||||||
|
Dim roleResult As (Integer, Integer) ' Role additions, removals
|
||||||
' Do actual role updating
|
' Do actual role updating
|
||||||
Try
|
Try
|
||||||
announcementList = Await UpdateGuildBirthdayRoles(guild, role, birthdays)
|
Dim updateResult = Await UpdateGuildBirthdayRoles(guild, role, birthdays)
|
||||||
SyncLock op
|
announcementList = updateResult.Item1
|
||||||
op(OperationType.BirthdayRole) = New OperationInfo()
|
roleResult = updateResult.Item2
|
||||||
End SyncLock
|
|
||||||
Catch ex As Discord.Net.HttpException
|
Catch ex As Discord.Net.HttpException
|
||||||
SyncLock op
|
SyncLock gs
|
||||||
op(OperationType.BirthdayRole) = New OperationInfo(ex)
|
gs.OperationLog = New OperationStatus((OperationStatus.OperationType.UpdateBirthdayRoleMembership, ex.Message))
|
||||||
op(OperationType.BirthdayAnnounce) = Nothing
|
|
||||||
End SyncLock
|
End SyncLock
|
||||||
If ex.HttpCode <> HttpStatusCode.Forbidden Then
|
If ex.HttpCode <> HttpStatusCode.Forbidden Then
|
||||||
' Send unusual exceptions to calling method
|
' Send unusual exceptions to calling method
|
||||||
|
@ -117,9 +116,20 @@ Class BirthdayRoleUpdate
|
||||||
Return
|
Return
|
||||||
End Try
|
End Try
|
||||||
|
|
||||||
|
Dim opResult1, opResult2 As (OperationStatus.OperationType, String)
|
||||||
|
opResult1 = (OperationStatus.OperationType.UpdateBirthdayRoleMembership,
|
||||||
|
$"Success: Added {roleResult.Item1} member(s), Removed {roleResult.Item2} member(s) from target role.")
|
||||||
|
|
||||||
If announcementList.Count <> 0 Then
|
If announcementList.Count <> 0 Then
|
||||||
Await AnnounceBirthdaysAsync(announce, announceping, channel, announcementList, op)
|
Dim announceOpResult = Await AnnounceBirthdaysAsync(announce, announceping, channel, announcementList)
|
||||||
|
opResult2 = (OperationStatus.OperationType.SendBirthdayAnnouncementMessage, announceOpResult)
|
||||||
|
Else
|
||||||
|
opResult2 = (OperationStatus.OperationType.SendBirthdayAnnouncementMessage, "Announcement not considered.")
|
||||||
End If
|
End If
|
||||||
|
|
||||||
|
SyncLock gs
|
||||||
|
gs.OperationLog = New OperationStatus(opResult1, opResult2)
|
||||||
|
End SyncLock
|
||||||
End Function
|
End Function
|
||||||
|
|
||||||
''' <summary>
|
''' <summary>
|
||||||
|
@ -127,19 +137,19 @@ Class BirthdayRoleUpdate
|
||||||
''' </summary>
|
''' </summary>
|
||||||
Private Function CheckCorrectRoleSettings(guild As SocketGuild, role As SocketRole) As (Boolean, String)
|
Private Function CheckCorrectRoleSettings(guild As SocketGuild, role As SocketRole) As (Boolean, String)
|
||||||
If role Is Nothing Then
|
If role Is Nothing Then
|
||||||
Return (False, "Designated role not found or defined in guild")
|
Return (False, "Failed: Designated role not found or defined.")
|
||||||
End If
|
End If
|
||||||
|
|
||||||
If Not guild.CurrentUser.GuildPermissions.ManageRoles Then
|
If Not guild.CurrentUser.GuildPermissions.ManageRoles Then
|
||||||
Return (False, "Bot does not contain Manage Roles permission")
|
Return (False, "Failed: Bot does not contain Manage Roles permission.")
|
||||||
End If
|
End If
|
||||||
|
|
||||||
' Check potential role order conflict
|
' Check potential role order conflict
|
||||||
If role.Position >= guild.CurrentUser.Hierarchy Then
|
If role.Position >= guild.CurrentUser.Hierarchy Then
|
||||||
Return (False, "Targeted role is at or above bot's highest rank")
|
Return (False, "Failed: Bot is beneath the designated role in the role hierarchy.")
|
||||||
End If
|
End If
|
||||||
|
|
||||||
Return (True, "Success")
|
Return (True, Nothing)
|
||||||
End Function
|
End Function
|
||||||
|
|
||||||
''' <summary>
|
''' <summary>
|
||||||
|
@ -189,7 +199,7 @@ Class BirthdayRoleUpdate
|
||||||
''' <returns>A list of users who had the birthday role applied. Use for the announcement message.</returns>
|
''' <returns>A list of users who had the birthday role applied. Use for the announcement message.</returns>
|
||||||
Private Async Function UpdateGuildBirthdayRoles(g As SocketGuild,
|
Private Async Function UpdateGuildBirthdayRoles(g As SocketGuild,
|
||||||
r As SocketRole,
|
r As SocketRole,
|
||||||
names As HashSet(Of ULong)) As Task(Of IEnumerable(Of SocketGuildUser))
|
names As HashSet(Of ULong)) As Task(Of (IEnumerable(Of SocketGuildUser), (Integer, Integer)))
|
||||||
' Check members currently with the role. Figure out which users to remove it from.
|
' Check members currently with the role. Figure out which users to remove it from.
|
||||||
Dim roleRemoves As New List(Of SocketGuildUser)
|
Dim roleRemoves As New List(Of SocketGuildUser)
|
||||||
Dim roleKeeps As New HashSet(Of ULong)
|
Dim roleKeeps As New HashSet(Of ULong)
|
||||||
|
@ -218,7 +228,7 @@ Class BirthdayRoleUpdate
|
||||||
newBirthdays.Add(member)
|
newBirthdays.Add(member)
|
||||||
Next
|
Next
|
||||||
|
|
||||||
Return newBirthdays
|
Return (newBirthdays, (newBirthdays.Count, roleRemoves.Count))
|
||||||
End Function
|
End Function
|
||||||
|
|
||||||
Public Const DefaultAnnounce = "Please wish a happy birthday to %n!"
|
Public Const DefaultAnnounce = "Please wish a happy birthday to %n!"
|
||||||
|
@ -231,13 +241,9 @@ Class BirthdayRoleUpdate
|
||||||
Private Async Function AnnounceBirthdaysAsync(announce As (String, String),
|
Private Async Function AnnounceBirthdaysAsync(announce As (String, String),
|
||||||
announcePing As Boolean,
|
announcePing As Boolean,
|
||||||
c As SocketTextChannel,
|
c As SocketTextChannel,
|
||||||
names As IEnumerable(Of SocketGuildUser),
|
names As IEnumerable(Of SocketGuildUser)) As Task(Of String)
|
||||||
op As OperationStatus) As Task
|
|
||||||
If c Is Nothing Then
|
If c Is Nothing Then
|
||||||
SyncLock op
|
Return "Announcement channel is undefined."
|
||||||
op(OperationType.BirthdayAnnounce) = New OperationInfo("Announcement channel missing or undefined")
|
|
||||||
End SyncLock
|
|
||||||
Return
|
|
||||||
End If
|
End If
|
||||||
|
|
||||||
Dim announceMsg As String
|
Dim announceMsg As String
|
||||||
|
@ -268,13 +274,9 @@ Class BirthdayRoleUpdate
|
||||||
|
|
||||||
Try
|
Try
|
||||||
Await c.SendMessageAsync(announceMsg.Replace("%n", namedisplay.ToString()))
|
Await c.SendMessageAsync(announceMsg.Replace("%n", namedisplay.ToString()))
|
||||||
SyncLock op
|
Return $"Successfully announced {names.Count} name(s)"
|
||||||
op(OperationType.BirthdayAnnounce) = New OperationInfo()
|
|
||||||
End SyncLock
|
|
||||||
Catch ex As Discord.Net.HttpException
|
Catch ex As Discord.Net.HttpException
|
||||||
SyncLock op
|
Return ex.Message
|
||||||
op(OperationType.BirthdayAnnounce) = New OperationInfo(ex)
|
|
||||||
End SyncLock
|
|
||||||
End Try
|
End Try
|
||||||
End Function
|
End Function
|
||||||
End Class
|
End Class
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
Imports BirthdayBot.CommandsCommon
|
Imports BirthdayBot.CommandsCommon
|
||||||
Imports Discord
|
Imports Discord
|
||||||
Imports Discord.Net
|
Imports Discord.Net
|
||||||
|
Imports Discord.Webhook
|
||||||
Imports Discord.WebSocket
|
Imports Discord.WebSocket
|
||||||
|
|
||||||
Class BirthdayBot
|
Class BirthdayBot
|
||||||
|
@ -10,7 +11,6 @@ Class BirthdayBot
|
||||||
Private ReadOnly _cmdsListing As ListingCommands
|
Private ReadOnly _cmdsListing As ListingCommands
|
||||||
Private ReadOnly _cmdsHelp As HelpInfoCommands
|
Private ReadOnly _cmdsHelp As HelpInfoCommands
|
||||||
Private ReadOnly _cmdsMods As ManagerCommands
|
Private ReadOnly _cmdsMods As ManagerCommands
|
||||||
Private ReadOnly _cmdsDiag As DiagnosticCommands
|
|
||||||
|
|
||||||
Private WithEvents Client As DiscordShardedClient
|
Private WithEvents Client As DiscordShardedClient
|
||||||
Private ReadOnly _worker As BackgroundServiceRunner
|
Private ReadOnly _worker As BackgroundServiceRunner
|
||||||
|
@ -22,12 +22,13 @@ Class BirthdayBot
|
||||||
Return Client
|
Return Client
|
||||||
End Get
|
End Get
|
||||||
End Property
|
End Property
|
||||||
|
|
||||||
Friend ReadOnly Property GuildCache As ConcurrentDictionary(Of ULong, GuildStateInformation)
|
Friend ReadOnly Property GuildCache As ConcurrentDictionary(Of ULong, GuildStateInformation)
|
||||||
|
Friend ReadOnly Property LogWebhook As DiscordWebhookClient
|
||||||
|
|
||||||
Public Sub New(conf As Configuration, dc As DiscordShardedClient)
|
Public Sub New(conf As Configuration, dc As DiscordShardedClient)
|
||||||
Config = conf
|
Config = conf
|
||||||
Client = dc
|
Client = dc
|
||||||
|
LogWebhook = New DiscordWebhookClient(conf.LogWebhook)
|
||||||
GuildCache = New ConcurrentDictionary(Of ULong, GuildStateInformation)
|
GuildCache = New ConcurrentDictionary(Of ULong, GuildStateInformation)
|
||||||
|
|
||||||
_worker = New BackgroundServiceRunner(Me)
|
_worker = New BackgroundServiceRunner(Me)
|
||||||
|
@ -50,10 +51,6 @@ Class BirthdayBot
|
||||||
For Each item In _cmdsMods.Commands
|
For Each item In _cmdsMods.Commands
|
||||||
_dispatchCommands.Add(item.Item1, item.Item2)
|
_dispatchCommands.Add(item.Item1, item.Item2)
|
||||||
Next
|
Next
|
||||||
_cmdsDiag = New DiagnosticCommands(Me, conf)
|
|
||||||
For Each item In _cmdsDiag.Commands
|
|
||||||
_dispatchCommands.Add(item.Item1, item.Item2)
|
|
||||||
Next
|
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
Public Async Function Start() As Task
|
Public Async Function Start() As Task
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<RootNamespace>BirthdayBot</RootNamespace>
|
<RootNamespace>BirthdayBot</RootNamespace>
|
||||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||||
<Version>1.3.6</Version>
|
<Version>1.4.0</Version>
|
||||||
<Authors>Noi</Authors>
|
<Authors>Noi</Authors>
|
||||||
<Company />
|
<Company />
|
||||||
<Description>Discord bot for birthday reminders.</Description>
|
<Description>Discord bot for birthday reminders.</Description>
|
||||||
|
|
|
@ -8,7 +8,6 @@ Imports System.IO
|
||||||
Class Configuration
|
Class Configuration
|
||||||
Public ReadOnly Property BotToken As String
|
Public ReadOnly Property BotToken As String
|
||||||
Public ReadOnly Property LogWebhook As String
|
Public ReadOnly Property LogWebhook As String
|
||||||
Public ReadOnly Property DiagnosticChannel As ULong
|
|
||||||
Public ReadOnly Property DBotsToken As String
|
Public ReadOnly Property DBotsToken As String
|
||||||
Public ReadOnly Property DatabaseSettings As Database
|
Public ReadOnly Property DatabaseSettings As Database
|
||||||
|
|
||||||
|
@ -36,8 +35,6 @@ Class Configuration
|
||||||
Throw New Exception("'LogWebhook' must be specified.")
|
Throw New Exception("'LogWebhook' must be specified.")
|
||||||
End If
|
End If
|
||||||
|
|
||||||
DiagnosticChannel = jc("DiagnosticChannel").Value(Of ULong)()
|
|
||||||
|
|
||||||
Dim dbj = jc("DBotsToken")
|
Dim dbj = jc("DBotsToken")
|
||||||
If dbj IsNot Nothing Then
|
If dbj IsNot Nothing Then
|
||||||
DBotsToken = dbj.Value(Of String)()
|
DBotsToken = dbj.Value(Of String)()
|
||||||
|
|
|
@ -19,7 +19,7 @@ Class GuildStateInformation
|
||||||
Private _announceMsgPl As String
|
Private _announceMsgPl As String
|
||||||
Private _announcePing As Boolean
|
Private _announcePing As Boolean
|
||||||
Private ReadOnly _userCache As Dictionary(Of ULong, GuildUserSettings)
|
Private ReadOnly _userCache As Dictionary(Of ULong, GuildUserSettings)
|
||||||
Public ReadOnly Property OperationLog As OperationStatus
|
Public Property OperationLog As OperationStatus
|
||||||
|
|
||||||
''' <summary>
|
''' <summary>
|
||||||
''' Gets a list of cached registered user information.
|
''' Gets a list of cached registered user information.
|
||||||
|
|
48
BirthdayBot/Data/OperationStatus.vb
Normal file
48
BirthdayBot/Data/OperationStatus.vb
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
Imports System.Text
|
||||||
|
''' <summary>
|
||||||
|
''' Holds information regarding the previous updating operation done on a guild including success/error information.
|
||||||
|
''' </summary>
|
||||||
|
Class OperationStatus
|
||||||
|
Private ReadOnly _log As New Dictionary(Of OperationType, String)
|
||||||
|
|
||||||
|
Public ReadOnly Property Timestamp As DateTimeOffset
|
||||||
|
|
||||||
|
Sub New(ParamArray statuses() As (OperationType, String))
|
||||||
|
Timestamp = DateTimeOffset.UtcNow
|
||||||
|
For Each status In statuses
|
||||||
|
_log(status.Item1) = status.Item2
|
||||||
|
Next
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
''' <summary>
|
||||||
|
''' Prepares known information in a displayable format.
|
||||||
|
''' </summary>
|
||||||
|
Public Function GetDiagStrings() As String
|
||||||
|
Dim report As New StringBuilder
|
||||||
|
For Each otype As OperationType In [Enum].GetValues(GetType(OperationType))
|
||||||
|
Dim prefix = $"`{[Enum].GetName(GetType(OperationType), otype)}`: "
|
||||||
|
|
||||||
|
Dim info As String = Nothing
|
||||||
|
|
||||||
|
If Not _log.TryGetValue(otype, info) Then
|
||||||
|
report.AppendLine(prefix + "No data")
|
||||||
|
Continue For
|
||||||
|
End If
|
||||||
|
|
||||||
|
If info Is Nothing Then
|
||||||
|
report.AppendLine(prefix + "Success")
|
||||||
|
Else
|
||||||
|
report.AppendLine(prefix + info)
|
||||||
|
End If
|
||||||
|
Next
|
||||||
|
Return report.ToString()
|
||||||
|
End Function
|
||||||
|
|
||||||
|
''' <summary>
|
||||||
|
''' Specifies the type of operation logged. These enum values are publicly displayed in the specified order.
|
||||||
|
''' </summary>
|
||||||
|
Public Enum OperationType
|
||||||
|
UpdateBirthdayRoleMembership
|
||||||
|
SendBirthdayAnnouncementMessage
|
||||||
|
End Enum
|
||||||
|
End Class
|
|
@ -1,75 +0,0 @@
|
||||||
Imports Discord.WebSocket
|
|
||||||
Imports Discord.Webhook
|
|
||||||
Imports System.Text
|
|
||||||
''' <summary>
|
|
||||||
''' Implements the command used by global bot moderators to get operation info for each guild.
|
|
||||||
''' </summary>
|
|
||||||
Class DiagnosticCommands
|
|
||||||
Inherits CommandsCommon
|
|
||||||
|
|
||||||
Private ReadOnly _webhook As DiscordWebhookClient
|
|
||||||
Private ReadOnly _diagChannel As ULong
|
|
||||||
|
|
||||||
Sub New(inst As BirthdayBot, db As Configuration)
|
|
||||||
MyBase.New(inst, db)
|
|
||||||
|
|
||||||
_webhook = New DiscordWebhookClient(db.LogWebhook)
|
|
||||||
_diagChannel = db.DiagnosticChannel
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Public Overrides ReadOnly Property Commands As IEnumerable(Of (String, CommandHandler))
|
|
||||||
Get
|
|
||||||
Return New List(Of (String, CommandHandler)) From {
|
|
||||||
("diag", AddressOf CmdDiag),
|
|
||||||
("checkme", AddressOf CmdCheckme)
|
|
||||||
}
|
|
||||||
End Get
|
|
||||||
End Property
|
|
||||||
|
|
||||||
' Dumps all known guild information to the given webhook
|
|
||||||
Private Async Function CmdDiag(param As String(), reqChannel As SocketTextChannel, reqUser As SocketGuildUser) As Task
|
|
||||||
' Ignore if not in the correct channel
|
|
||||||
If reqChannel.Id <> _diagChannel Then Return
|
|
||||||
|
|
||||||
' Requires two parameters: (cmd) (guild id)
|
|
||||||
If param.Length <> 2 Then
|
|
||||||
Await reqChannel.SendMessageAsync(":x: Usage: (command) (guild ID)")
|
|
||||||
Return
|
|
||||||
End If
|
|
||||||
|
|
||||||
Dim rgid As ULong
|
|
||||||
If Not ULong.TryParse(param(1), rgid) Then
|
|
||||||
Await reqChannel.SendMessageAsync(":x: Cannot parse numeric guild ID")
|
|
||||||
Return
|
|
||||||
End If
|
|
||||||
|
|
||||||
Dim guild = Instance.DiscordClient.GetGuild(rgid)
|
|
||||||
If guild Is Nothing Then
|
|
||||||
Await reqChannel.SendMessageAsync(":x: Guild is not known to the bot")
|
|
||||||
End If
|
|
||||||
|
|
||||||
Dim gi = Instance.GuildCache(rgid)
|
|
||||||
If gi Is Nothing Then
|
|
||||||
Await reqChannel.SendMessageAsync(":x: Guild is known, but information is not available.")
|
|
||||||
End If
|
|
||||||
|
|
||||||
Await reqChannel.SendMessageAsync(":white_check_mark: Compiling info and sending to webhook.")
|
|
||||||
|
|
||||||
Dim report As New StringBuilder
|
|
||||||
report.AppendLine("=-=-=-=-GUILD INFORMATION-=-=-=-=")
|
|
||||||
' TODO dump config info
|
|
||||||
report.AppendLine($"{guild.Id}: {guild.Name}")
|
|
||||||
report.AppendLine($"User count: {guild.Users.Count}")
|
|
||||||
report.AppendLine("---")
|
|
||||||
SyncLock gi.OperationLog
|
|
||||||
report.Append(gi.OperationLog.GetDiagStrings())
|
|
||||||
End SyncLock
|
|
||||||
report.AppendLine("**Note**: Full stack traces for captured exceptions are printed to console.")
|
|
||||||
|
|
||||||
Await _webhook.SendMessageAsync(report.ToString())
|
|
||||||
End Function
|
|
||||||
|
|
||||||
Private Async Function CmdCheckme(param As String(), reqChannel As SocketTextChannel, reqUser As SocketGuildUser) As Task
|
|
||||||
Await _webhook.SendMessageAsync($"{reqUser.Username}#{reqUser.Discriminator}: {reqChannel.Guild.Id} checkme")
|
|
||||||
End Function
|
|
||||||
End Class
|
|
|
@ -1,37 +0,0 @@
|
||||||
''' <summary>
|
|
||||||
''' Information regarding a single type of operation.
|
|
||||||
''' </summary>
|
|
||||||
Class OperationInfo
|
|
||||||
''' <summary>
|
|
||||||
''' The time in which the respective operation was attempted.
|
|
||||||
''' </summary>
|
|
||||||
ReadOnly Property Timestamp As DateTimeOffset
|
|
||||||
''' <summary>
|
|
||||||
''' Any exception encountered during the respective operation.
|
|
||||||
''' </summary>
|
|
||||||
''' <returns>Nothing/null if the previous given operation was a success.</returns>
|
|
||||||
ReadOnly Property Exception As Exception
|
|
||||||
|
|
||||||
''' <summary>
|
|
||||||
''' Creates an instance containing a success status.
|
|
||||||
''' </summary>
|
|
||||||
Sub New()
|
|
||||||
Timestamp = DateTimeOffset.UtcNow
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
''' <summary>
|
|
||||||
''' Creates an instance containing a captured exception
|
|
||||||
''' </summary>
|
|
||||||
Sub New(ex As Exception)
|
|
||||||
Me.New()
|
|
||||||
Exception = ex
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
''' <summary>
|
|
||||||
''' Creates an instance containing a custom error message
|
|
||||||
''' </summary>
|
|
||||||
Sub New(message As String)
|
|
||||||
Me.New()
|
|
||||||
Exception = New Exception(message)
|
|
||||||
End Sub
|
|
||||||
End Class
|
|
|
@ -1,49 +0,0 @@
|
||||||
Imports System.Text
|
|
||||||
''' <summary>
|
|
||||||
''' Holds information regarding previous operations done on a guild and their most recent success/error status.
|
|
||||||
''' </summary>
|
|
||||||
Class OperationStatus
|
|
||||||
Private ReadOnly _log As New Dictionary(Of OperationType, OperationInfo)
|
|
||||||
|
|
||||||
Default Public Property Item(otype As OperationType) As OperationInfo
|
|
||||||
Get
|
|
||||||
Dim o As OperationInfo = Nothing
|
|
||||||
If Not _log.TryGetValue(otype, o) Then
|
|
||||||
Return Nothing
|
|
||||||
End If
|
|
||||||
Return o
|
|
||||||
End Get
|
|
||||||
Set(value As OperationInfo)
|
|
||||||
If value Is Nothing Then
|
|
||||||
_log.Remove(otype)
|
|
||||||
Else
|
|
||||||
_log(otype) = value
|
|
||||||
End If
|
|
||||||
End Set
|
|
||||||
End Property
|
|
||||||
|
|
||||||
''' <summary>
|
|
||||||
''' Prepares known information in a displayable format.
|
|
||||||
''' </summary>
|
|
||||||
Public Function GetDiagStrings() As String
|
|
||||||
Dim report As New StringBuilder
|
|
||||||
For Each otype As OperationType In [Enum].GetValues(GetType(OperationType))
|
|
||||||
Dim prefix = $"`{[Enum].GetName(GetType(OperationType), otype)}`: "
|
|
||||||
|
|
||||||
Dim info = Item(otype)
|
|
||||||
If info Is Nothing Then
|
|
||||||
report.AppendLine(prefix + "No data")
|
|
||||||
Continue For
|
|
||||||
End If
|
|
||||||
prefix += info.Timestamp.ToString("u") + " "
|
|
||||||
|
|
||||||
If info.Exception Is Nothing Then
|
|
||||||
report.AppendLine(prefix + "Success")
|
|
||||||
Else
|
|
||||||
Log("OperationStatus", prefix + info.Exception.ToString())
|
|
||||||
report.AppendLine(prefix + info.Exception.Message)
|
|
||||||
End If
|
|
||||||
Next
|
|
||||||
Return report.ToString()
|
|
||||||
End Function
|
|
||||||
End Class
|
|
|
@ -1,5 +0,0 @@
|
||||||
Enum OperationType
|
|
||||||
BirthdayRole
|
|
||||||
BirthdayAnnounce
|
|
||||||
CommandDispatch
|
|
||||||
End Enum
|
|
|
@ -1,18 +1,22 @@
|
||||||
Imports System.Text.RegularExpressions
|
Imports System.Text.RegularExpressions
|
||||||
|
Imports Discord
|
||||||
Imports Discord.WebSocket
|
Imports Discord.WebSocket
|
||||||
|
Imports NodaTime
|
||||||
|
|
||||||
Friend Class ManagerCommands
|
Friend Class ManagerCommands
|
||||||
Inherits CommandsCommon
|
Inherits CommandsCommon
|
||||||
|
|
||||||
Private Delegate Function ConfigSubcommand(param As String(), reqChannel As SocketTextChannel) As Task
|
Private Delegate Function ConfigSubcommand(param As String(), reqChannel As SocketTextChannel) As Task
|
||||||
|
|
||||||
Private _subcommands As Dictionary(Of String, ConfigSubcommand)
|
Private ReadOnly _subcommands As Dictionary(Of String, ConfigSubcommand)
|
||||||
Private _usercommands As Dictionary(Of String, CommandHandler)
|
Private ReadOnly _usercommands As Dictionary(Of String, CommandHandler)
|
||||||
|
|
||||||
Public Overrides ReadOnly Property Commands As IEnumerable(Of (String, CommandHandler))
|
Public Overrides ReadOnly Property Commands As IEnumerable(Of (String, CommandHandler))
|
||||||
Get
|
Get
|
||||||
Return New List(Of (String, CommandHandler)) From {
|
Return New List(Of (String, CommandHandler)) From {
|
||||||
("config", AddressOf CmdConfigDispatch),
|
("config", AddressOf CmdConfigDispatch),
|
||||||
("override", AddressOf CmdOverride)
|
("override", AddressOf CmdOverride),
|
||||||
|
("status", AddressOf CmdStatus)
|
||||||
}
|
}
|
||||||
End Get
|
End Get
|
||||||
End Property
|
End Property
|
||||||
|
@ -338,6 +342,40 @@ Friend Class ManagerCommands
|
||||||
Await action.Invoke(overparam, reqChannel, overuser)
|
Await action.Invoke(overparam, reqChannel, overuser)
|
||||||
End Function
|
End Function
|
||||||
|
|
||||||
|
' Prints a status report useful for troubleshooting operational issues within a guild
|
||||||
|
Private Async Function CmdStatus(param As String(), reqChannel As SocketTextChannel, reqUser As SocketGuildUser) As Task
|
||||||
|
' Moderators only. As with config, silently drop if this check fails.
|
||||||
|
If Not Instance.GuildCache(reqUser.Guild.Id).IsUserModerator(reqUser) Then Return
|
||||||
|
|
||||||
|
Dim result As New EmbedBuilder
|
||||||
|
Dim optime As DateTimeOffset
|
||||||
|
Dim optext As String
|
||||||
|
Dim zone As String
|
||||||
|
Dim gi = Instance.GuildCache(reqChannel.Guild.Id)
|
||||||
|
SyncLock gi
|
||||||
|
Dim opstat = gi.OperationLog
|
||||||
|
optext = opstat.GetDiagStrings() ' !!! Bulk of output handled by this method
|
||||||
|
optime = opstat.Timestamp
|
||||||
|
zone = If(gi.TimeZone, "UTC")
|
||||||
|
End SyncLock
|
||||||
|
Dim shard = Instance.DiscordClient.GetShardIdFor(reqChannel.Guild)
|
||||||
|
|
||||||
|
' Calculate timestamp in current zone
|
||||||
|
Dim ts As String = "Last update:"
|
||||||
|
Dim zonedTimeInstant = SystemClock.Instance.GetCurrentInstant().InZone(DateTimeZoneProviders.Tzdb.GetZoneOrNull(zone))
|
||||||
|
Dim timeAgoEstimate = DateTimeOffset.UtcNow - optime
|
||||||
|
|
||||||
|
With result
|
||||||
|
.Title = "Background operation status"
|
||||||
|
.Description = $"Shard: {shard}" + vbLf +
|
||||||
|
$"Operation time: {Math.Round(timeAgoEstimate.TotalSeconds)} second(s) ago at {zonedTimeInstant}" + vbLf +
|
||||||
|
"Report:" + vbLf +
|
||||||
|
optext.TrimEnd()
|
||||||
|
End With
|
||||||
|
|
||||||
|
Await reqChannel.SendMessageAsync(embed:=result.Build())
|
||||||
|
End Function
|
||||||
|
|
||||||
#Region "Common/helper methods"
|
#Region "Common/helper methods"
|
||||||
Private Const RoleInputError = ":x: Unable to determine the given role."
|
Private Const RoleInputError = ":x: Unable to determine the given role."
|
||||||
Private Shared ReadOnly RoleMention As New Regex("<@?&(?<snowflake>\d+)>", RegexOptions.Compiled)
|
Private Shared ReadOnly RoleMention As New Regex("<@?&(?<snowflake>\d+)>", RegexOptions.Compiled)
|
||||||
|
|
Loading…
Reference in a new issue