From afa00c8d4f83274b0cae4d175722d809c1c6f267 Mon Sep 17 00:00:00 2001 From: Toastie Date: Thu, 18 Jul 2024 21:54:45 +1200 Subject: [PATCH] Added 'afk ? command which sets an afk message which will trigger whenever someone pings a user. --- CHANGELOG.md | 7 + src/EllieBot/Modules/Utility/AfkService.cs | 148 ++++++++++++++++++ src/EllieBot/Modules/Utility/Utility.cs | 95 +---------- .../data/strings/commands/commands.en-US.yml | 3 +- 4 files changed, 158 insertions(+), 95 deletions(-) create mode 100644 src/EllieBot/Modules/Utility/AfkService.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index d2be481..c5722a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except date format. a-c-f-r-o +### Added + +- Added: Added a `'afk ?` command which sets an afk message which will trigger whenever someone pings you + - Message will when you type a message in any channel that the bot sees, or after 8 hours, whichever comes first + - The specified message will be prefixed with "The user is afk: " + - The afk message will disappear 30 seconds after being triggered + ## [5.1.4] - 15.07.2024 ### Added diff --git a/src/EllieBot/Modules/Utility/AfkService.cs b/src/EllieBot/Modules/Utility/AfkService.cs new file mode 100644 index 0000000..d41169b --- /dev/null +++ b/src/EllieBot/Modules/Utility/AfkService.cs @@ -0,0 +1,148 @@ +using EllieBot.Common.ModuleBehaviors; + +namespace EllieBot.Modules.Utility; + +public sealed class AfkService : IEService, IReadyExecutor +{ + private readonly IBotCache _cache; + private readonly DiscordSocketClient _client; + private readonly MessageSenderService _mss; + + private static readonly TimeSpan _maxAfkDuration = 8.Hours(); + 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, _maxAfkDuration, overwrite: true); + + async Task StopAfk(SocketMessage socketMessage) + { + try + { + if (socketMessage.Author?.Id == userId) + { + await _cache.RemoveAsync(GetKey(userId)); + _client.MessageReceived -= StopAfk; + + // write the message saying afk status cleared + + if (socketMessage.Channel is ITextChannel tc) + { + _ = Task.Run(async () => + { + var msg = await _mss.Response(tc).Confirm("AFK message cleared!").SendAsync(); + + msg.DeleteAfter(5); + }); + } + + } + + } + catch (Exception ex) + { + Log.Warning("Unexpected error occurred while trying to stop afk: {Message}", ex.Message); + } + } + + _client.MessageReceived += StopAfk; + + + _ = Task.Run(async () => + { + await Task.Delay(_maxAfkDuration); + _client.MessageReceived -= StopAfk; + }); + + 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 is not IUserMessage uMsg || uMsg.Channel is not ITextChannel tc) + return Task.CompletedTask; + + if ((arg.MentionedUsers.Count is 0 or > 3) && uMsg.ReferencedMessage is null) + return Task.CompletedTask; + + _ = Task.Run(async () => + { + var botUser = await tc.Guild.GetCurrentUserAsync(); + + var perms = botUser.GetPermissions(tc); + + if (!perms.SendMessages) + return; + + ulong mentionedUserId = 0; + + if (arg.MentionedUsers.Count <= 3) + { + 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) + { + if (uMsg.ReferencedMessage?.Author?.Id is not ulong repliedUserId) + { + return; + } + + mentionedUserId = repliedUserId; + } + + try + { + var result = await _cache.GetAsync(GetKey(mentionedUserId)); + if (result.TryPickT0(out var msg, out _)) + { + var st = SmartText.CreateFrom(msg); + + st = "The user is AFK: " + st; + + 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/EllieBot/Modules/Utility/Utility.cs b/src/EllieBot/Modules/Utility/Utility.cs index 6ec28b5..7afa03e 100644 --- a/src/EllieBot/Modules/Utility/Utility.cs +++ b/src/EllieBot/Modules/Utility/Utility.cs @@ -7,7 +7,6 @@ using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting; -using EllieBot.Common.ModuleBehaviors; using EllieBot.Modules.Games.Hangman; using EllieBot.Modules.Searches.Common; @@ -702,7 +701,7 @@ public partial class Utility : EllieModule } [Cmd] - public async Task Afk([Leftover] string text) + public async Task Afk([Leftover] string text = "No reason specified.") { var succ = await _afkService.SetAfkAsync(ctx.User.Id, text); @@ -777,96 +776,4 @@ public partial class Utility : EllieModule await Response().Error(ex.Message).SendAsync(); } } -} - -public sealed class AfkService : IEService, 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/EllieBot/data/strings/commands/commands.en-US.yml b/src/EllieBot/data/strings/commands/commands.en-US.yml index 42a69c5..2ce37fb 100644 --- a/src/EllieBot/data/strings/commands/commands.en-US.yml +++ b/src/EllieBot/data/strings/commands/commands.en-US.yml @@ -4571,7 +4571,8 @@ coins: desc: "Page number to show. Starts at 1." afk: desc: |- - Toggles AFK status for yourself with the specified message. + Toggles AFK status for yourself with the specified message. + If you don't provide a message it default to a generic one. 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: