From fd9c18d953441745154bfa3fb6a71f6432ee94e2 Mon Sep 17 00:00:00 2001 From: skyjoshua Date: Tue, 5 May 2026 16:16:07 +0100 Subject: [PATCH] 0.3.3.0 --- SkyBot/Commands/Info/Help.cs | 12 +-- SkyBot/Commands/Info/Info.cs | 107 +++++++++++++++++++++ SkyBot/Commands/Mods/Ban.cs | 53 +++++++++++ SkyBot/Commands/Mods/Kick.cs | 83 ++++++++++++++++ SkyBot/Commands/RP/Emote.cs | 163 ++++++++++++++++++++++++++++++++ SkyBot/Commands/RP/Marriage.cs | 4 +- SkyBot/Helpers/EmbedStyles.cs | 1 + SkyBot/Helpers/MessageHelper.cs | 27 +++++- SkyBot/Helpers/PlanetHelper.cs | 33 +++++++ SkyBot/Helpers/UserHelper.cs | 29 ++++++ SkyBot/SkyBot.csproj | 2 +- 11 files changed, 504 insertions(+), 10 deletions(-) create mode 100644 SkyBot/Commands/Info/Info.cs create mode 100644 SkyBot/Commands/Mods/Ban.cs create mode 100644 SkyBot/Commands/Mods/Kick.cs create mode 100644 SkyBot/Commands/RP/Emote.cs create mode 100644 SkyBot/Helpers/PlanetHelper.cs create mode 100644 SkyBot/Helpers/UserHelper.cs diff --git a/SkyBot/Commands/Info/Help.cs b/SkyBot/Commands/Info/Help.cs index 6a12c81..144776b 100644 --- a/SkyBot/Commands/Info/Help.cs +++ b/SkyBot/Commands/Info/Help.cs @@ -57,7 +57,7 @@ namespace SkyBot.Commands builder.embed.HideChangePageArrows = true; // Home page - builder.AddPage("✦ Help Menu", $"Prefix: {Config.Prefix}"); + builder.AddPage("✦ Help Menu", $"Prefix: {Config.Prefix} | <> = required [] = optional"); builder.AddRow() .AddText("Select a Category") .WithStyles(EmbedStyles.LabelText) @@ -77,15 +77,15 @@ namespace SkyBot.Commands foreach (var (categoryName, cmds, chunkIndex, totalChunks) in allChunks) { string? footer = totalChunks > 1 - ? $"Page {chunkIndex + 1}/{totalChunks} | Prefix: {Config.Prefix}" - : $"Prefix: {Config.Prefix}"; + ? $"Page {chunkIndex + 1}/{totalChunks} | Prefix: {Config.Prefix} | <> = required [] = optional" + : $"Prefix: {Config.Prefix} | <> = required [] = optional"; builder.AddPage($"✦ {categoryName.ToTitleCase()} Commands", footer); foreach (var cmd in cmds) { builder.AddRow() - .AddButton(cmd.Name) + .AddButton(cmd.Name.ToTitleCase()) .WithStyles(EmbedStyles.CommandBtn) .OnClickGoToEmbedPage(cmdDetailPage[cmd.Name]) .CloseRow(); @@ -118,7 +118,7 @@ namespace SkyBot.Commands // Command detail pages foreach (var cmd in allCmds) { - builder.AddPage($"✦ {cmd.Name.ToTitleCase()}", $"Prefix: {Config.Prefix}"); + builder.AddPage($"✦ {cmd.Name.ToTitleCase()}", $"Prefix: {Config.Prefix} | <> = required [] = optional"); builder.AddRow() .AddText("Description", cmd.Description) @@ -141,7 +141,7 @@ namespace SkyBot.Commands if (cmd.SubCommands.Length > 0) { builder.AddRow() - .AddText("Sub-commands", string.Join(", ", cmd.SubCommands.Select(s => s.ToTitleCase()))) + .AddText("Sub-commands", string.Join(", ", cmd.SubCommands.Select(s => s))) .CloseRow(); } diff --git a/SkyBot/Commands/Info/Info.cs b/SkyBot/Commands/Info/Info.cs new file mode 100644 index 0000000..9accbf6 --- /dev/null +++ b/SkyBot/Commands/Info/Info.cs @@ -0,0 +1,107 @@ +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Text; +using System.Text.RegularExpressions; +using SkyBot.Helpers; +using SkyBot.Models; +using Superpower.Model; +using Valour.Sdk.Models; +using Valour.Sdk.Models.Messages.Embeds; +using Valour.Sdk.Models.Messages.Embeds.Styles; +using Valour.Sdk.Models.Messages.Embeds.Styles.Basic; +using Valour.Sdk.Models.Messages.Embeds.Styles.Flex; +using Valour.Shared.Models; + +namespace SkyBot.Commands +{ + public class Info : ICommand + { + public string Name => "info"; + public string[] Aliases => []; + public string Description => "Shows info about a Planet or User"; + public string Category => "Info"; + public string Usage => "info [user]"; + public string[] SubCommands => ["planet", "user"]; + + public async Task Execute(CommandContext ctx) + { + if (ctx.Args.Length == 0) + { + await MessageHelper.ReplyAsync(ctx, "Please specify `planet` or `user`."); + return; + } + + switch (ctx.Args[0].ToLower()) + { + case "user": + case "u": + await HandleUserInfo(ctx); + break; + + case "planet": + case "p": + await HandlePlanetInfo(ctx); + break; + + default: + await MessageHelper.ReplyAsync(ctx, "Invalid Option. Use either `planet` or `user`."); + break; + } + } + + private async Task HandleUserInfo(CommandContext ctx) + { + long memberId = ctx.Message.Mentions?.Any() == true + ? ctx.Message.Mentions.First().TargetId + : long.TryParse(ctx.Args.ElementAtOrDefault(1), out var parsed) + ? parsed + : ctx.Member.Id; + PlanetMember member = await ctx.Planet.FetchMemberAsync(memberId); + if (member is null) + { + await MessageHelper.ReplyAsync(ctx, "Could not find member."); + return; + } + + var b = new EmbedBuilder() + .AddPage($"{member.Name.Trim()}'s Info") + .WithTitleStyles(new TextColor(member.PrimaryRole.Color)) + // .AddMedia(MessageAttachmentType.Image, 64, 64, "image/webp", "avatar.webp", member.GetAvatar(AvatarFormat.Webp64)) + // .WithStyles( + // new Position(right: new Size(Unit.Pixels, 8), top: new Size(Unit.Pixels, 8)), + // new Width(new Size(Unit.Pixels, 64)), + // new Height(new Size(Unit.Pixels, 64)), + // new BorderRadius(new Size(Unit.Percent, 50)) + // ) + .AddRow().AddText("Member ID", member.Id.ToString()).WithStyles(new TextColor("#ff9d00")).CloseRow() + .AddRow().AddText("User ID", member.UserId.ToString()).WithStyles(new TextColor("#ff9d00")).CloseRow() + .AddRow().AddText("Nickname", string.IsNullOrWhiteSpace(member.Nickname) ? "None" : member.Nickname).WithStyles(new TextColor(string.IsNullOrWhiteSpace(member.Nickname) ? "#ffed4a" : member.PrimaryRole.Color)).CloseRow() + .AddRow().AddText("Subscription", string.IsNullOrWhiteSpace(member.User.SubscriptionType) ? "None" : member.User.SubscriptionType).WithStyles(new TextColor(member.User.HasStargazer ? member.User.GetStarColor1() : "#ffed4a")).CloseRow() + .AddRow().AddText("Status", string.IsNullOrWhiteSpace(member.Status) ? "None" : member.Status).WithStyles(new TextColor("#ffed4a")).CloseRow() + .AddRow().AddText("Primary Role", member.PrimaryRole.Name).WithStyles(new TextColor(member.PrimaryRole.Color)).CloseRow() + .AddRow().AddText("Roles", string.Join(", ", member.Roles.Select(r => r.Name))).WithStyles(new TextColor(member.Roles[Random.Shared.Next(member.Roles.Count)].Color)).CloseRow() + .AddRow().AddText("Account Created", member.User.TimeJoined.ToString()).WithStyles(new TextColor("#979797")).CloseRow(); + + await MessageHelper.ReplyAsync(ctx, null, b.embed); + } + + private async Task HandlePlanetInfo(CommandContext ctx) + { + PlanetMember pOwner = await ctx.Planet.FetchMemberByUserAsync(ctx.Planet.OwnerId); + EmbedBuilder b = new EmbedBuilder() + .AddPage($"{ctx.Planet.Name}'s Info") + .AddRow().AddText("Planet ID", ctx.Planet.Id.ToString()).CloseRow() + .AddRow().AddText("Planet Description", ctx.Planet.Description).CloseRow() + .AddRow().AddText("State", ctx.Planet.Public ? ctx.Planet.Discoverable ? "Public (Discoverable)" : "Public (Not Discoverable)" : "Private").CloseRow() + .AddRow().AddText("NSFW", ctx.Planet.Nsfw.ToString()).CloseRow() + .AddRow().AddText("Owner Name (ID)", $"{pOwner.Name} ({pOwner.Id})").CloseRow() + .AddRow().AddText("Members", ctx.Planet.Members.Count.ToString()) + .AddText("Channels", ctx.Planet.Channels.Count.ToString()) + .AddText("Roles", ctx.Planet.Roles.Count.ToString()).CloseRow() + ; + + await MessageHelper.ReplyAsync(ctx, null, b.embed); + + } + } +} \ No newline at end of file diff --git a/SkyBot/Commands/Mods/Ban.cs b/SkyBot/Commands/Mods/Ban.cs new file mode 100644 index 0000000..2ffca80 --- /dev/null +++ b/SkyBot/Commands/Mods/Ban.cs @@ -0,0 +1,53 @@ + +using SkyBot.Helpers; +using SkyBot.Models; +using Valour.Sdk.Models; +using Valour.Shared.Authorization; + +namespace SkyBot.Commands +{ + public class Ban : ICommand + { + public string Name => "ban"; + public string[] Aliases => []; + public string Description => "Bans a member from the planet."; + public string Category => "Mod"; + public string Usage => "ban <@member> [reason]"; + public string[] SubCommands => []; + + public async Task Execute(CommandContext ctx) + { + if (!await PermissionHelper.HasPermAsync(ctx.Member, [PlanetPermissions.Ban])) + { + await MessageHelper.ReplyAsync(ctx, $"You dont have permission to execute this command."); + return; + } + + if (!await PermissionHelper.HasPermAsync(ctx.Planet.MyMember, [PlanetPermissions.Ban])) + { + await MessageHelper.ReplyAsync(ctx, $"I don't have permission to ban members."); + return; + } + + if (ctx.Message.Mentions?.Any() != true && ctx.Args.Length < 1) + { + await MessageHelper.ReplyAsync(ctx, "Mention a member or enter their ID to ban them."); + return; + } + + long? targetId = ctx.Message.Mentions?.Any() == true ? ctx.Message.Mentions.First().TargetId : long.TryParse(ctx.Args.ElementAtOrDefault(0), out long parsed) ? parsed : null; + if (targetId is null) {await MessageHelper.ReplyAsync(ctx, "Could not find user."); return;}; + + User victim = await ctx.Client.UserService.FetchUserAsync(targetId.Value); + if (victim is null) {await MessageHelper.ReplyAsync(ctx, "Could not find user."); return;}; + + DateTime? expires = ctx.Args.Select(MessageHelper.ParseDuration).FirstOrDefault(x => x != null); + + string reason = string.Join(" ", ctx.Args.Skip(1).Where(a => MessageHelper.ParseDuration(a) is null)); + if (string.IsNullOrWhiteSpace(reason)) reason = "No reason provided"; + + await ctx.Planet.BanAsync(victim.Id, reason, expires); + await MessageHelper.ReplyAsync(ctx, $"Banned {victim.NameAndTag}. Reason: `{reason}`. Expires: `{(expires.HasValue ? expires + " UTC" : "Never")}`"); + } + } +} \ No newline at end of file diff --git a/SkyBot/Commands/Mods/Kick.cs b/SkyBot/Commands/Mods/Kick.cs new file mode 100644 index 0000000..4917a7d --- /dev/null +++ b/SkyBot/Commands/Mods/Kick.cs @@ -0,0 +1,83 @@ +using System.Drawing; +using SkyBot.Helpers; +using SkyBot.Models; +using Valour.Sdk.Models; +using Valour.Sdk.Models.Messages.Embeds; +using Valour.Sdk.Models.Messages.Embeds.Styles.Basic; +using Valour.Shared.Authorization; + +namespace SkyBot.Commands +{ + public class Kick : ICommand + { + public string Name => "Kick"; + public string[] Aliases => []; + public string Description => "Kicks a member from the planet."; + public string Category => "Mod"; + public string Usage => "kick <@member> [reason]"; + public string[] SubCommands => []; + + public async Task Execute(CommandContext ctx) + { + if (!await PermissionHelper.HasPermAsync(ctx.Member, [PlanetPermissions.Kick])) + { + await MessageHelper.ReplyAsync(ctx, "You don't have permission to execute this command."); + return; + } + + if (!await PermissionHelper.HasPermAsync(ctx.Planet.MyMember, [PlanetPermissions.Kick])) + { + await MessageHelper.ReplyAsync(ctx, "I don't have permission to kick members."); + return; + } + + if (ctx.Message.Mentions?.Any() != true && ctx.Args.Length < 1) + { + await MessageHelper.ReplyAsync(ctx, "Mention a member or enter their ID to kick them."); + return; + } + + long? targetId = ctx.Message.Mentions?.Any() == true ? ctx.Message.Mentions.First().TargetId : long.TryParse(ctx.Args.ElementAtOrDefault(0), out long parsed) ? parsed : null; + if (targetId is null) {await MessageHelper.ReplyAsync(ctx, "Could not find member."); return;}; + + PlanetMember offender = await ctx.Planet.FetchMemberAsync(targetId.Value); + if (offender is null) {await MessageHelper.ReplyAsync(ctx, "Could not find member."); return;}; + + string reason = string.Join(" ", ctx.Args.Skip(1)); + if (string.IsNullOrWhiteSpace(reason)) reason = "No reason provided"; + + EmbedBuilder p = new EmbedBuilder() + .AddPage("Member Kick", DateTime.UtcNow.ToString()) + .WithTitleStyles(new TextColor("#ff9900")) + .WithFooterStyles(new TextColor("#555555")) + .AddRow() + .AddText("Offender", offender.User.NameAndTag) + .WithStyles(new TextColor("#ff9900")) + .CloseRow() + .AddRow() + .AddText("Issuer", ctx.Member.User.NameAndTag) + .WithStyles(new TextColor("#ff9900")) + .CloseRow() + .AddText("Reason", reason) + .WithStyles(new TextColor("#ff9900")) + .CloseRow(); + + EmbedBuilder d = new EmbedBuilder() + .AddPage("Kicked from Planet", DateTime.UtcNow.ToString()) + .WithTitleStyles(new TextColor("#ff9900")) + .WithFooterStyles(new TextColor("#555555")) + .AddRow() + .AddText("Planet (ID)", $"{ctx.Planet.Name} ({ctx.Planet.Id})") + .WithStyles(new TextColor("#ff9900")) + .CloseRow() + .AddRow() + .AddText("Reason", reason) + .WithStyles(new TextColor("#ff9900")) + .CloseRow(); + + await offender.User.SendDirectMessageAsync(ctx.Client, null, d.embed); + await offender.DeleteAsync(); + await MessageHelper.ReplyAsync(ctx, null, p.embed); + } + }; +} \ No newline at end of file diff --git a/SkyBot/Commands/RP/Emote.cs b/SkyBot/Commands/RP/Emote.cs new file mode 100644 index 0000000..d24cdc2 --- /dev/null +++ b/SkyBot/Commands/RP/Emote.cs @@ -0,0 +1,163 @@ +using System.Text.Json; +using SkyBot.Helpers; +using SkyBot.Models; +using Valour.Sdk.Models; +using Valour.Shared.Models; + +namespace SkyBot.Commands +{ + public class Emote : ICommand + { + public string Name => "emote"; + public string[] Aliases => ["e", "action"]; + public string Description => "RP emote actions."; + public string Category => "RP"; + public string Usage => "emote [@user]"; + public string[] SubCommands => ["angry", "baka", "bite", "blowkiss", "blush", "bonk", "carry", "clap", "cry", "cuddle", "dance", "facepalm", "happy", "holdhand", "hug", "kiss", "laugh", "lurk", "nom", "nya", "pat", "poke", "pout", "punch", "run", "shocked", "sleep", "smug", "spin", "tableflip", "teehee", "tickle", "wave", "wink", "yawn"]; + + private static readonly HttpClient _http = new() + { + DefaultRequestHeaders = { { "User-Agent", "SkyBot/1.0 (https://skyjoshua.xyz, https://valour.gg)" } } + }; + + public async Task Execute(CommandContext ctx) + { + switch (ctx.Args.ElementAtOrDefault(0)?.ToLower()) + { + case "angry": await SendAction(ctx, "angry", "{0} is angry at {1}! 😠", "{0} is angry! 😠", false); break; + case "baka": await SendAction(ctx, "baka", "{0} calls {1} a baka!", "Please mention someone who is a baka!", true); break; + case "bite": await SendAction(ctx, "bite", "{0} bites {1}!", "Please mention someone to bite!", true); break; + case "blowkiss": await SendAction(ctx, "blowkiss", "{0} blows a kiss to {1}!", "Please mention someone to blow a kiss too!", true); break; + case "blush": await SendAction(ctx, "blush", "{0} is blushing at {1}!", "{0} is blushing!", false); break; + case "bonk": await SendAction(ctx, "bonk", "{0} bonks {1}!", "Please mention someone to bonk!", true); break; + case "carry": await SendAction(ctx, "carry", "{0} carries {1}!", "Please mention someone to carry!", true); break; + case "clap": await SendAction(ctx, "clap", "{0} claps for {1}!", "{0} is clapping!", false); break; + case "cry": await SendAction(ctx, "cry", "{0} is crying because of {1}!", "{0} is crying!", false); break; + case "cuddle": await SendAction(ctx, "cuddle", "{0} cuddles with {1}!", "Please mention someone to cuddle!", true); break; + case "dance": await SendAction(ctx, "dance", "{0} dances with {1}!", "{0} is dancing!", false); break; + case "facepalm": await SendAction(ctx, "facepalm", "{0} facepalms at {1}!", "{0} facepalms!", false); break; + case "happy": await SendAction(ctx, "happy", "{0} is happy with {1}!", "{0} is happy!", false); break; + case "holdhand": await SendAction(ctx, "handhold", "{0} holds hands with {1}!", "Please mention someone to hold hands with!", true); break; + case "hug": await SendAction(ctx, "hug", "{0} hugs {1}!", "Please mention someone to hug!", true); break; + case "kiss": await SendAction(ctx, "kiss", "{0} kisses {1}!", "Please mention someone to kiss!", true); break; + case "laugh": await SendAction(ctx, "laugh", "{0} laughs at {1}!", "{0} is laughing!", false); break; + case "lurk": await SendAction(ctx, "lurk", "{0} lurks around {1}!", "{0} is lurking!", false); break; + case "nom": await SendAction(ctx, "nom", "{0} noms on {1}!", "{0} is nomming!", false); break; + case "nya": await SendAction(ctx, "nya", "{0} nyas at {1}!", "{0} nyas!", false); break; + case "pat": await SendAction(ctx, "pat", "{0} gives {1} headpats!", "Please mention someone to give heatpats too!", true); break; + case "poke": await SendAction(ctx, "poke", "{0} pokes {1}!", "Please mention someone to poke!", true); break; + case "pout": await SendAction(ctx, "pout", "{0} pouts at {1}!", "{0} is pouting!", false); break; + case "punch": await SendAction(ctx, "punch", "{0} punches {1}!", "Please mention someone to punch!", true); break; + case "run": await SendAction(ctx, "run", "{0} runs away from {1}!", "{0} runs away!", false); break; + case "shocked": await SendAction(ctx, "shocked", "{0} is shocked by {1}!", "{0} is shocked!", false); break; + case "sleep": await SendAction(ctx, "sleep", "{0} falls asleep on {1}!", "{0} is sleeping! zzz...", false); break; + case "smug": await SendAction(ctx, "smug", "{0} looks smug at {1}!", "{0} is feeling smug!", false); break; + case "spin": await SendAction(ctx, "spin", "{0} spins {1} around!", "{0} is spinning!", false); break; + case "tableflip": await SendAction(ctx, "tableflip", "{0} flips the table at {1}! (╯°□°)╯︵ ┻━┻", "{0} flips the table! (╯°□°)╯︵ ┻━┻", false); break; + case "teehee": await SendAction(ctx, "teehee", "{0} teehees at {1}!", "{0} teehees~", false); break; + case "tickle": await SendAction(ctx, "tickle", "{0} tickles {1}!", "Please mention someone to tickle!", true); break; + case "wave": await SendAction(ctx, "wave", "{0} waves at {1}!", "{0} waves!", false); break; + case "wink": await SendAction(ctx, "wink", "{0} winks at {1}! ;)", "{0} winks! ;)", false); break; + case "yawn": await SendAction(ctx, "yawn", "{0} yawns at {1}!", "{0} is yawning!", false); break; + default: + await MessageHelper.ReplyAsync(ctx, $"Unknown emote. Usage: `{Config.Prefix}emote `\nActions: {string.Join(", ", SubCommands)}"); + break; + } + } + + private static async Task SendAction(CommandContext ctx, string apiAction, string withTarget, string withoutTarget, bool requiresTarget = false) + { + string? target = null; + if (ctx.Message.ReplyToId is not null) + { + var replied = await ctx.Message.FetchReplyMessageAsync(); + if (replied is not null) + { + var author = await replied.FetchAuthorAsync(); + if (author is not null) + target = author.Name; + } + } + if (target is null && ctx.Args.Length > 1) + target = string.Join(" ", ctx.Args[1..]); + + if (requiresTarget && target is null) + { + await MessageHelper.ReplyAsync(ctx, withoutTarget); + return; + } + + await ctx.Channel.SendIsTyping(); + + string json; + try + { + json = await _http.GetStringAsync($"https://nekos.best/api/v2/{apiAction}"); + } + catch + { + await MessageHelper.ReplyAsync(ctx, $"Could not fetch a {apiAction} gif. Try again later."); + return; + } + + using var doc = JsonDocument.Parse(json); + string gifUrl = doc.RootElement.GetProperty("results")[0].GetProperty("url").GetString()!; + + byte[] gifBytes; + try + { + gifBytes = await _http.GetByteArrayAsync(gifUrl); + } + catch + { + await MessageHelper.ReplyAsync(ctx, $"Could not download the {apiAction} gif. Try again later."); + return; + } + + int width = 0, height = 0; + if (gifBytes.Length >= 10) + { + width = gifBytes[6] | (gifBytes[7] << 8); + height = gifBytes[8] | (gifBytes[9] << 8); + } + + string cdnUrl; + try + { + using var form = new MultipartFormDataContent(); + using var fileContent = new ByteArrayContent(gifBytes); + fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/gif"); + form.Add(fileContent, "file", $"{apiAction}.gif"); + + var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse("upload/image", form); + if (!uploadResult.Success) + { + await MessageHelper.ReplyAsync(ctx, $"Could not upload the {apiAction} gif. Try again later."); + return; + } + cdnUrl = uploadResult.Data!; + } + catch + { + await MessageHelper.ReplyAsync(ctx, $"Could not upload the {apiAction} gif. Try again later."); + return; + } + + string sender = ctx.Member.Nickname ?? ctx.Member.User?.Name ?? "Unknown"; + string text = target is not null + ? string.Format(withTarget, sender, target) + : string.Format(withoutTarget, sender); + + var attachment = new MessageAttachment(MessageAttachmentType.Image) + { + Location = cdnUrl, + MimeType = "image/gif", + FileName = $"{apiAction}.gif", + Width = width, + Height = height + }; + + await MessageHelper.ReplyAsync(ctx, text, attachments: [attachment]); + } + } +} diff --git a/SkyBot/Commands/RP/Marriage.cs b/SkyBot/Commands/RP/Marriage.cs index a9e3554..8ce76f3 100644 --- a/SkyBot/Commands/RP/Marriage.cs +++ b/SkyBot/Commands/RP/Marriage.cs @@ -9,7 +9,7 @@ namespace SkyBot.Commands public string[] Aliases => ["marry"]; public string Description => "Marriage system — propose, check status, or divorce."; public string Category => "RP"; - public string Usage => "marriage "; + public string Usage => "marriage "; public string[] SubCommands => ["propose", "status", "divorce", "force"]; public async Task Execute(CommandContext ctx) @@ -130,7 +130,7 @@ namespace SkyBot.Commands long partnerId = marriage.SpouseId; DateTimeOffset dt = DateTimeOffset.FromUnixTimeMilliseconds(marriage.MarriedAt); string marriedAt = $"{dt.OrdinalDay()} {dt:MMMM yyyy HH:mm} UTC"; - await MessageHelper.ReplyAsync(ctx, $"{name} is married to «@u-{partnerId}»!\nThey got married: {marriedAt}"); + await MessageHelper.ReplyAsync(ctx, $"{name} is married to «@u-{partnerId}»!\nThey got married: `{marriedAt}`"); } } diff --git a/SkyBot/Helpers/EmbedStyles.cs b/SkyBot/Helpers/EmbedStyles.cs index 876cfa3..5186256 100644 --- a/SkyBot/Helpers/EmbedStyles.cs +++ b/SkyBot/Helpers/EmbedStyles.cs @@ -10,6 +10,7 @@ namespace SkyBot.Helpers private static readonly Size PadH = new(Unit.Pixels, 6); private static readonly Size FitContent = new(Unit.FitContent); + public static StyleBase[] LabelText => [ new TextColor("#a0a0b8"), new FontWeight(600), diff --git a/SkyBot/Helpers/MessageHelper.cs b/SkyBot/Helpers/MessageHelper.cs index 0319630..d329928 100644 --- a/SkyBot/Helpers/MessageHelper.cs +++ b/SkyBot/Helpers/MessageHelper.cs @@ -28,7 +28,7 @@ namespace SkyBot.Helpers return $"{dt.Day}{suffix}"; } - public static async Task> ReplyAsync(CommandContext ctx, string? content, Embed? embed = null, bool reply = false) + public static async Task> ReplyAsync(CommandContext ctx, string? content, Embed? embed = null, bool reply = false, IEnumerable? attachments = null) { long? replyToId = reply ? ctx.Message.ReplyToId : ctx.Message.Id; @@ -46,6 +46,12 @@ namespace SkyBot.Helpers if (embed is not null) msg.SetEmbed(embed); + if (attachments is not null) + { + msg.Attachments ??= []; + msg.Attachments.AddRange(attachments); + } + return await ctx.Client.MessageService.SendMessage(msg); } @@ -55,6 +61,25 @@ namespace SkyBot.Helpers message.Content = content; return await channel.Planet.Node.PutAsyncWithResponse($"api/messages/{message.Id}", message); } + + public static DateTime? ParseDuration(string input) + { + if (string.IsNullOrWhiteSpace(input)) return null; + + var unit = input[^1]; + if (!int.TryParse(input[..^1], out int value)) return null; + + return unit switch + { + 'm' => DateTime.UtcNow.AddMinutes(value), + 'h' => DateTime.UtcNow.AddHours(value), + 'd' => DateTime.UtcNow.AddDays(value), + 'w' => DateTime.UtcNow.AddDays(value * 7), + 'M' => DateTime.UtcNow.AddMonths(value), + 'y' => DateTime.UtcNow.AddYears(value), + _ => null + }; + } }; }; \ No newline at end of file diff --git a/SkyBot/Helpers/PlanetHelper.cs b/SkyBot/Helpers/PlanetHelper.cs new file mode 100644 index 0000000..acc46c9 --- /dev/null +++ b/SkyBot/Helpers/PlanetHelper.cs @@ -0,0 +1,33 @@ +using Valour.Sdk.Models; +using Valour.Shared; + +namespace SkyBot.Helpers +{ + public static class PlanetHelper + { + public static Task> BanAsync(this Planet planet, long targetUserId, string reason, DateTime? expires = null) + { + var ban = new PlanetBan(planet.Client) + { + PlanetId = planet.Id, + IssuerId = planet.Client.Me.Id, + TargetId = targetUserId, + Reason = reason, + TimeCreated = DateTime.UtcNow, + TimeExpires = expires + }; + return ban.CreateAsync(); + } + + public static async Task FindBanAsync(this Planet planet, long targetUserId) + { + var engine = planet.GetBanQueryEngine(); + await foreach (var ban in engine) + { + if (ban.TargetId == targetUserId) + return ban; + } + return null; + } + } +} diff --git a/SkyBot/Helpers/UserHelper.cs b/SkyBot/Helpers/UserHelper.cs new file mode 100644 index 0000000..715dd9f --- /dev/null +++ b/SkyBot/Helpers/UserHelper.cs @@ -0,0 +1,29 @@ +using SkyBot.Models; +using Valour.Sdk.Client; +using Valour.Sdk.Models; +using Valour.Sdk.Models.Messages.Embeds; +using Valour.Shared; + +namespace SkyBot.Helpers +{ + public static class UserHelper + { + public static async Task> SendDirectMessageAsync(this User user, ValourClient client, string? content, Embed? embed = null) + { + var channelResult = await client.PrimaryNode.GetJsonAsync($"api/channels/direct/byUser/{user.Id}"); + if (!channelResult.Success) + return new TaskResult(false, "Could not open DM channel."); + + var msg = new Message(client) + { + Content = content, + ChannelId = channelResult.Data.Id, + AuthorUserId = client.Me.Id, + Fingerprint = Guid.NewGuid().ToString() + }; + msg.SetEmbed(embed); + + return await client.MessageService.SendMessage(msg); + } + } +} diff --git a/SkyBot/SkyBot.csproj b/SkyBot/SkyBot.csproj index 89faa4c..e44f24c 100644 --- a/SkyBot/SkyBot.csproj +++ b/SkyBot/SkyBot.csproj @@ -5,7 +5,7 @@ net10.0 enable enable - 0.3.2.0 + 0.3.3.0