diff --git a/src/NadekoBot/Modules/Expressions/NadekoExpressions.cs b/src/NadekoBot/Modules/Expressions/NadekoExpressions.cs index b24be77f4..cce639807 100644 --- a/src/NadekoBot/Modules/Expressions/NadekoExpressions.cs +++ b/src/NadekoBot/Modules/Expressions/NadekoExpressions.cs @@ -402,11 +402,10 @@ public partial class NadekoExpressions : NadekoModule } [Cmd] -#if GLOBAL_NADEKO - [OwnerOnly] -#endif public async Task ExprsImport([Leftover] string input = null) { + // todo cooldown on public bot for 1 day, limit 100 + if (!AdminInGuildOrOwnerInDm()) { await Response().Error(strs.expr_insuff_perms).SendAsync(); diff --git a/src/NadekoBot/Modules/Utility/Utility.cs b/src/NadekoBot/Modules/Utility/Utility.cs index edee3c3fd..92b2e9842 100644 --- a/src/NadekoBot/Modules/Utility/Utility.cs +++ b/src/NadekoBot/Modules/Utility/Utility.cs @@ -1,4 +1,4 @@ -#nullable disable +using LinqToDB.Reflection; using NadekoBot.Modules.Utility.Services; using Newtonsoft.Json; using System.Diagnostics; @@ -7,6 +7,8 @@ using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting; +using NadekoBot.Common.ModuleBehaviors; +using NadekoBot.Modules.Games.Hangman; using NadekoBot.Modules.Searches.Common; namespace NadekoBot.Modules.Utility; @@ -41,6 +43,7 @@ public partial class Utility : NadekoModule private readonly IHttpClientFactory _httpFactory; private readonly VerboseErrorsService _veService; private readonly IServiceProvider _services; + private readonly AfkService _afkService; public Utility( DiscordSocketClient client, @@ -50,7 +53,8 @@ public partial class Utility : NadekoModule DownloadTracker tracker, IHttpClientFactory httpFactory, VerboseErrorsService veService, - IServiceProvider services) + IServiceProvider services, + AfkService afkService) { _client = client; _coord = coord; @@ -60,6 +64,7 @@ public partial class Utility : NadekoModule _httpFactory = httpFactory; _veService = veService; _services = services; + _afkService = afkService; } [Cmd] @@ -99,7 +104,7 @@ public partial class Utility : NadekoModule [Cmd] [RequireContext(ContextType.Guild)] - public async Task WhosPlaying([Leftover] string game) + public async Task WhosPlaying([Leftover] string? game) { game = game?.Trim().ToUpperInvariant(); if (string.IsNullOrWhiteSpace(game)) @@ -140,7 +145,7 @@ public partial class Utility : NadekoModule [Cmd] [RequireContext(ContextType.Guild)] [Priority(0)] - public async Task InRole(int page, [Leftover] IRole role = null) + public async Task InRole(int page, [Leftover] IRole? role = null) { if (--page < 0) return; @@ -178,7 +183,7 @@ public partial class Utility : NadekoModule [Cmd] [RequireContext(ContextType.Guild)] [Priority(1)] - public Task InRole([Leftover] IRole role = null) + public Task InRole([Leftover] IRole? role = null) => InRole(1, role); [Cmd] @@ -218,7 +223,7 @@ public partial class Utility : NadekoModule [Cmd] [RequireContext(ContextType.Guild)] - public async Task UserId([Leftover] IGuildUser target = null) + public async Task UserId([Leftover] IGuildUser? target = null) { var usr = target ?? ctx.User; await Response() @@ -248,7 +253,7 @@ public partial class Utility : NadekoModule [Cmd] [RequireContext(ContextType.Guild)] - public async Task Roles(IGuildUser target, int page = 1) + public async Task Roles(IGuildUser? target, int page = 1) { var guild = ctx.Guild; @@ -301,7 +306,7 @@ public partial class Utility : NadekoModule [Cmd] [RequireContext(ContextType.Guild)] - public async Task ChannelTopic([Leftover] ITextChannel channel = null) + public async Task ChannelTopic([Leftover] ITextChannel? channel = null) { if (channel is null) channel = (ITextChannel)ctx.Channel; @@ -382,7 +387,7 @@ public partial class Utility : NadekoModule [BotPerm(GuildPerm.ManageEmojisAndStickers)] [UserPerm(GuildPerm.ManageEmojisAndStickers)] [Priority(0)] - public async Task EmojiAdd(string name, string url = null) + public async Task EmojiAdd(string name, string? url = null) { name = name.Trim(':'); @@ -456,10 +461,10 @@ public partial class Utility : NadekoModule [RequireContext(ContextType.Guild)] [BotPerm(GuildPerm.ManageEmojisAndStickers)] [UserPerm(GuildPerm.ManageEmojisAndStickers)] - public async Task StickerAdd(string name = null, string description = null, params string[] tags) + public async Task StickerAdd(string? name = null, string? description = null, params string[] tags) { string format; - Stream stream = null; + Stream? stream = null; try { @@ -696,6 +701,19 @@ public partial class Utility : NadekoModule await Response().Confirm(strs.verbose_errors_disabled).SendAsync(); } + [Cmd] + public async Task Afk([Leftover] string text) + { + var succ = await _afkService.SetAfkAsync(ctx.User.Id, text); + + if (succ) + { + await Response() + .Confirm(strs.afk_set) + .SendAsync(); + } + } + [Cmd] [NoPublicBot] [OwnerOnly] @@ -759,4 +777,96 @@ public partial class Utility : NadekoModule await Response().Error(ex.Message).SendAsync(); } } +} + +public sealed class AfkService : INService, IReadyExecutor +{ + private readonly IBotCache _cache; + private readonly DiscordSocketClient _client; + private readonly MessageSenderService _mss; + + public AfkService(IBotCache cache, DiscordSocketClient client, MessageSenderService mss) + { + _cache = cache; + _client = client; + _mss = mss; + } + + private static TypedKey GetKey(ulong userId) + => new($"afk:msg:{userId}"); + + public async Task SetAfkAsync(ulong userId, string text) + { + var added = await _cache.AddAsync(GetKey(userId), text, TimeSpan.FromHours(8), overwrite: true); + return added; + } + + public Task OnReadyAsync() + { + _client.MessageReceived += TryTriggerAfkMessage; + + return Task.CompletedTask; + } + + private Task TryTriggerAfkMessage(SocketMessage arg) + { + if (arg.Author.IsBot) + return Task.CompletedTask; + + if (arg.MentionedUsers.Count is 0 or > 2) + return Task.CompletedTask; + + if (arg is not IUserMessage uMsg || uMsg.Channel is not ITextChannel tc) + return Task.CompletedTask; + + + _ = Task.Run(async () => + { + var botUser = await tc.Guild.GetCurrentUserAsync(); + + var perms = botUser.GetPermissions(tc); + + if (!perms.SendMessages) + return; + + ulong mentionedUserId = 0; + foreach (var uid in uMsg.MentionedUserIds) + { + if (uid == arg.Author.Id) + continue; + + if (arg.Content.StartsWith($"<@{uid}>") || arg.Content.StartsWith($"<@!{uid}>")) + { + mentionedUserId = uid; + break; + } + } + + if (mentionedUserId == 0) + return; + + try + { + var result = await _cache.GetAsync(GetKey(mentionedUserId)); + if (result.TryPickT0(out var msg, out _)) + { + var st = SmartText.CreateFrom(msg); + + var toDelete = await _mss.Response(arg.Channel) + .Message(uMsg) + .Text(st) + .Sanitize(false) + .SendAsync(); + + toDelete.DeleteAfter(30); + } + } + catch (HttpException ex) + { + Log.Warning("Error in afk service: {Message}", ex.Message); + } + }); + + return Task.CompletedTask; + } } \ No newline at end of file diff --git a/src/NadekoBot/data/aliases.yml b/src/NadekoBot/data/aliases.yml index 08505c58b..971881dab 100644 --- a/src/NadekoBot/data/aliases.yml +++ b/src/NadekoBot/data/aliases.yml @@ -1410,4 +1410,6 @@ honeypot: coins: - coins - crypto - - cryptos \ No newline at end of file + - cryptos +afk: + - afk \ No newline at end of file diff --git a/src/NadekoBot/data/strings/commands/commands.en-US.yml b/src/NadekoBot/data/strings/commands/commands.en-US.yml index 2f1c49ce0..06a0f33a9 100644 --- a/src/NadekoBot/data/strings/commands/commands.en-US.yml +++ b/src/NadekoBot/data/strings/commands/commands.en-US.yml @@ -4577,4 +4577,14 @@ coins: - '2' params: - page: - desc: "Page number to show. Starts at 1." \ No newline at end of file + desc: "Page number to show. Starts at 1." +afk: + desc: |- + Toggles AFK status for yourself with the specified message. + Anyone @ mentioning you in any server will receive the afk message. + This will only work if the other user's message starts with the mention. + ex: + - '' + params: + - msg: + desc: "The message to send when someone pings you." \ No newline at end of file diff --git a/src/NadekoBot/data/strings/responses/responses.en-US.json b/src/NadekoBot/data/strings/responses/responses.en-US.json index 8e75be33a..c5a4b36ad 100644 --- a/src/NadekoBot/data/strings/responses/responses.en-US.json +++ b/src/NadekoBot/data/strings/responses/responses.en-US.json @@ -1104,5 +1104,6 @@ "queue_search_results": "Type the number of the search result to queue up that track.", "overloads": "Overloads", "honeypot_on": "Honeypot enabled on this channel." , - "honeypot_off": "Honeypot disabled." + "honeypot_off": "Honeypot disabled.", + "afk_set": "AFK message set. Type a message in any channel to clear." }