Files
openttd-client/main_admin.py
T
kovagoadiandClaude 25953bea06
Continuous Integration / lint-and-security (pull_request) Successful in 20s
Continuous Integration / tests-and-coverage (pull_request) Successful in 25s
Fix ruff lint findings across client, protocol, and tests
Resolve 51 findings from the I/RUF/BLE/TRY002/S110/PLR0402 rule set:

- Sort imports and __all__ (I001, RUF022, PLR0402). The sys.path.insert
  calls in check_public_calls.py and tests/test_e2e.py still precede the
  openttd imports that depend on them.
- Replace unused unpacked values with _ (RUF059) and annotate the two
  timetable lookup tables as ClassVar (RUF012).
- Narrow the best-effort excepts in OpenTTDClient.quit and
  OpenTTDAdminClient.quit to (OSError, SocketClosed) and log at debug
  rather than swallowing silently (BLE001, S110). The test doubles now
  raise an OSError subclass so they still exercise that branch.
- Narrow the gamescript JSON fallback to json.JSONDecodeError. The broad
  catch in receive_packet keeps a noqa: it guards untrusted wire data and
  must degrade to a no-op packet instead of killing the connection.
- Use contextlib.suppress instead of try/except/pass in tests.

ruff check . is clean, 102 tests pass, coverage stays at 100%.

Co-Authored-By: Claude <[email protected]>
2026-08-26 20:57:56 +02:00

101 lines
4.1 KiB
Python

import asyncio
import logging
import os
import sys
# Add the lib directory to sys.path so we can import the openttd package
sys.path.append(os.path.join(os.path.dirname(__file__), 'lib'))
from openttd import OpenTTDAdminClient
from openttd.protocol import AdminUpdateFrequency, AdminUpdateType
# Configuration
SERVER_HOST = "127.0.0.1"
ADMIN_PORT = 3977
ADMIN_PASSWORD = "asd"
async def run_admin():
admin = OpenTTDAdminClient(host=SERVER_HOST, port=ADMIN_PORT, admin_name="GeminiAdmin")
# Setup callbacks
def chat_logger(**kwargs):
print(f">>> [ADMIN CHAT] <{kwargs.get('client_id')}> {kwargs.get('message')}")
def console_logger(**kwargs):
print(f">>> [CONSOLE] [{kwargs.get('origin')}] {kwargs.get('text')}")
def gamescript_logger(data):
print(f">>> [GAMESCRIPT] {data}")
admin.on_chat = chat_logger
admin.on_console = console_logger
admin.on_gamescript = gamescript_logger
try:
await admin.connect(admin_password=ADMIN_PASSWORD, secure=True)
await admin.joined.wait()
print("--- Admin joined ---")
# Initial poll
print("--- Initial Poll ---")
await admin.poll_companies()
await admin.poll_clients()
# Subscribe to everything
await admin.update_frequency(AdminUpdateType.ClientInfo, AdminUpdateFrequency.Automatic)
await admin.update_frequency(AdminUpdateType.CompanyInfo, AdminUpdateFrequency.Automatic)
await admin.update_frequency(AdminUpdateType.Chat, AdminUpdateFrequency.Automatic)
await admin.update_frequency(AdminUpdateType.Gamescript, AdminUpdateFrequency.Automatic)
print("--- Requesting vehicle info via GameScript ---")
await admin.list_vehicles()
# Capture station-list replies (delivered to on_gamescript, like list_vehicles) while
# still logging every other GameScript message.
stations = []
def gamescript_capture(data):
if isinstance(data, dict) and "stations" in data:
stations.append(data["stations"])
gamescript_logger(data)
admin.on_gamescript = gamescript_capture
print("--- Requesting station info via GameScript ---")
await admin.list_stations()
await asyncio.sleep(1)
# Fetch one station's authoritative live cargo (real-time waiting + planned).
if stations and stations[-1]:
sid = stations[-1][0]["id"]
try:
data = await admin.get_station(sid, timeout=10.0)
print(f"--- Station {sid} ({data.get('name')}) cargo: real-time waiting vs planned ---")
for cargo in data.get("cargo", []):
print(f" cargo {cargo['cargo_id']}: waiting={cargo['waiting']} "
f"planned={cargo['planned']} rating={cargo['rating']}")
# Break the first cargo down by source station and by next hop (routing destination).
if data.get("cargo"):
cid = data["cargo"][0]["cargo_id"]
flow = await admin.get_station_cargo(sid, cid, timeout=10.0)
print(f"--- Station {sid} cargo {cid} flow breakdown (station 65535 = none/deleted) ---")
print(f" waiting by source: {flow['waiting_by_from']}")
print(f" waiting by next hop: {flow['waiting_by_via']}")
print(f" planned by source: {flow['planned_by_from']}")
print(f" planned by next hop: {flow['planned_by_via']}")
except Exception as e: # noqa: BLE001 - demo script: one failed station query should not abort the walk
print(f"!!! station query failed: {e}")
await asyncio.sleep(5)
print("--- Quitting ---")
await admin.quit()
except Exception as e: # noqa: BLE001 - top-level demo handler: report any failure instead of dumping a traceback
print(f"!!! Error: {e}")
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO, format='%(levelname)s:%(name)s:%(message)s')
try:
asyncio.run(run_admin())
except KeyboardInterrupt:
pass