From b41d7268f2938ff6d74232f2325f673b2f02bad0 Mon Sep 17 00:00:00 2001 From: skyjoshua Date: Sat, 21 Mar 2026 00:27:25 +0000 Subject: [PATCH 1/2] image command --- SkyBot/Commands/Fun/Image.cs | 167 +++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 SkyBot/Commands/Fun/Image.cs diff --git a/SkyBot/Commands/Fun/Image.cs b/SkyBot/Commands/Fun/Image.cs new file mode 100644 index 0000000..9401bae --- /dev/null +++ b/SkyBot/Commands/Fun/Image.cs @@ -0,0 +1,167 @@ +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 Image : ICommand + { + public string Name => "image"; + public string[] Aliases => ["img"]; + public string Description => "Fetches a random image matching your search."; + public string Section => "Fun"; + public string Usage => "image "; + + private static readonly HttpClient _http = new(); + private static readonly Random _rng = new(); + + private record ImageResult(string Url, int Width, int Height, string Mime); + + public async Task Execute(CommandContext ctx) + { + ConcurrentDictionary channelCache = ctx.ChannelCache; + long channelId = ctx.ChannelId; + + if (!channelCache.TryGetValue(channelId, out var channel)) return; + + string[] args = ctx.Args; + if (args.Length == 0) + { + await MessageHelper.ReplyAsync(ctx, channel, $"Usage: `{Config.Prefix}image `"); + return; + } + + string query = string.Join(" ", args); + await channel.SendIsTyping(); + + var result = await FetchPixabayAsync(query) ?? await FetchWikimediaAsync(query); + + if (result is null) + { + await MessageHelper.ReplyAsync(ctx, channel, $"🔍 No images found for **{query}**."); + return; + } + + string ext = Path.GetExtension(result.Url.Split('?')[0]).ToLowerInvariant(); + string fileName = $"image{(string.IsNullOrEmpty(ext) ? ".jpg" : ext)}"; + + byte[] imageBytes; + try + { + imageBytes = await _http.GetByteArrayAsync(result.Url); + } + catch + { + await MessageHelper.ReplyAsync(ctx, channel, "🖼️ Could not download the image. Try again later."); + return; + } + + string cdnUrl; + try + { + using var form = new MultipartFormDataContent(); + using var fileContent = new ByteArrayContent(imageBytes); + fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(result.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 image. Try again later."); + return; + } + cdnUrl = uploadResult.Data; + } + catch + { + await MessageHelper.ReplyAsync(ctx, channel, "🖼️ Could not upload the image. Try again later."); + return; + } + + var attachment = new MessageAttachment(MessageAttachmentType.Image) + { + Location = cdnUrl, + MimeType = result.Mime, + FileName = fileName, + Width = result.Width, + Height = result.Height + }; + + await channel.SendMessageAsync($"🖼️ **{query}**", attachments: [attachment]); + } + + private static async Task FetchPixabayAsync(string query) + { + string? key = Environment.GetEnvironmentVariable("PIXABAY_API_KEY"); + if (string.IsNullOrWhiteSpace(key)) return null; + + string url = $"https://pixabay.com/api/?key={key}" + + $"&q={Uri.EscapeDataString(query)}&image_type=photo&per_page=20&safesearch=true"; + + HttpResponseMessage response; + try + { + response = await _http.GetAsync(url); + } + catch { return null; } + + // 429 = rate limited — fall through to Wikimedia + if (!response.IsSuccessStatusCode) return null; + + string json = await response.Content.ReadAsStringAsync(); + using var doc = JsonDocument.Parse(json); + var hits = doc.RootElement.GetProperty("hits"); + if (hits.GetArrayLength() == 0) return null; + + var hit = hits[_rng.Next(hits.GetArrayLength())]; + string imgUrl = hit.GetProperty("webformatURL").GetString()!; + int width = hit.TryGetProperty("webformatWidth", out var w) ? w.GetInt32() : 0; + int height = hit.TryGetProperty("webformatHeight", out var h) ? h.GetInt32() : 0; + string ext = Path.GetExtension(imgUrl.Split('?')[0]).ToLowerInvariant(); + string mime = ext == ".png" ? "image/png" : ext == ".gif" ? "image/gif" : "image/jpeg"; + + return new ImageResult(imgUrl, width, height, mime); + } + + private static async Task FetchWikimediaAsync(string query) + { + string url = "https://commons.wikimedia.org/w/api.php" + + $"?action=query&generator=search&gsrsearch=intitle:{Uri.EscapeDataString(query)}" + + "&gsrnamespace=6&gsrlimit=30&prop=imageinfo&iiprop=url|size|mime&format=json"; + + string json; + try + { + _http.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "SkyBot/1.0 (https://github.com/SkyJoshua/SkyBot)"); + json = await _http.GetStringAsync(url); + } + catch { return null; } + + using var doc = JsonDocument.Parse(json); + if (!doc.RootElement.TryGetProperty("query", out var queryEl) || + !queryEl.TryGetProperty("pages", out var pages)) return null; + + var candidates = new List(); + foreach (var page in pages.EnumerateObject()) + { + if (!page.Value.TryGetProperty("imageinfo", out var infoArr)) continue; + var info = infoArr[0]; + + string mime = info.TryGetProperty("mime", out var m) ? m.GetString() ?? "" : ""; + if (mime is not ("image/jpeg" or "image/png" or "image/gif" or "image/webp")) continue; + + string imgUrl = info.TryGetProperty("url", out var u) ? u.GetString() ?? "" : ""; + int width = info.TryGetProperty("width", out var w) ? w.GetInt32() : 0; + int height = info.TryGetProperty("height", out var h) ? h.GetInt32() : 0; + + if (!string.IsNullOrEmpty(imgUrl)) + candidates.Add(new ImageResult(imgUrl, width, height, mime)); + } + + return candidates.Count == 0 ? null : candidates[_rng.Next(candidates.Count)]; + } + } +} From aba6eb9fd484cbb331cba6319588fa612fc164e4 Mon Sep 17 00:00:00 2001 From: skyjoshua Date: Sat, 21 Mar 2026 00:27:44 +0000 Subject: [PATCH 2/2] image command --- SkyBot/SkyBot.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SkyBot/SkyBot.csproj b/SkyBot/SkyBot.csproj index de69cbc..24cf203 100644 --- a/SkyBot/SkyBot.csproj +++ b/SkyBot/SkyBot.csproj @@ -5,7 +5,7 @@ net10.0 enable enable - 0.2.2.0 + 0.2.2.1