167
SkyBot/Commands/Fun/Image.cs
Normal file
167
SkyBot/Commands/Fun/Image.cs
Normal file
@@ -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 <query>";
|
||||||
|
|
||||||
|
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<long, Channel> 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 <query>`");
|
||||||
|
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<string>("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<ImageResult?> 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<ImageResult?> 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<ImageResult>();
|
||||||
|
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)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<Version>0.2.2.0</Version>
|
<Version>0.2.2.1</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
Reference in New Issue
Block a user