diff --git a/example.volte.json b/example.volte.json index ba8204ab..ea1071ed 100644 --- a/example.volte.json +++ b/example.volte.json @@ -19,8 +19,8 @@ //same as above but for failures "log_all_commands": true, //whether or not to log all commands to the bot's console; enabled by default - "join_leave_log": { - //send a log of the bot's joins/leaves to a channel within a certain guild + "guild_logging": { + //send a log of the bot's guild joins/leaves, as well as any exceptions that occur "enabled": false, "guild_id": 0, "channel_id": 0 diff --git a/src/Commands/Modules/Utility/InfoCommands.cs b/src/Commands/Modules/Utility/InfoCommands.cs index 041fdca0..25407d7e 100644 --- a/src/Commands/Modules/Utility/InfoCommands.cs +++ b/src/Commands/Modules/Utility/InfoCommands.cs @@ -1,4 +1,5 @@ -using System.Diagnostics; +using System; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Discord.WebSocket; diff --git a/src/Core/Config.cs b/src/Core/Config.cs index 08104152..af771bdc 100644 --- a/src/Core/Config.cs +++ b/src/Core/Config.cs @@ -31,7 +31,7 @@ namespace Volte.Core SuccessEmbedColor = 0x7000FB, ErrorEmbedColor = 0xFF0000, LogAllCommands = true, - JoinLeaveLog = new JoinLeaveLog(), + GuildLogging = new GuildLogging(), BlacklistedGuildOwners = new ulong[] { }, EnabledFeatures = new EnabledFeatures() }; @@ -90,7 +90,7 @@ namespace Volte.Core public static bool LogAllCommands => _configuration.LogAllCommands; - public static JoinLeaveLog JoinLeaveLog => _configuration.JoinLeaveLog; + public static GuildLogging GuildLogging => _configuration.GuildLogging; public static IEnumerable BlacklistedOwners => _configuration.BlacklistedGuildOwners; @@ -126,8 +126,8 @@ namespace Volte.Core [JsonProperty("log_all_commands")] public bool LogAllCommands { get; internal set; } - [JsonProperty("join_leave_log")] - public JoinLeaveLog JoinLeaveLog { get; internal set; } + [JsonProperty("guild_logging")] + public GuildLogging GuildLogging { get; internal set; } [JsonProperty("blacklisted_guild_owners")] public ulong[] BlacklistedGuildOwners { get; internal set; } diff --git a/src/Core/Models/BotConfig/JoinLeaveLog.cs b/src/Core/Models/BotConfig/GuildLogging.cs similarity index 86% rename from src/Core/Models/BotConfig/JoinLeaveLog.cs rename to src/Core/Models/BotConfig/GuildLogging.cs index 245262b2..faaa98fc 100644 --- a/src/Core/Models/BotConfig/JoinLeaveLog.cs +++ b/src/Core/Models/BotConfig/GuildLogging.cs @@ -2,9 +2,9 @@ using Newtonsoft.Json; namespace Volte.Core.Models.BotConfig { - public sealed class JoinLeaveLog + public sealed class GuildLogging { - internal JoinLeaveLog() + internal GuildLogging() { Enabled = false; GuildId = ulong.MinValue; diff --git a/src/Services/GuildService.cs b/src/Services/GuildService.cs index bfaf5fbb..b091e89e 100644 --- a/src/Services/GuildService.cs +++ b/src/Services/GuildService.cs @@ -1,5 +1,5 @@ -using System; using System.Linq; +using System.Net; using System.Text; using System.Threading.Tasks; using Discord; @@ -48,7 +48,7 @@ namespace Volte.Services .ToString()) .AddField("Support Server", "[Join my support Discord here](https://discord.gg/H8bcFr2)"); - _logger.Error(LogSource.Volte, + _logger.Debug(LogSource.Volte, "Attempting to send the guild owner the introduction message."); try { @@ -56,7 +56,7 @@ namespace Volte.Services _logger.Error(LogSource.Volte, "Sent the guild owner the introduction message."); } - catch (HttpException ex) when (ex.DiscordCode is 50007) + catch (HttpException ex) when (ex.HttpCode is HttpStatusCode.Forbidden) { var c = args.Guild.TextChannels.OrderByDescending(x => x.Position).FirstOrDefault(); _logger.Error(LogSource.Volte, @@ -64,20 +64,20 @@ namespace Volte.Services if (c != null) await embed.SendToAsync(c); } - if (!Config.JoinLeaveLog.Enabled) return; - var joinLeave = Config.JoinLeaveLog; - if (joinLeave.GuildId is 0 || joinLeave.ChannelId is 0) + if (!Config.GuildLogging.Enabled) return; + var guildLogging = Config.GuildLogging; + if (guildLogging.GuildId is 0 || guildLogging.ChannelId is 0) { _logger.Error(LogSource.Volte, - "Invalid value set for the GuildId or ChannelId in the JoinLeaveLog config option. " + - "To fix, set Enabled to false, or correctly fill in your options."); + "Invalid value set for the guild_id or channel_id in the guild_logging config section. " + + "To fix, set enabled to false, or correctly fill in your options."); return; } - var channel = _client.GetGuild(joinLeave.GuildId).GetTextChannel(joinLeave.ChannelId); + var channel = _client.GetGuild(guildLogging.GuildId)?.GetTextChannel(guildLogging.ChannelId); if (channel is null) { - _logger.Error(LogSource.Volte, "Invalid JoinLeaveLog.GuildId/JoinLeaveLog.ChannelId configuration. Check your IDs and try again."); + _logger.Error(LogSource.Volte, "Invalid guild_logging.guild_id/guild_logging.channel_id configuration. Check your IDs and try again."); return; } @@ -100,14 +100,14 @@ namespace Volte.Services $"{_client.GetOwner().Mention}: Joined a guild with more bots than users.", false, e.WithSuccessColor().Build()); else - await channel.SendMessageAsync("", false, e.WithSuccessColor().Build()); + await e.WithSuccessColor().SendToAsync(channel); } public async Task OnLeaveAsync(LeftGuildEventArgs args) { _logger.Debug(LogSource.Volte, "Left a guild."); - if (!Config.JoinLeaveLog.Enabled) return; - var joinLeave = Config.JoinLeaveLog; + if (!Config.GuildLogging.Enabled) return; + var joinLeave = Config.GuildLogging; if (joinLeave.GuildId is 0 || joinLeave.ChannelId is 0) { _logger.Error(LogSource.Volte, diff --git a/src/Services/LoggingService.cs b/src/Services/LoggingService.cs index a04f7592..30f59f96 100644 --- a/src/Services/LoggingService.cs +++ b/src/Services/LoggingService.cs @@ -1,10 +1,16 @@ using System; using System.IO; +using System.Net.Http; using System.Text; using System.Threading.Tasks; +using System.Xml; using Discord; +using Discord.WebSocket; using Gommon; using Humanizer; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using RestSharp; using Volte.Core; using Volte.Core.Models; using Volte.Core.Models.EventArgs; @@ -15,8 +21,18 @@ namespace Volte.Services { public sealed class LoggingService : VolteEventService { + private readonly DiscordShardedClient _client; + private readonly HttpClient _http; + private readonly object _lock; private const string LogFile = "data/Volte.log"; - private readonly object _lock = new object(); + + public LoggingService(DiscordShardedClient discordShardedClient, + HttpClient httpClient) + { + _client = discordShardedClient; + _http = httpClient; + _lock = new object(); + } public override Task DoAsync(EventArgs args) { @@ -97,7 +113,7 @@ namespace Volte.Services /// /// Exception to print. public void LogException(Exception e) - => Log(LogSeverity.Error, LogSource.Volte, string.Empty, e); + => Execute(LogSeverity.Error, LogSource.Volte, string.Empty, e); private void Execute(LogSeverity s, LogSource src, string message, Exception e) { @@ -122,6 +138,7 @@ namespace Volte.Services var toWrite = $"{Environment.NewLine}{e.Message}{Environment.NewLine}{e.StackTrace}"; Append(toWrite, Color.IndianRed); content.Append(toWrite); + LogExceptionInDiscord(e); } Console.Write(Environment.NewLine); @@ -163,5 +180,31 @@ namespace Volte.Services LogSeverity.Debug => (Color.SandyBrown, "DEBG"), _ => throw new ArgumentNullException(nameof(severity), "severity cannot be null") }; + + private void LogExceptionInDiscord(Exception e) + { + if (!Config.GuildLogging.Enabled) return; + var guildLogging = Config.GuildLogging; + var channel = _client.GetGuild(guildLogging.GuildId)?.GetTextChannel(guildLogging.ChannelId); + if (channel is null) + { + Error(LogSource.Volte, "Invalid guild_logging.guild_id/guild_logging.channel_id configuration. Check your IDs and try again."); + return; + } + + _ = Task.Run(async () => + { + var response = await _http.PostAsync("https://paste.greemdev.net/documents", new StringContent(e.StackTrace, Encoding.UTF8, "text/plain")); + var respObj = JObject.Parse(await response.Content.ReadAsStringAsync()); + var url = $"https://paste.greemdev.net/{respObj.GetValue("key")}.cs"; + var embed = new EmbedBuilder() + .WithErrorColor() + .WithTitle($"Exception at {DateTimeOffset.UtcNow.FormatDate()}, {DateTimeOffset.UtcNow.FormatFullTime()} UTC") + .AddField("Exception Type", e.GetType(), true) + .AddField("Exception Message", e.Message, true) + .WithDescription($"View the full Stack Trace [here]({url})."); + await embed.SendToAsync(channel); + }); + } } } \ No newline at end of file