From 7a7ea3fc264e27dffabdddcf0af36216a3a37520 Mon Sep 17 00:00:00 2001 From: skyjoshua Date: Fri, 20 Mar 2026 08:04:00 +0000 Subject: [PATCH 1/2] cat. --- SkyBot/Commands/Dev/Delete.cs | 46 +++++++++++++ SkyBot/Commands/Fun/Cat.cs | 101 ++++++++++++++++++++++++++++ SkyBot/Commands/Fun/T9Decode.cs | 114 ++++++++++++++++++++++++++++++++ SkyBot/Commands/Fun/T9Encode.cs | 108 ++++++++++++++++++++++++++++++ 4 files changed, 369 insertions(+) create mode 100644 SkyBot/Commands/Dev/Delete.cs create mode 100644 SkyBot/Commands/Fun/Cat.cs create mode 100644 SkyBot/Commands/Fun/T9Decode.cs create mode 100644 SkyBot/Commands/Fun/T9Encode.cs diff --git a/SkyBot/Commands/Dev/Delete.cs b/SkyBot/Commands/Dev/Delete.cs new file mode 100644 index 0000000..28ca165 --- /dev/null +++ b/SkyBot/Commands/Dev/Delete.cs @@ -0,0 +1,46 @@ +using System.Collections.Concurrent; +using SkyBot.Helpers; +using SkyBot.Models; +using Valour.Sdk.Client; +using Valour.Sdk.Models; + +namespace SkyBot.Commands +{ + public class Delete : ICommand + { + public string Name => "delete"; + public string[] Aliases => ["del"]; + public string Description => "Delete a bot message"; + public string Section => "Dev"; + public string Usage => "reply -> delete"; + + public async Task Execute(CommandContext ctx) + { + ConcurrentDictionary channelCache = ctx.ChannelCache; + ValourClient client = ctx.Client; + long channelId = ctx.ChannelId; + PlanetMember member = ctx.Member; + Message message = ctx.Message; + + if (channelCache.TryGetValue(channelId, out var channel)) + { + if (!PermissionHelper.IsOwner(member)) + { + await MessageHelper.ReplyAsync(ctx, channel, "This is a Dev only command."); + return; + } + + if (message.ReplyToId == null) + { + await MessageHelper.ReplyAsync(ctx, channel, "Please reply to a message."); + return; + } + + if (client.Cache.Messages.TryGet(message.ReplyToId.Value, out var msg)) + { + await msg.DeleteAsync(); + } + } + } + } +} diff --git a/SkyBot/Commands/Fun/Cat.cs b/SkyBot/Commands/Fun/Cat.cs new file mode 100644 index 0000000..a721fd6 --- /dev/null +++ b/SkyBot/Commands/Fun/Cat.cs @@ -0,0 +1,101 @@ +using System.Collections.Concurrent; +using System.Text.Json; +using SkyBot.Helpers; +using SkyBot.Models; +using Valour.Sdk.Models; +using Valour.Shared.Models; + +namespace SkyBot.Commands +{ + public class Cat : ICommand + { + public string Name => "cat"; + public string[] Aliases => ["kitty", "meow"]; + public string Description => "Posts a random cat picture."; + public string Section => "Fun"; + public string Usage => "cat"; + + private static readonly HttpClient _http = new(); + + public async Task Execute(CommandContext ctx) + { + ConcurrentDictionary channelCache = ctx.ChannelCache; + long channelId = ctx.ChannelId; + + if (!channelCache.TryGetValue(channelId, out var channel)) return; + + await channel.SendIsTyping(); + + // Fetch a random cat from TheCatAPI + string json; + try + { + json = await _http.GetStringAsync("https://api.thecatapi.com/v1/images/search"); + } + catch + { + await MessageHelper.ReplyAsync(ctx, channel, "😿 Could not fetch a cat image. Try again later."); + return; + } + + using var doc = JsonDocument.Parse(json); + var root = doc.RootElement[0]; + + string catUrl = root.GetProperty("url").GetString()!; + int width = root.TryGetProperty("width", out var w) ? w.GetInt32() : 0; + int height = root.TryGetProperty("height", out var h) ? h.GetInt32() : 0; + + string ext = Path.GetExtension(catUrl.Split('?')[0]).ToLowerInvariant(); + string mime = ext == ".png" ? "image/png" + : ext == ".gif" ? "image/gif" + : "image/jpeg"; + string fileName = $"cat{ext}"; + + // Download the image bytes + byte[] imageBytes; + try + { + imageBytes = await _http.GetByteArrayAsync(catUrl); + } + catch + { + await MessageHelper.ReplyAsync(ctx, channel, "😿 Could not download the cat image. Try again later."); + return; + } + + // Upload to Valour CDN so the server can scan/serve it + string cdnUrl; + try + { + using var form = new MultipartFormDataContent(); + using var fileContent = new ByteArrayContent(imageBytes); + fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(mime); + form.Add(fileContent, "file", fileName); + + var uploadResult = await ctx.Planet.Node.PostMultipartDataWithResponse("upload/image", form); + if (!uploadResult.Success) + { + await MessageHelper.ReplyAsync(ctx, channel, "😿 Could not upload the cat image. Try again later."); + return; + } + cdnUrl = uploadResult.Data!; + } + catch + { + await MessageHelper.ReplyAsync(ctx, channel, "😿 Could not upload the cat image. Try again later."); + return; + } + + var attachment = new MessageAttachment(MessageAttachmentType.Image) + { + Location = cdnUrl, + MimeType = mime, + FileName = fileName, + Width = width, + Height = height + }; + + await channel.SendMessageAsync("",attachments: [attachment]); + } + } +} diff --git a/SkyBot/Commands/Fun/T9Decode.cs b/SkyBot/Commands/Fun/T9Decode.cs new file mode 100644 index 0000000..51c10f2 --- /dev/null +++ b/SkyBot/Commands/Fun/T9Decode.cs @@ -0,0 +1,114 @@ +using System.Collections.Concurrent; +using System.Text; +using SkyBot.Helpers; +using SkyBot.Models; +using Valour.Sdk.Models; + +namespace SkyBot.Commands +{ + public class MultiTap : ICommand + { + public string Name => "t9decode"; + public string[] Aliases => ["t9d"]; + public string Description => "Decodes old phone keypad multi-tap input into text."; + public string Section => "Fun"; + public string Usage => "multitap (e.g. 44 3 555 555 666 or reply to a message)"; + + private static readonly Dictionary Keymap = new() + { + ['2'] = "ABC", + ['3'] = "DEF", + ['4'] = "GHI", + ['5'] = "JKL", + ['6'] = "MNO", + ['7'] = "PQRS", + ['8'] = "TUV", + ['9'] = "WXYZ", + }; + + public async Task Execute(CommandContext ctx) + { + ConcurrentDictionary channelCache = ctx.ChannelCache; + long channelId = ctx.ChannelId; + Message message = ctx.Message; + string[] args = ctx.Args; + + if (!channelCache.TryGetValue(channelId, out var channel)) return; + + string raw; + + if (message.ReplyToId.HasValue) + { + var replyMessage = await message.FetchReplyMessageAsync(); + raw = replyMessage?.Content ?? ""; + } + else + { + if (args.Length == 0) + { + await MessageHelper.ReplyAsync(ctx, channel, "Please provide digits to decode, or reply to a message. Example: `multitap 44 3 555 555 666`"); + return; + } + raw = string.Join("", args); + } + + // Strip anything that isn't a digit + string input = new([..raw.Where(char.IsDigit)]); + + var result = new StringBuilder(); + int i = 0; + + while (i < input.Length) + { + char digit = input[i]; + + if (digit == '0') + { + result.Append(' '); + i++; + continue; + } + + // 1 = silent same-key separator if surrounded by the same digit, otherwise a space + if (digit == '1') + { + int j = i; + while (j < input.Length && input[j] == '1') j++; + + char before = i > 0 ? input[i - 1] : '\0'; + char after = j < input.Length ? input[j] : '\0'; + bool sameKey = before >= '2' && before <= '9' && before == after; + + if (!sameKey) result.Append(' '); + i = j; + continue; + } + + if (!Keymap.TryGetValue(digit, out string? letters)) + { + i++; + continue; + } + + // Count consecutive presses of the same digit + int count = 0; + while (i + count < input.Length && input[i + count] == digit) + count++; + + int letterIndex = (count - 1) % letters.Length; + result.Append(letters[letterIndex]); + i += count; + } + + string decoded = result.ToString().Trim(); + + if (string.IsNullOrWhiteSpace(decoded)) + { + await MessageHelper.ReplyAsync(ctx, channel, "Couldn't decode anything from that input."); + return; + } + + await MessageHelper.ReplyAsync(ctx, channel, $"📱 **{decoded}**"); + } + } +} diff --git a/SkyBot/Commands/Fun/T9Encode.cs b/SkyBot/Commands/Fun/T9Encode.cs new file mode 100644 index 0000000..2b9c46b --- /dev/null +++ b/SkyBot/Commands/Fun/T9Encode.cs @@ -0,0 +1,108 @@ +using System.Collections.Concurrent; +using System.Text; +using SkyBot.Helpers; +using SkyBot.Models; +using Valour.Sdk.Models; + +namespace SkyBot.Commands +{ + public class T9Encode : ICommand + { + public string Name => "t9encode"; + public string[] Aliases => ["t9e"]; + public string Description => "Encodes text into old phone keypad multi-tap digits."; + public string Section => "Fun"; + public string Usage => "t9encode (or reply to a message)"; + + // Maps each letter to (key digit, press count) + private static readonly Dictionary Charmap = BuildCharmap(); + + private static Dictionary BuildCharmap() + { + Dictionary keymap = new() + { + ['2'] = "ABC", + ['3'] = "DEF", + ['4'] = "GHI", + ['5'] = "JKL", + ['6'] = "MNO", + ['7'] = "PQRS", + ['8'] = "TUV", + ['9'] = "WXYZ", + }; + + var map = new Dictionary(); + foreach (var (key, letters) in keymap) + for (int i = 0; i < letters.Length; i++) + map[letters[i]] = (key, i + 1); + + return map; + } + + public async Task Execute(CommandContext ctx) + { + ConcurrentDictionary channelCache = ctx.ChannelCache; + long channelId = ctx.ChannelId; + Message message = ctx.Message; + string[] args = ctx.Args; + + if (!channelCache.TryGetValue(channelId, out var channel)) return; + + string raw; + + if (message.ReplyToId.HasValue) + { + var replyMessage = await message.FetchReplyMessageAsync(); + raw = replyMessage?.Content ?? ""; + } + else + { + if (args.Length == 0) + { + await MessageHelper.ReplyAsync(ctx, channel, "Please provide text to encode, or reply to a message."); + return; + } + raw = string.Join(" ", args); + } + + if (string.IsNullOrWhiteSpace(raw)) + { + await MessageHelper.ReplyAsync(ctx, channel, "No text to encode."); + return; + } + + var result = new StringBuilder(); + char prevKey = '\0'; + + foreach (char c in raw.ToUpper()) + { + if (c == ' ') + { + result.Append('0'); + prevKey = '\0'; + continue; + } + + if (!Charmap.TryGetValue(c, out var entry)) + continue; + + // If this letter uses the same key as the previous one, insert a 1 separator + if (entry.Key == prevKey) + result.Append('1'); + + result.Append(entry.Key, entry.Presses); + prevKey = entry.Key; + } + + string encoded = result.ToString().Trim('0'); + + if (string.IsNullOrWhiteSpace(encoded)) + { + await MessageHelper.ReplyAsync(ctx, channel, "Couldn't encode anything from that input."); + return; + } + + await MessageHelper.ReplyAsync(ctx, channel, $"📱 `{encoded}`"); + } + } +} From 22750c29608cb510480929534ba64404ba24c46d Mon Sep 17 00:00:00 2001 From: skyjoshua Date: Fri, 20 Mar 2026 08:04:13 +0000 Subject: [PATCH 2/2] cat v2 --- SkyBot/Commands/Fun/Cat.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SkyBot/Commands/Fun/Cat.cs b/SkyBot/Commands/Fun/Cat.cs index a721fd6..cab7112 100644 --- a/SkyBot/Commands/Fun/Cat.cs +++ b/SkyBot/Commands/Fun/Cat.cs @@ -10,7 +10,7 @@ namespace SkyBot.Commands public class Cat : ICommand { public string Name => "cat"; - public string[] Aliases => ["kitty", "meow"]; + public string[] Aliases => []; public string Description => "Posts a random cat picture."; public string Section => "Fun"; public string Usage => "cat";