using System; using System.Net; using System.Text; using System.Diagnostics; using System.Collections.Generic; using System.Linq; using System.IO; using System.Threading; using System.Threading.Tasks; using System.Text.Json; using System.Text.Json.Serialization; using Xabbo.Messages; #nullable enable public class ItemView { [JsonPropertyName("h")] public string HabboId { get; set; } = ""; [JsonPropertyName("n")] public string Name { get; set; } = ""; [JsonPropertyName("r")] public int Revision { get; set; } [JsonPropertyName("c")] public int Count { get; set; } } public class LiveState { [JsonPropertyName("items")] public List Items { get; set; } = new(); [JsonPropertyName("status")] public StatusUpdate Status { get; set; } = new(); } public class RecycleRequest { [JsonPropertyName("items")] public List Items { get; set; } = new(); [JsonPropertyName("delay")] public int Delay { get; set; } } public class RecycleItem { [JsonPropertyName("h")] public string HabboId { get; set; } = ""; [JsonPropertyName("a")] public int Amount { get; set; } } public class StatusUpdate { [JsonPropertyName("done")] public int Done { get; set; } [JsonPropertyName("total")] public int Total { get; set; } } var port = 8226; var queue = new List(); var progress = 0; var total = 0; var delay = 12000; var lastRun = DateTime.Now; HttpListener? server = null; void TryRefreshInventory() { try { Send(Out["RequestFurniInventory"]); return; } catch { } } OnIntercept(In["RecyclerFinished"], e => { TryRefreshInventory(); }); LiveState GetCurrentState() { EnsureInventory(10000); var currentItems = Inventory.Where(x => x.IsRecyclable) .GroupBy(x => x.GetDescriptor()) .Where(g => g.Count() >= 8) .Select(g => new ItemView { HabboId = g.Key.GetInfo().Identifier, Name = g.Key.GetName(), Revision = g.Key.GetInfo().Revision, Count = g.Count() }) .OrderByDescending(x => x.Count) .ToList(); if (progress >= total && total > 0) { progress = total = 0; } return new LiveState { Items = currentItems, Status = new StatusUpdate { Done = progress, Total = total } }; } try { log("starting server..."); var html = @" Recycler
recycler
idle
select items

queue

"; server = new HttpListener(); server.Prefixes.Add($"http://localhost:{port}/"); server.Start(); Process.Start(new ProcessStartInfo { FileName = $"http://localhost:{port}/", UseShellExecute = true }); log($"server listening on http://localhost:{port}"); _ = Task.Run(async () => { while (Run && server.IsListening) { try { var ctx = await server.GetContextAsync(); _ = Task.Run(() => HandleContextAsync(ctx, html)); } catch { break; } } }); while (Run) { if (queue.Count >= 8 && (DateTime.Now - lastRun).TotalMilliseconds >= delay) { var batch = queue.Take(8).ToList(); queue.RemoveRange(0, 8); Send(Out["RecycleItems"], 8, batch[0], batch[1], batch[2], batch[3], batch[4], batch[5], batch[6], batch[7]); progress += 8; lastRun = DateTime.Now; } await Task.Delay(10); } } catch (TaskCanceledException) { log("error: inventory load timed out. ensure you are fully in-game."); } catch (Exception ex) { log($"unhandled exception: {ex.Message}"); } finally { server?.Stop(); server?.Close(); log("server shut down."); } async Task HandleContextAsync(HttpListenerContext ctx, string html) { var req = ctx.Request; var res = ctx.Response; try { var endpoint = (req.HttpMethod, req.Url?.AbsolutePath); switch (endpoint) { case ("GET", "/"): await WriteResponseAsync(res, html, "text/html"); break; case ("GET", "/state"): var stateJson = JsonSerializer.Serialize(GetCurrentState()); await WriteResponseAsync(res, stateJson, "application/json"); break; case ("POST", "/recycle"): EnsureInventory(10000); using (var r = new StreamReader(req.InputStream)) { var payload = JsonSerializer.Deserialize(await r.ReadToEndAsync()); if (payload != null) { queue.Clear(); delay = Math.Clamp(payload.Delay, 500, 60000); foreach (var pItem in payload.Items) { var itemInstances = Inventory .Where(x => x.IsRecyclable && x.GetInfo().Identifier == pItem.HabboId) .Select(i => -(int)i.Id) .ToList(); int amount = Math.Min(pItem.Amount, itemInstances.Count); queue.AddRange(itemInstances.Take(amount)); } total = queue.Count; progress = 0; log($"queueing {total} items, {delay}ms delay."); } } res.StatusCode = 200; break; case ("POST", "/stop"): queue.Clear(); progress = total = 0; log("recycling stopped."); res.StatusCode = 200; break; default: res.StatusCode = 404; break; } } catch (Exception ex) { log($"request error: {ex.Message}"); if(!res.OutputStream.CanWrite) return; res.StatusCode = 500; } finally { res.Close(); } } async Task WriteResponseAsync(HttpListenerResponse res, string content, string type) { var buffer = Encoding.UTF8.GetBytes(content); res.ContentType = type; res.ContentLength64 = buffer.Length; await res.OutputStream.WriteAsync(buffer, 0, buffer.Length); } void log(string message) => Log(message);