diff --git a/src/NadekoBot.Coordinator/Services/CoordinatorRunner.cs b/src/NadekoBot.Coordinator/Services/CoordinatorRunner.cs index 6246b6760..a5f793a07 100644 --- a/src/NadekoBot.Coordinator/Services/CoordinatorRunner.cs +++ b/src/NadekoBot.Coordinator/Services/CoordinatorRunner.cs @@ -441,9 +441,7 @@ namespace NadekoBot.Coordinator } public string GetConfigText() - { - return File.ReadAllText(CONFIG_PATH); - } + => File.ReadAllText(CONFIG_PATH); public void SetConfigText(string text) { diff --git a/src/NadekoBot/Common/Configs/BotConfig.cs b/src/NadekoBot/Common/Configs/BotConfig.cs index 2801420bb..364f991b8 100644 --- a/src/NadekoBot/Common/Configs/BotConfig.cs +++ b/src/NadekoBot/Common/Configs/BotConfig.cs @@ -77,7 +77,7 @@ note: This setting is primarily used if you're afraid of raids, or you're runnin public bool GroupGreets { get; set; } [Comment(@"Whether the bot will rotate through all specified statuses. -This setting can be changed via .rots command. +This setting can be changed via .ropl command. See RotatingStatuses submodule in Administration.")] public bool RotateStatuses { get; set; } diff --git a/src/NadekoBot/Modules/Administration/Mute/MuteService.cs b/src/NadekoBot/Modules/Administration/Mute/MuteService.cs index dc52043a4..716cee325 100644 --- a/src/NadekoBot/Modules/Administration/Mute/MuteService.cs +++ b/src/NadekoBot/Modules/Administration/Mute/MuteService.cs @@ -16,7 +16,7 @@ public class MuteService : INService { public enum TimerType { Mute, Ban, AddRole } - private static readonly OverwritePermissions denyOverwrite = new(addReactions: PermValue.Deny, + private static readonly OverwritePermissions _denyOverwrite = new(addReactions: PermValue.Deny, sendMessages: PermValue.Deny, attachFiles: PermValue.Deny); @@ -26,7 +26,7 @@ public class MuteService : INService public ConcurrentDictionary GuildMuteRoles { get; } public ConcurrentDictionary> MutedUsers { get; } - public ConcurrentDictionary> Un_Timers { get; } = new(); + public ConcurrentDictionary> UnTimers { get; } = new(); private readonly DiscordSocketClient _client; private readonly DbService _db; @@ -312,7 +312,7 @@ public class MuteService : INService if (!toOverwrite.PermissionOverwrites.Any(x => x.TargetId == muteRole.Id && x.TargetType == PermissionTarget.Role)) { - await toOverwrite.AddPermissionOverwriteAsync(muteRole, denyOverwrite); + await toOverwrite.AddPermissionOverwriteAsync(muteRole, _denyOverwrite); await Task.Delay(200); } @@ -394,12 +394,13 @@ public class MuteService : INService ulong? roleId = null) { //load the unmute timers for this guild - var userUnTimers = Un_Timers.GetOrAdd(guildId, new ConcurrentDictionary<(ulong, TimerType), Timer>()); + var userUnTimers = UnTimers.GetOrAdd(guildId, new ConcurrentDictionary<(ulong, TimerType), Timer>()); //unmute timer to be added var toAdd = new Timer(async _ => { if (type == TimerType.Ban) + { try { RemoveTimerFromDb(guildId, userId, type); @@ -409,24 +410,28 @@ public class MuteService : INService } catch (Exception ex) { - Log.Warning(ex, "Couldn't unban user {0} in guild {1}", userId, guildId); + Log.Warning(ex, "Couldn't unban user {UserId} in guild {GuildId}", userId, guildId); } + } else if (type == TimerType.AddRole) + { try { RemoveTimerFromDb(guildId, userId, type); StopTimer(guildId, userId, type); var guild = _client.GetGuild(guildId); var user = guild?.GetUser(userId); - var role = guild.GetRole(roleId.Value); + var role = guild?.GetRole(roleId.Value); if (guild is not null && user is not null && user.Roles.Contains(role)) await user.RemoveRoleAsync(role); } catch (Exception ex) { - Log.Warning(ex, "Couldn't remove role from user {0} in guild {1}", userId, guildId); + Log.Warning(ex, "Couldn't remove role from user {UserId} in guild {GuildId}", userId, guildId); } + } else + { try { // unmute the user, this will also remove the timer from the db @@ -435,8 +440,9 @@ public class MuteService : INService catch (Exception ex) { RemoveTimerFromDb(guildId, userId, type); // if unmute errored, just remove unmute from db - Log.Warning(ex, "Couldn't unmute user {0} in guild {1}", userId, guildId); + Log.Warning(ex, "Couldn't unmute user {UserId} in guild {GuildId}", userId, guildId); } + } }, null, after, @@ -454,7 +460,7 @@ public class MuteService : INService public void StopTimer(ulong guildId, ulong userId, TimerType type) { - if (!Un_Timers.TryGetValue(guildId, out var userTimer)) + if (!UnTimers.TryGetValue(guildId, out var userTimer)) return; if (userTimer.TryRemove((userId, type), out var removed)) removed.Change(Timeout.Infinite, Timeout.Infinite); diff --git a/src/NadekoBot/Modules/Administration/PlayingRotate/PlayingRotateService.cs b/src/NadekoBot/Modules/Administration/PlayingRotate/PlayingRotateService.cs index e47d2bba6..ec10f28cc 100644 --- a/src/NadekoBot/Modules/Administration/PlayingRotate/PlayingRotateService.cs +++ b/src/NadekoBot/Modules/Administration/PlayingRotate/PlayingRotateService.cs @@ -37,7 +37,8 @@ public sealed class PlayingRotateService : INService, IReadyExecutor { try { - if (!_bss.Data.RotateStatuses) return; + if (!_bss.Data.RotateStatuses) + continue; IReadOnlyList rotatingStatuses; await using (var uow = _db.GetDbContext()) @@ -46,7 +47,7 @@ public sealed class PlayingRotateService : INService, IReadyExecutor } if (rotatingStatuses.Count == 0) - return; + continue; var playingStatus = index >= rotatingStatuses.Count ? rotatingStatuses[index = 0] diff --git a/src/NadekoBot/Modules/Administration/Self/SelfCommands.cs b/src/NadekoBot/Modules/Administration/Self/SelfCommands.cs index 960f608c4..b9566aded 100644 --- a/src/NadekoBot/Modules/Administration/Self/SelfCommands.cs +++ b/src/NadekoBot/Modules/Administration/Self/SelfCommands.cs @@ -243,8 +243,8 @@ public partial class Administration var allShardStrings = statuses.Select(st => { - var stateStr = ConnectionStateToEmoji(st); var timeDiff = DateTime.UtcNow - st.LastUpdate; + var stateStr = ConnectionStateToEmoji(st); var maxGuildCountLength = statuses.Max(x => x.GuildCount).ToString().Length; return $"`{stateStr} " @@ -272,9 +272,9 @@ public partial class Administration var timeDiff = DateTime.UtcNow - status.LastUpdate; return status.ConnectionState switch { - ConnectionState.Connected => "✅", ConnectionState.Disconnected => "🔻", _ when timeDiff > TimeSpan.FromSeconds(30) => " ❗ ", + ConnectionState.Connected => "✅", _ => " ⏳" }; } diff --git a/src/NadekoBot/Modules/Administration/ServerLog/ServerLogCommandService.cs b/src/NadekoBot/Modules/Administration/ServerLog/ServerLogCommandService.cs index c1474bd40..1c7011c5e 100644 --- a/src/NadekoBot/Modules/Administration/ServerLog/ServerLogCommandService.cs +++ b/src/NadekoBot/Modules/Administration/ServerLog/ServerLogCommandService.cs @@ -1,19 +1,19 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; +using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Db; using NadekoBot.Modules.Administration.Services; using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Administration; -public sealed class LogCommandService : ILogCommandService +public sealed class LogCommandService : ILogCommandService, IReadyExecutor { public ConcurrentDictionary GuildLogSettings { get; } private ConcurrentDictionary> PresenceUpdates { get; } = new(); private readonly DiscordSocketClient _client; - private readonly Timer _timerReference; private readonly IBotStrings _strings; private readonly DbService _db; private readonly MuteService _mute; @@ -58,11 +58,6 @@ public sealed class LogCommandService : ILogCommandService GuildLogSettings = configs.ToDictionary(ls => ls.GuildId).ToConcurrent(); } - _timerReference = new(Callback, - null, - TimeSpan.FromSeconds(15), - TimeSpan.FromSeconds(15)); - //_client.MessageReceived += _client_MessageReceived; _client.MessageUpdated += _client_MessageUpdated; _client.MessageDeleted += _client_MessageDeleted; @@ -96,28 +91,34 @@ public sealed class LogCommandService : ILogCommandService #endif } - private async void Callback(object? state) + public async Task OnReadyAsync() { - try +#if GLOBAL_NADEKO + var timer = new PeriodicTimer(TimeSpan.FromSeconds(15)); + while (await timer.WaitForNextTickAsync()) { - var keys = PresenceUpdates.Keys.ToList(); - - await keys.Select(key => - { - if (!((SocketGuild)key.Guild).CurrentUser.GetPermissions(key).SendMessages) - return Task.CompletedTask; - if (PresenceUpdates.TryRemove(key, out var msgs)) - { - var title = GetText(key.Guild, strs.presence_updates); - var desc = string.Join(Environment.NewLine, msgs); - return key.SendConfirmAsync(_eb, title, desc.TrimTo(2048)!); - } + try + { + var keys = PresenceUpdates.Keys.ToList(); + await keys.Select(key => + { + if (!((SocketGuild)key.Guild).CurrentUser.GetPermissions(key).SendMessages) return Task.CompletedTask; - }) - .WhenAll(); + if (PresenceUpdates.TryRemove(key, out var msgs)) + { + var title = GetText(key.Guild, strs.presence_updates); + var desc = string.Join(Environment.NewLine, msgs); + return key.SendConfirmAsync(_eb, title, desc.TrimTo(2048)!); + } + + return Task.CompletedTask; + }) + .WhenAll(); + } + catch { } } - catch { } +#endif } public LogSetting? GetGuildLogSettings(ulong guildId) diff --git a/src/NadekoBot/Modules/Help/Help.cs b/src/NadekoBot/Modules/Help/Help.cs index 5f842b2d4..e4fa12ba5 100644 --- a/src/NadekoBot/Modules/Help/Help.cs +++ b/src/NadekoBot/Modules/Help/Help.cs @@ -362,7 +362,7 @@ public partial class Help : NadekoModule ContentType = "application/json", ContentBody = uploadData, // either use a path provided in the argument or the default one for public nadeko, other/cmds.json - Key = $"cmds/{StatsService.BotVersion}.json", + Key = $"cmds/{StatsService.BOT_VERSION}.json", CannedACL = S3CannedACL.PublicRead }); } @@ -372,11 +372,11 @@ public partial class Help : NadekoModule var versionListString = Encoding.UTF8.GetString(ms.ToArray()); var versionList = JsonSerializer.Deserialize>(versionListString); - if (versionList is not null && !versionList.Contains(StatsService.BotVersion)) + if (versionList is not null && !versionList.Contains(StatsService.BOT_VERSION)) { // save the file with new version added // versionList.Add(StatsService.BotVersion); - versionListString = JsonSerializer.Serialize(versionList.Prepend(StatsService.BotVersion), + versionListString = JsonSerializer.Serialize(versionList.Prepend(StatsService.BOT_VERSION), new JsonSerializerOptions { WriteIndented = true }); // upload the updated version list @@ -395,7 +395,7 @@ public partial class Help : NadekoModule { Log.Warning( "Version {Version} already exists in the version file. " + "Did you forget to increment it?", - StatsService.BotVersion); + StatsService.BOT_VERSION); } } diff --git a/src/NadekoBot/Modules/Utility/Utility.cs b/src/NadekoBot/Modules/Utility/Utility.cs index 45ab7dc9b..553d0f2d2 100644 --- a/src/NadekoBot/Modules/Utility/Utility.cs +++ b/src/NadekoBot/Modules/Utility/Utility.cs @@ -253,7 +253,7 @@ public partial class Utility : NadekoModule await ctx.Channel.EmbedAsync(_eb.Create() .WithOkColor() - .WithAuthor($"NadekoBot v{StatsService.BotVersion}", + .WithAuthor($"NadekoBot v{StatsService.BOT_VERSION}", "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/avatar.png", "https://nadekobot.readthedocs.io/en/latest/") .AddField(GetText(strs.author), _stats.Author, true) diff --git a/src/NadekoBot/Services/Impl/StatsService.cs b/src/NadekoBot/Services/Impl/StatsService.cs index db58d58e4..ffca0ca53 100644 --- a/src/NadekoBot/Services/Impl/StatsService.cs +++ b/src/NadekoBot/Services/Impl/StatsService.cs @@ -1,4 +1,5 @@ #nullable disable +using Humanizer.Localisation; using NadekoBot.Common.ModuleBehaviors; using System.Diagnostics; @@ -6,7 +7,7 @@ namespace NadekoBot.Services; public class StatsService : IStatsService, IReadyExecutor, INService, IDisposable { - public const string BotVersion = "4.0.0"; + public const string BOT_VERSION = "4.0.0"; public string Author => "Kwoth#2452"; @@ -18,28 +19,27 @@ public class StatsService : IStatsService, IReadyExecutor, INService, IDisposabl => MessageCounter / GetUptime().TotalSeconds; public long TextChannels - => Interlocked.Read(ref _textChannels); + => Interlocked.Read(ref textChannels); public long VoiceChannels - => Interlocked.Read(ref _voiceChannels); + => Interlocked.Read(ref voiceChannels); public long MessageCounter - => Interlocked.Read(ref _messageCounter); + => Interlocked.Read(ref messageCounter); public long CommandsRan - => Interlocked.Read(ref _commandsRan); + => Interlocked.Read(ref commandsRan); private readonly Process _currentProcess = Process.GetCurrentProcess(); private readonly DiscordSocketClient _client; private readonly IBotCredentials _creds; private readonly DateTime _started; - private long _textChannels; - private long _voiceChannels; - private long _messageCounter; - private long _commandsRan; + private long textChannels; + private long voiceChannels; + private long messageCounter; + private long commandsRan; - private readonly Timer _botlistTimer; private readonly IHttpClientFactory _httpFactory; public StatsService( @@ -53,17 +53,17 @@ public class StatsService : IStatsService, IReadyExecutor, INService, IDisposabl _httpFactory = factory; _started = DateTime.UtcNow; - _client.MessageReceived += _ => Task.FromResult(Interlocked.Increment(ref _messageCounter)); - cmdHandler.CommandExecuted += (_, _) => Task.FromResult(Interlocked.Increment(ref _commandsRan)); + _client.MessageReceived += _ => Task.FromResult(Interlocked.Increment(ref messageCounter)); + cmdHandler.CommandExecuted += (_, _) => Task.FromResult(Interlocked.Increment(ref commandsRan)); _client.ChannelCreated += c => { var _ = Task.Run(() => { if (c is ITextChannel) - Interlocked.Increment(ref _textChannels); + Interlocked.Increment(ref textChannels); else if (c is IVoiceChannel) - Interlocked.Increment(ref _voiceChannels); + Interlocked.Increment(ref voiceChannels); }); return Task.CompletedTask; @@ -74,9 +74,9 @@ public class StatsService : IStatsService, IReadyExecutor, INService, IDisposabl var _ = Task.Run(() => { if (c is ITextChannel) - Interlocked.Decrement(ref _textChannels); + Interlocked.Decrement(ref textChannels); else if (c is IVoiceChannel) - Interlocked.Decrement(ref _voiceChannels); + Interlocked.Decrement(ref voiceChannels); }); return Task.CompletedTask; @@ -88,8 +88,8 @@ public class StatsService : IStatsService, IReadyExecutor, INService, IDisposabl { var tc = g.Channels.Count(cx => cx is ITextChannel); var vc = g.Channels.Count - tc; - Interlocked.Add(ref _textChannels, tc); - Interlocked.Add(ref _voiceChannels, vc); + Interlocked.Add(ref textChannels, tc); + Interlocked.Add(ref voiceChannels, vc); }); return Task.CompletedTask; }; @@ -100,8 +100,8 @@ public class StatsService : IStatsService, IReadyExecutor, INService, IDisposabl { var tc = g.Channels.Count(cx => cx is ITextChannel); var vc = g.Channels.Count - tc; - Interlocked.Add(ref _textChannels, tc); - Interlocked.Add(ref _voiceChannels, vc); + Interlocked.Add(ref textChannels, tc); + Interlocked.Add(ref voiceChannels, vc); }); return Task.CompletedTask; }; @@ -112,8 +112,8 @@ public class StatsService : IStatsService, IReadyExecutor, INService, IDisposabl { var tc = g.Channels.Count(cx => cx is ITextChannel); var vc = g.Channels.Count - tc; - Interlocked.Add(ref _textChannels, -tc); - Interlocked.Add(ref _voiceChannels, -vc); + Interlocked.Add(ref textChannels, -tc); + Interlocked.Add(ref voiceChannels, -vc); }); return Task.CompletedTask; @@ -125,60 +125,57 @@ public class StatsService : IStatsService, IReadyExecutor, INService, IDisposabl { var tc = g.Channels.Count(cx => cx is ITextChannel); var vc = g.Channels.Count - tc; - Interlocked.Add(ref _textChannels, -tc); - Interlocked.Add(ref _voiceChannels, -vc); + Interlocked.Add(ref textChannels, -tc); + Interlocked.Add(ref voiceChannels, -vc); }); return Task.CompletedTask; }; - - _botlistTimer = new(async _ => - { - if (string.IsNullOrWhiteSpace(_creds.BotListToken)) - return; - try - { - using var http = _httpFactory.CreateClient(); - using var content = new FormUrlEncodedContent(new Dictionary - { - { "shard_count", _creds.TotalShards.ToString() }, - { "shard_id", client.ShardId.ToString() }, - { "server_count", client.Guilds.Count().ToString() } - }); - content.Headers.Clear(); - content.Headers.Add("Content-Type", "application/x-www-form-urlencoded"); - http.DefaultRequestHeaders.Add("Authorization", _creds.BotListToken); - - using (await http.PostAsync( - new Uri($"https://discordbots.org/api/bots/{client.CurrentUser.Id}/stats"), - content)) { } - } - catch (Exception ex) - { - Log.Error(ex, "Error "); - // ignored - } - }, - null, - TimeSpan.FromMinutes(5), - TimeSpan.FromHours(1)); } + public async Task OnReadyAsync() + { + var guilds = _client.Guilds; + textChannels = guilds.Sum(g => g.Channels.Count(cx => cx is ITextChannel)); + voiceChannels = guilds.Sum(g => g.Channels.Count(cx => cx is IVoiceChannel)); + + var timer = new PeriodicTimer(TimeSpan.FromHours(1)); + do + { + if (string.IsNullOrWhiteSpace(_creds.BotListToken)) + continue; + + try + { + using var http = _httpFactory.CreateClient(); + using var content = new FormUrlEncodedContent(new Dictionary + { + { "shard_count", _creds.TotalShards.ToString() }, + { "shard_id", _client.ShardId.ToString() }, + { "server_count", _client.Guilds.Count().ToString() } + }); + content.Headers.Clear(); + content.Headers.Add("Content-Type", "application/x-www-form-urlencoded"); + http.DefaultRequestHeaders.Add("Authorization", _creds.BotListToken); + + using var res = await http.PostAsync( + new Uri($"https://discordbots.org/api/bots/{_client.CurrentUser.Id}/stats"), + content); + } + catch (Exception ex) + { + Log.Error(ex, "Error in botlist post"); + } + } while (await timer.WaitForNextTickAsync()); + } + public TimeSpan GetUptime() => DateTime.UtcNow - _started; public string GetUptimeString(string separator = ", ") { var time = GetUptime(); - return $"{time.Days} days{separator}{time.Hours} hours{separator}{time.Minutes} minutes"; - } - - public Task OnReadyAsync() - { - var guilds = _client.Guilds; - _textChannels = guilds.Sum(g => g.Channels.Count(cx => cx is ITextChannel)); - _voiceChannels = guilds.Sum(g => g.Channels.Count(cx => cx is IVoiceChannel)); - return Task.CompletedTask; + return time.Humanize(3, maxUnit: TimeUnit.Day, minUnit: TimeUnit.Minute); } public double GetPrivateMemory() diff --git a/src/NadekoBot/data/bot.yml b/src/NadekoBot/data/bot.yml index 5af9905a5..16b52251b 100644 --- a/src/NadekoBot/data/bot.yml +++ b/src/NadekoBot/data/bot.yml @@ -81,6 +81,6 @@ prefix: . # and (slightly) reduce the greet spam in those servers. groupGreets: false # Whether the bot will rotate through all specified statuses. -# This setting can be changed via .rots command. +# This setting can be changed via .ropl command. # See RotatingStatuses submodule in Administration. rotateStatuses: false