Habbo Hotel automation scripts including: - Game solvers (Snake, Color Puzzle, Tetris, Flappy Bird, Flood-IT) - Room utilities (Autogate, One-Way Door, Furni Scanner) - Bot tools (Heal Bot, Pet Trainer, User Collector) - Trading & economy (Furni-Matic, Seed Trade, Trade Spam) Cleaned up: removed 5 duplicates and 2 broken scripts, renamed 37 gibberish filenames to descriptive names.
578 lines
15 KiB
C#
578 lines
15 KiB
C#
using System.Net;
|
|
using System.Text;
|
|
using System.Diagnostics;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.IO;
|
|
using System.Threading.Tasks;
|
|
|
|
var port = 8230;
|
|
var queue = new List<int>();
|
|
var progress = 0;
|
|
var total = 0;
|
|
var delay = 850;
|
|
var lastrun = DateTime.Now;
|
|
HttpListener server = null;
|
|
|
|
try {
|
|
EnsureInventory();
|
|
|
|
var items = Inventory
|
|
.Where(x => x.IsRecyclable)
|
|
.GroupBy(x => x.GetDescriptor())
|
|
.Where(g => g.Count() >= 8)
|
|
.Select(g => new {
|
|
name = g.Key.GetName(),
|
|
id = g.Key.GetInfo().Identifier,
|
|
rev = g.Key.GetInfo().Revision,
|
|
count = g.Count(),
|
|
list = g.Select(i => (int)i.Id).ToList()
|
|
})
|
|
.OrderByDescending(x => x.count)
|
|
.ToList();
|
|
|
|
Log($"Found {items.Count} recyclable types (8+ items)");
|
|
items.ForEach(x => Log($" {x.name}: {x.count}x"));
|
|
|
|
var json = "[" + string.Join(",", items.Select((item, i) =>
|
|
$"{{\"i\":{i},\"n\":\"{item.name.Replace("\"", "\\\"")}\",\"id\":\"{item.id}\",\"r\":{item.rev},\"c\":{item.count}}}"
|
|
)) + "]";
|
|
|
|
var html = @"<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset='utf-8'>
|
|
<title>Recycler</title>
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body {
|
|
background: linear-gradient(135deg, #0f0f0f 0%, #1a1a1a 100%);
|
|
color: #e0e0e0;
|
|
font: 14px -apple-system, system-ui, sans-serif;
|
|
min-height: 100vh;
|
|
padding: 20px;
|
|
}
|
|
.wrap {
|
|
max-width: 900px;
|
|
margin: 0 auto;
|
|
animation: fadein 0.5s;
|
|
}
|
|
@keyframes fadein {
|
|
from { opacity: 0; transform: translateY(20px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
h1 {
|
|
font-size: 24px;
|
|
font-weight: 600;
|
|
margin-bottom: 25px;
|
|
text-align: center;
|
|
background: linear-gradient(90deg, #4ade80, #22d3ee);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
}
|
|
.panel {
|
|
background: rgba(255,255,255,0.03);
|
|
backdrop-filter: blur(10px);
|
|
border: 1px solid rgba(255,255,255,0.08);
|
|
border-radius: 12px;
|
|
padding: 16px;
|
|
margin-bottom: 16px;
|
|
}
|
|
.search-box {
|
|
width: 100%;
|
|
background: rgba(255,255,255,0.05);
|
|
border: 1px solid rgba(255,255,255,0.1);
|
|
color: #fff;
|
|
padding: 12px;
|
|
border-radius: 8px;
|
|
font-size: 14px;
|
|
margin-bottom: 12px;
|
|
transition: all 0.2s;
|
|
}
|
|
.search-box:focus {
|
|
outline: none;
|
|
border-color: #4ade80;
|
|
background: rgba(255,255,255,0.08);
|
|
}
|
|
.search-box::placeholder {
|
|
color: #666;
|
|
}
|
|
.controls {
|
|
display: flex;
|
|
gap: 12px;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-wrap: wrap;
|
|
}
|
|
.controls label {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-size: 13px;
|
|
color: #a0a0a0;
|
|
}
|
|
button {
|
|
padding: 10px 20px;
|
|
background: linear-gradient(135deg, #4ade80, #22d3ee);
|
|
border: none;
|
|
color: #000;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
border-radius: 8px;
|
|
transition: all 0.2s;
|
|
font-size: 13px;
|
|
}
|
|
button:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 5px 15px rgba(74,222,128,0.3);
|
|
}
|
|
button:active {
|
|
transform: translateY(0);
|
|
}
|
|
button:disabled {
|
|
background: #333;
|
|
color: #666;
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
box-shadow: none;
|
|
}
|
|
button.stop {
|
|
background: linear-gradient(135deg, #ef4444, #f97316);
|
|
}
|
|
button.stop:hover {
|
|
box-shadow: 0 5px 15px rgba(239,68,68,0.3);
|
|
}
|
|
input[type=number] {
|
|
width: 70px;
|
|
background: rgba(255,255,255,0.05);
|
|
border: 1px solid rgba(255,255,255,0.1);
|
|
color: #fff;
|
|
padding: 8px;
|
|
border-radius: 6px;
|
|
font-size: 13px;
|
|
transition: all 0.2s;
|
|
}
|
|
input[type=number]:focus {
|
|
outline: none;
|
|
border-color: #4ade80;
|
|
background: rgba(255,255,255,0.08);
|
|
}
|
|
.bar {
|
|
height: 40px;
|
|
background: rgba(0,0,0,0.3);
|
|
border-radius: 20px;
|
|
position: relative;
|
|
overflow: hidden;
|
|
box-shadow: inset 0 2px 4px rgba(0,0,0,0.2);
|
|
}
|
|
.fill {
|
|
height: 100%;
|
|
background: linear-gradient(90deg, #4ade80, #22d3ee);
|
|
border-radius: 20px;
|
|
transition: width 0.5s ease;
|
|
box-shadow: 0 0 20px rgba(74,222,128,0.5);
|
|
}
|
|
.bartext {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
font-weight: 600;
|
|
font-size: 14px;
|
|
color: #fff;
|
|
text-shadow: 0 1px 2px rgba(0,0,0,0.3);
|
|
}
|
|
.status {
|
|
text-align: center;
|
|
font-size: 13px;
|
|
color: #888;
|
|
}
|
|
.grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
|
gap: 12px;
|
|
max-height: 450px;
|
|
overflow-y: auto;
|
|
padding: 4px;
|
|
}
|
|
.grid::-webkit-scrollbar {
|
|
width: 8px;
|
|
}
|
|
.grid::-webkit-scrollbar-track {
|
|
background: rgba(255,255,255,0.02);
|
|
border-radius: 4px;
|
|
}
|
|
.grid::-webkit-scrollbar-thumb {
|
|
background: rgba(255,255,255,0.1);
|
|
border-radius: 4px;
|
|
}
|
|
.grid::-webkit-scrollbar-thumb:hover {
|
|
background: rgba(255,255,255,0.15);
|
|
}
|
|
.card {
|
|
background: rgba(255,255,255,0.04);
|
|
border: 2px solid rgba(255,255,255,0.08);
|
|
border-radius: 10px;
|
|
padding: 12px;
|
|
text-align: center;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
position: relative;
|
|
}
|
|
.card:hover {
|
|
background: rgba(255,255,255,0.07);
|
|
transform: translateY(-3px);
|
|
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
|
|
}
|
|
.card.on {
|
|
border-color: #4ade80;
|
|
background: rgba(74,222,128,0.1);
|
|
}
|
|
.card.hidden {
|
|
display: none;
|
|
}
|
|
.card img {
|
|
width: 60px;
|
|
height: 60px;
|
|
margin-bottom: 8px;
|
|
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3));
|
|
}
|
|
.card .name {
|
|
font-size: 12px;
|
|
margin-bottom: 6px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
color: #ccc;
|
|
}
|
|
.card .count {
|
|
color: #4ade80;
|
|
font-weight: 600;
|
|
font-size: 14px;
|
|
margin-bottom: 8px;
|
|
}
|
|
.amt {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 4px;
|
|
margin-top: 8px;
|
|
}
|
|
.amt button {
|
|
width: 24px;
|
|
height: 24px;
|
|
padding: 0;
|
|
background: rgba(255,255,255,0.1);
|
|
border-radius: 4px;
|
|
font-size: 16px;
|
|
line-height: 1;
|
|
color: #fff;
|
|
}
|
|
.amt button:hover {
|
|
background: rgba(74,222,128,0.3);
|
|
transform: none;
|
|
box-shadow: none;
|
|
}
|
|
.amt span {
|
|
min-width: 40px;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: #fff;
|
|
}
|
|
.empty {
|
|
grid-column: 1 / -1;
|
|
text-align: center;
|
|
padding: 60px 20px;
|
|
color: #666;
|
|
}
|
|
.result-count {
|
|
text-align: center;
|
|
font-size: 12px;
|
|
color: #666;
|
|
margin-bottom: 8px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class='wrap'>
|
|
<h1>Recycler</h1>
|
|
<div class='panel'>
|
|
<input type='text' class='search-box' id='search' placeholder='Search items to recycle...' autofocus>
|
|
<div class='controls'>
|
|
<label>Delay <input type='number' id='delay' value='850' min='100' max='2000' step='50'>ms</label>
|
|
<button onclick='reset()'>Clear</button>
|
|
<button onclick='go()' id='btn'>Start</button>
|
|
<button onclick='stop()' class='stop'>Stop</button>
|
|
</div>
|
|
</div>
|
|
<div class='panel'>
|
|
<div class='bar'>
|
|
<div class='fill' id='bar' style='width:0%'></div>
|
|
<div class='bartext' id='txt'>Ready</div>
|
|
</div>
|
|
</div>
|
|
<div class='panel'>
|
|
<div class='status' id='info'>Select items to recycle</div>
|
|
<div class='result-count' id='results'></div>
|
|
<div class='grid' id='grid'></div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
const d = " + json + @";
|
|
let sel = [];
|
|
let vals = {};
|
|
let searchterm = '';
|
|
let wasrunning = false;
|
|
|
|
d.forEach(x => vals[x.i] = Math.min(80, Math.floor(x.c/8)*8));
|
|
|
|
function img(x) {
|
|
return 'https://images.habbo.com/dcr/hof_furni/' + x.r + '/' + x.id + '_icon.png';
|
|
}
|
|
|
|
function draw() {
|
|
const g = document.getElementById('grid');
|
|
if (!d.length) {
|
|
g.innerHTML = '<div class=""empty"">No recyclable items found<br><small>Need 8+ of the same type</small></div>';
|
|
return;
|
|
}
|
|
|
|
let visible = 0;
|
|
let html = '';
|
|
|
|
d.forEach(x => {
|
|
const show = !searchterm || x.n.toLowerCase().includes(searchterm.toLowerCase());
|
|
if (show) visible++;
|
|
|
|
html += '<div class=""card' + (show ? '' : ' hidden') + '"" id=""c' + x.i + '"" onclick=""pick(' + x.i + ')"">' +
|
|
'<img src=""' + img(x) + '"" onerror=""this.style.display=\'none\'"">' +
|
|
'<div class=""name"" title=""' + x.n + '"">' + x.n + '</div>' +
|
|
'<div class=""count"">' + x.c + 'x</div>' +
|
|
'<div class=""amt"" onclick=""event.stopPropagation()"">' +
|
|
'<button onclick=""adj(' + x.i + ',-8)"">-</button>' +
|
|
'<span id=""v' + x.i + '"">' + vals[x.i] + '</span>' +
|
|
'<button onclick=""adj(' + x.i + ',8)"">+</button>' +
|
|
'</div>' +
|
|
'</div>';
|
|
});
|
|
|
|
g.innerHTML = html;
|
|
|
|
// Restore selected state
|
|
sel.forEach(i => {
|
|
const el = document.getElementById('c' + i);
|
|
if (el) el.classList.add('on');
|
|
});
|
|
|
|
// Update result count
|
|
const rc = document.getElementById('results');
|
|
if (searchterm) {
|
|
rc.textContent = visible + ' items found';
|
|
} else {
|
|
rc.textContent = '';
|
|
}
|
|
|
|
if (visible === 0 && searchterm) {
|
|
g.innerHTML = '<div class=""empty"">No items match ""' + searchterm + '""<br><small>Try different search</small></div>';
|
|
}
|
|
}
|
|
|
|
function adj(i, n) {
|
|
const max = Math.floor(d.find(x => x.i === i).c / 8) * 8;
|
|
vals[i] = Math.max(8, Math.min(max, vals[i] + n));
|
|
document.getElementById('v' + i).textContent = vals[i];
|
|
update();
|
|
}
|
|
|
|
function pick(i) {
|
|
const e = document.getElementById('c' + i);
|
|
if (sel.includes(i)) {
|
|
sel = sel.filter(x => x !== i);
|
|
e.classList.remove('on');
|
|
} else {
|
|
sel.push(i);
|
|
e.classList.add('on');
|
|
}
|
|
update();
|
|
}
|
|
|
|
function reset() {
|
|
sel = [];
|
|
d.forEach(x => document.getElementById('c' + x.i)?.classList.remove('on'));
|
|
update();
|
|
}
|
|
|
|
function update() {
|
|
const t = sel.reduce((s, i) => s + vals[i], 0);
|
|
document.getElementById('info').textContent = sel.length ?
|
|
sel.length + ' types • ' + t + ' items' :
|
|
'Select items to recycle';
|
|
}
|
|
|
|
function go() {
|
|
if (!sel.length) {
|
|
alert('Select items first');
|
|
return;
|
|
}
|
|
|
|
const data = sel.map(i => ({i: i, a: vals[i]}));
|
|
|
|
document.getElementById('btn').disabled = true;
|
|
fetch('/recycle', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({items: data, delay: parseInt(document.getElementById('delay').value)})
|
|
});
|
|
}
|
|
|
|
function stop() {
|
|
fetch('/stop', {method: 'POST'});
|
|
document.getElementById('btn').disabled = false;
|
|
wasrunning = false;
|
|
}
|
|
|
|
// Search functionality
|
|
document.getElementById('search').addEventListener('input', (e) => {
|
|
searchterm = e.target.value.trim();
|
|
draw();
|
|
});
|
|
|
|
setInterval(() => {
|
|
fetch('/status')
|
|
.then(r => r.json())
|
|
.then(x => {
|
|
const p = x.total ? Math.round(x.done / x.total * 100) : 0;
|
|
document.getElementById('bar').style.width = p + '%';
|
|
document.getElementById('txt').textContent = x.total ? x.done + ' / ' + x.total : 'Ready';
|
|
|
|
// Track if we were running
|
|
if (x.total > 0) {
|
|
wasrunning = true;
|
|
}
|
|
|
|
// If we were running and now total is 0, we're done
|
|
if (wasrunning && x.total === 0) {
|
|
wasrunning = false;
|
|
document.getElementById('btn').disabled = false;
|
|
document.getElementById('bar').style.width = '100%';
|
|
document.getElementById('txt').textContent = 'Complete!';
|
|
setTimeout(() => {
|
|
document.getElementById('bar').style.width = '0%';
|
|
document.getElementById('txt').textContent = 'Ready';
|
|
}, 2000);
|
|
}
|
|
})
|
|
.catch(() => {});
|
|
}, 500);
|
|
|
|
draw();
|
|
</script>
|
|
</body>
|
|
</html>";
|
|
|
|
server = new HttpListener();
|
|
server.Prefixes.Add($"http://localhost:{port}/");
|
|
server.Start();
|
|
|
|
Process.Start(new ProcessStartInfo {
|
|
FileName = $"http://localhost:{port}/",
|
|
UseShellExecute = true
|
|
});
|
|
|
|
Log($"Server on port {port}");
|
|
|
|
Task.Run(async () => {
|
|
while (Run && server.IsListening) {
|
|
try {
|
|
var ctx = await server.GetContextAsync();
|
|
Task.Run(() => handle(ctx));
|
|
}
|
|
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;
|
|
|
|
if (queue.Count < 8) {
|
|
Log($"Done - recycled {progress} items");
|
|
queue.Clear();
|
|
progress = 0;
|
|
total = 0;
|
|
}
|
|
}
|
|
Delay(10);
|
|
}
|
|
|
|
void handle(HttpListenerContext ctx) {
|
|
try {
|
|
var req = ctx.Request;
|
|
var res = ctx.Response;
|
|
var path = req.RawUrl;
|
|
|
|
if (path == "/") {
|
|
var b = Encoding.UTF8.GetBytes(html);
|
|
res.ContentType = "text/html";
|
|
res.ContentLength64 = b.Length;
|
|
res.OutputStream.Write(b, 0, b.Length);
|
|
}
|
|
else if (path == "/recycle" && req.HttpMethod == "POST") {
|
|
using (var r = new StreamReader(req.InputStream)) {
|
|
var data = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(r.ReadToEnd());
|
|
|
|
queue.Clear();
|
|
progress = 0;
|
|
|
|
if (data.ContainsKey("delay"))
|
|
delay = System.Text.Json.JsonSerializer.Deserialize<int>(data["delay"].ToString());
|
|
|
|
if (data.ContainsKey("items")) {
|
|
var selected = System.Text.Json.JsonSerializer.Deserialize<List<Dictionary<string, int>>>(data["items"].ToString());
|
|
foreach (var s in selected) {
|
|
if (s["i"] < items.Count) {
|
|
var item = items[s["i"]];
|
|
var amt = Math.Min(s["a"], item.count);
|
|
for (int i = 0; i < amt && i < item.list.Count; i++)
|
|
queue.Add(item.list[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
total = queue.Count;
|
|
Log($"Queued {total} items @ {delay}ms");
|
|
}
|
|
res.StatusCode = 200;
|
|
}
|
|
else if (path == "/stop") {
|
|
queue.Clear();
|
|
progress = total = 0;
|
|
Log("Stopped");
|
|
res.StatusCode = 200;
|
|
}
|
|
else if (path == "/status") {
|
|
var json = $"{{\"done\":{progress},\"total\":{total}}}";
|
|
var b = Encoding.UTF8.GetBytes(json);
|
|
res.ContentType = "application/json";
|
|
res.ContentLength64 = b.Length;
|
|
res.OutputStream.Write(b, 0, b.Length);
|
|
}
|
|
else {
|
|
res.StatusCode = 404;
|
|
}
|
|
res.Close();
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
finally {
|
|
server?.Stop();
|
|
server?.Close();
|
|
Log("Shutdown");
|
|
} |