Add inventory controls and enhance dashboard

This commit is contained in:
Tanner
2026-11-05 03:00:13 +00:00
parent efa2140a91
commit 659fa49ea1
13 changed files with 979 additions and 36 deletions
+73 -4
View File
@@ -14,7 +14,7 @@ import logging
log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)
from flask import Flask, abort, send_file
from flask import Flask, abort, jsonify, request, send_file
from flask_cors import CORS
build_folder = 'web_interface/build'
app = Flask(__name__, template_folder=build_folder, static_folder=build_folder, static_url_path='')
@@ -62,6 +62,38 @@ _item_icon_cache = Path(__file__).resolve().parent / 'web_interface' / 'item_cac
_item_icon_locks = {}
_item_icon_locks_guard = threading.Lock()
def _api_plain(value, depth=0):
"""Convert item component values into bounded JSON-safe structures."""
if depth > 12:
return '[nested data omitted]'
if value is None or isinstance(value, (str, int, float, bool)):
return value
if isinstance(value, dict):
return {str(key): _api_plain(item, depth + 1)
for key, item in value.items()}
if isinstance(value, (list, tuple, set)):
return [_api_plain(item, depth + 1) for item in value]
if (type(value).__name__ in ('Slot', 'ItemStackData') and
all(hasattr(value, key) for key in
('present', 'item_id', 'item_count'))):
return {
'present': value.present,
'item_id': value.item_id,
'item_count': value.item_count,
'components': _api_plain(getattr(value, 'components', {}), depth + 1),
'components_unknown': bool(getattr(value, 'components_unknown', False)),
}
if hasattr(value, 'value'):
return _api_plain(value.value, depth + 1)
if hasattr(value, 'items'):
try:
return {str(key): _api_plain(item, depth + 1)
for key, item in value.items()}
except Exception:
pass
return str(value)
def _api_json_default(value):
"""Serialize live protocol objects without invoking fragile __repr__ code."""
from minecraft.networking.packets import Packet
@@ -72,11 +104,10 @@ def _api_json_default(value):
return {'__type__': type(value).__name__,
'packet_name': getattr(value, 'packet_name', None),
'fields': fields}
if (type(value).__name__ == 'Slot' and
if (type(value).__name__ in ('Slot', 'ItemStackData') and
all(hasattr(value, key) for key in
('present', 'item_id', 'item_count'))):
return {'present': value.present, 'item_id': value.item_id,
'item_count': value.item_count}
return _api_plain(value)
if all(hasattr(value, key) for key in ('x', 'y', 'z')):
return {'x': value.x, 'y': value.y, 'z': value.z}
if isinstance(value, (bytes, bytearray)):
@@ -117,6 +148,44 @@ def api_items():
response=json.dumps(data), status=200, mimetype='application/json')
@app.route('/api/inventory/drop', methods=['POST'])
def api_inventory_drop():
origin = request.headers.get('Origin')
if origin:
from urllib.parse import urlparse
if urlparse(origin).netloc.lower() != request.host.lower():
return jsonify(ok=False, error='cross-origin inventory actions are blocked'), 403
payload = request.get_json(silent=True) or {}
if not isinstance(payload, dict):
return jsonify(ok=False, error='JSON body must be an object'), 400
try:
slot = int(payload.get('slot'))
except (TypeError, ValueError):
return jsonify(ok=False, error='slot must be an integer'), 400
action = payload.get('action')
if action not in ('one', 'stack', 'matching'):
return jsonify(ok=False, error='unknown inventory action'), 400
if not g.get('connected') or not g.get('game'):
return jsonify(ok=False, error='bot is not connected'), 503
with g.reload_lock:
item = g.inv.get(slot)
if slot < 5 or slot > 45 or not item or not getattr(item, 'present', False):
return jsonify(ok=False, error='that inventory slot is empty or protected'), 409
if action == 'matching':
count = g.game.start_dump(item.item_id)
if count is None:
return jsonify(ok=False, error='another inventory action is running'), 409
if not count:
return jsonify(ok=False, error='no matching stacks to drop'), 409
return jsonify(ok=True, action=action, stacks=count)
if not g.game.drop_inventory_slot(
slot, whole_stack=(action == 'stack')):
return jsonify(ok=False, error='another inventory action is running or the menu is open'), 409
return jsonify(ok=True, action=action, slot=slot)
def _item_texture_urls(name):
root = 'https://assets.mcasset.cloud/26.3/assets/minecraft/textures'
yield root + '/item/{}.png'.format(name)