using Godot; using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; using System.Text.RegularExpressions; using System.Threading.Tasks; public partial class CommandHandler : Node { private Settings Settings { get; set; } private game Game { get; set; } private readonly List TaskQueue = new(); private System.Net.Http.HttpClient _Client = null; private System.Net.Http.HttpClient Client { get { _Client ??= new(); return _Client; } } // Called when the node enters the scene tree for the first time. public override void _Ready() { Settings = GetNode("/root/Settings"); Game = GetNode("/root/Game"); } // Called every frame. 'delta' is the elapsed time since the previous frame. public override void _Process(double delta) { //delete/cleanup one finished task per frame //currently just logs errors var t = TaskQueue.FirstOrDefault(t => t.IsCompleted, null); if (t is not null) { if (t.Exception is not null) GD.PrintErr(t.Exception); TaskQueue.Remove(t); } } private void IncomingCommand(Command command) { if (!Settings.IsUserAuthorized(command.User, command.IsStreamer, command.IsModerator)) return; var baseArgs = command.GetArgs(); var type = CommandTypeHelper.ParseCommand(baseArgs.Pop()); var args = baseArgs.DuplicateAtState(); switch (type) { case CommandType.MoveCard: MoveCard(args); break; case CommandType.MoveRow: MoveRow(args); break; case CommandType.DeleteCard: DeleteCards(args); break; case CommandType.DeleteRow: DeleteRow(args); break; case CommandType.CreateCard: TaskQueue.Add(Task.Run(() => CreateCard(args))); break; case CommandType.CreateRow: CreateRow(args); break; case CommandType.RenameCard: RenameCard(args); break; case CommandType.RenameRow: RenameRow(args); break; case CommandType.RecolorRow: RecolorRow(args); break; case CommandType.ChangeCardImage: TaskQueue.Add(Task.Run(() => ChangeCardImage(args))); break; default: throw new Exception(); } } private void MoveCard(CommandArguments args) { var cardId = args.Pop(); var rowId = args.Pop(); var indexStr = args.Pop(); if (int.TryParse(indexStr, out int index)) Game.MoveCard(cardId, rowId, index); else Game.MoveCard(cardId, rowId, null); } private void MoveRow(CommandArguments args) { var rowId = args.Pop(); var newIndexStr = args.Pop(); var newIndex = int.Parse(newIndexStr); Game.MoveRow(rowId, newIndex); } private void DeleteCards(CommandArguments args) { var ids = args.Enumerate().ToArray(); Game.DeleteCards(ids); } private void DeleteRow(CommandArguments args) { var rowId = args.Pop(); var deleteCards = args.Pop(); if (bool.TryParse(deleteCards, out bool del)) Game.DeleteRow(rowId, del); else Game.DeleteRow(rowId); } private async Task CreateCard(CommandArguments args) { var title = args.Pop(); var url = args.Pop(); Image img = await ImageFromUrl(url); Game.CreateCard(title, img); } private void CreateRow(CommandArguments args) { var colorStr = args.Pop(); var title = args.Remaining(); var color = Color.FromString(colorStr, new Color(0, 0, 0, 0)); if (color == new Color(0, 0, 0, 0)) Game.CreateRow(null, title); else Game.CreateRow(color, title); } private void RenameCard(CommandArguments args) { var cardId = args.Pop(); var title = args.Remaining(); Game.RenameCard(cardId, title); } private void RenameRow(CommandArguments args) { var rowId = args.Pop(); var title = args.Remaining(); Game.RenameRow(rowId, title); } private void RecolorRow(CommandArguments args) { var rowId = args.Pop(); var colorStr = args.Pop(); var color = Color.FromString(colorStr, new Color(0, 0, 0, 0)); if (color == new Color(0, 0, 0, 0)) throw new Exception($"invalid color {colorStr}"); Game.RecolorRow(rowId, color); } private async Task ChangeCardImage(CommandArguments args) { var cardId = args.Pop(); var img = await ImageFromUrl(args.Pop()); Game.ChangeCardImage(cardId, img); } private async Task ImageFromUrl(string url) { var uri = new Uri(url); var resp = await Client.GetAsync(uri); if (!resp.IsSuccessStatusCode) return null; Image img = new(); var arr = await resp.Content.ReadAsByteArrayAsync(); //TODO detect images by header rather than extension switch (Path.GetExtension(uri.AbsolutePath).ToLower()) { case "png": img.LoadPngFromBuffer(arr); break; case "jpg": case "jpeg": img.LoadJpgFromBuffer(arr); break; case "svg": img.LoadSvgFromBuffer(arr); break; case "webp": img.LoadWebpFromBuffer(arr); break; default: throw new Exception("unrecognized filetype"); } return img; } protected override void Dispose(bool disposing) { if (disposing) { _Client?.Dispose(); } base.Dispose(disposing); } }