191 lines
7.8 KiB
Python
191 lines
7.8 KiB
Python
"""Minimal current-protocol connection mode for AuthMe / offline servers.
|
|
|
|
Run with SERVER=... PLAYER_NAME=mosfet1 GAME_PASSWORD=... python -m mosfet.connect
|
|
"""
|
|
import os
|
|
import threading
|
|
from minecraft.networking.connection import Connection
|
|
from minecraft.networking.modern import (
|
|
CancelPostjoinDialog, ChatCommand, PlayGameEventPacket, PlayShowDialog,
|
|
PlayLogin, SubmitPostjoinLoginDialog, SystemChat, TeleportPosition,
|
|
)
|
|
from mosfet.protocol.managers import WorldState
|
|
from mosfet.vector import Point3D
|
|
|
|
|
|
def component_text(value):
|
|
"""Best-effort flattening of modern anonymous-NBT chat components."""
|
|
if value is None:
|
|
return ''
|
|
if isinstance(value, (str, int, float)):
|
|
return str(value)
|
|
if isinstance(value, (list, tuple)):
|
|
return ''.join(component_text(item) for item in value)
|
|
raw = getattr(value, 'value', None)
|
|
if isinstance(raw, (str, int, float)):
|
|
return str(raw)
|
|
if raw is not None and raw is not value and hasattr(raw, 'items'):
|
|
value = raw
|
|
tags = getattr(value, 'tags', None)
|
|
if tags is not None:
|
|
value = tags
|
|
if hasattr(value, 'items'):
|
|
fields = dict(value.items())
|
|
if 'text' in fields:
|
|
return component_text(fields['text']) + component_text(fields.get('extra', []))
|
|
if 'translate' in fields:
|
|
return component_text(fields['translate']) + component_text(fields.get('with', []))
|
|
return ''.join(component_text(v) for v in fields.values())
|
|
if isinstance(value, (list, tuple)):
|
|
return ''.join(component_text(item) for item in value)
|
|
return str(value)
|
|
|
|
|
|
def main(global_state=None):
|
|
server = os.environ.get('SERVER', '127.0.0.1')
|
|
port = int(os.environ.get('PORT', '25565'))
|
|
username = os.environ.get('PLAYER_NAME', 'mosfet1')
|
|
password = os.environ.get('GAME_PASSWORD', 'mosfetbot')
|
|
|
|
connection = Connection(server, port, username=username,
|
|
allowed_versions=['26.3'],
|
|
handle_exception=False)
|
|
connection.authme_password = password
|
|
connection.world_state = WorldState()
|
|
connection.dimension_types_by_id = []
|
|
sent_auth_commands = set()
|
|
if global_state is not None:
|
|
global_state.connection = connection
|
|
global_state.name = username
|
|
global_state.connected = False
|
|
global_state.connection_status = 'connecting'
|
|
global_state.player_position = None
|
|
global_state.world_state = connection.world_state
|
|
global_state.chunks = connection.world_state
|
|
from mosfet.protocol.managers import DataManager
|
|
global_state.mcdata = DataManager()
|
|
|
|
@connection.listener(PlayLogin)
|
|
def on_play_login(packet):
|
|
if global_state is not None:
|
|
global_state.connected = True
|
|
global_state.connection_status = 'in_game'
|
|
global_state.player_id = packet.player_id
|
|
global_state.game_mode = packet.game_mode
|
|
global_state.dimension = packet.dimension.removeprefix('minecraft:')
|
|
packet.render_distance = packet.chunk_radius
|
|
global_state.info = packet
|
|
global_state.eid = packet.player_id
|
|
global_state.command_lock = False
|
|
from mosfet.world import World
|
|
from mosfet.commands import Commands
|
|
from mosfet.protocol.modern import ModernChatManager
|
|
from mosfet.protocol.managers import EntityManager, InventoryManager
|
|
from mosfet.bot import start_automation
|
|
global_state.world = World(global_state)
|
|
global_state.chat = ModernChatManager(global_state)
|
|
global_state.entities = EntityManager(global_state)
|
|
global_state.inventory = InventoryManager(global_state)
|
|
global_state.commands = Commands(global_state)
|
|
start_automation(global_state)
|
|
|
|
|
|
def send_auth_command(action):
|
|
if action in sent_auth_commands:
|
|
return
|
|
sent_auth_commands.add(action)
|
|
command = '/register {0} {0}'.format(password) if action == 'register' \
|
|
else '/login ' + password
|
|
print('[AuthMe] sending ' + command.split()[0], flush=True)
|
|
connection.write_packet(ChatCommand(command=command.lstrip('/')),
|
|
force=True)
|
|
|
|
@connection.listener(PlayGameEventPacket)
|
|
def on_game_event(packet):
|
|
if global_state is not None and packet.event == 3:
|
|
global_state.game_mode = int(packet.value)
|
|
|
|
@connection.listener(TeleportPosition)
|
|
def on_spawn(packet):
|
|
if global_state is not None:
|
|
position = getattr(connection, 'player_position', None)
|
|
if position is None:
|
|
position = (packet.x, packet.y, packet.z,
|
|
packet.yaw, packet.pitch)
|
|
x, y, z, yaw, pitch = position
|
|
global_state.player_position = (x, y, z)
|
|
global_state.pos = Point3D(global_state.player_position)
|
|
global_state.yaw = yaw
|
|
global_state.pitch = pitch
|
|
if getattr(connection, 'authme_mode', 'register') == 'register':
|
|
send_auth_command('register')
|
|
|
|
@connection.listener(PlayShowDialog)
|
|
def on_authme_dialog(packet):
|
|
is_login = b'login' in packet.payload.lower()
|
|
print('[AuthMe] dismissing the post-join dialog to use /commands.',
|
|
flush=True)
|
|
if is_login:
|
|
connection.write_packet(SubmitPostjoinLoginDialog(
|
|
password=password), force=True)
|
|
else:
|
|
connection.write_packet(CancelPostjoinDialog(), force=True)
|
|
send_auth_command('register')
|
|
|
|
@connection.listener(SystemChat)
|
|
def on_system_chat(packet):
|
|
message = component_text(packet.content)
|
|
if global_state is not None:
|
|
global_state.last_chat = message
|
|
chat = global_state.get('chat')
|
|
if chat is not None:
|
|
chat.handle_system_chat(packet, message)
|
|
print('[server] ' + message, flush=True)
|
|
lowered = message.lower()
|
|
if 'already registered' in lowered:
|
|
send_auth_command('login')
|
|
elif 'register' in lowered and any(word in lowered for word in
|
|
('password', 'account', '/register')):
|
|
send_auth_command('register')
|
|
elif any(word in lowered for word in
|
|
('please login', 'please log in', 'log in', '/login',
|
|
'authenticate', 'authentication required')):
|
|
send_auth_command('login')
|
|
|
|
connection.connect()
|
|
print('Connected as {} to {}:{} using offline protocol 777.'.format(
|
|
username, server, port), flush=True)
|
|
shutdown_requested = False
|
|
try:
|
|
while connection.connected:
|
|
if global_state is not None and not global_state.get('running', True):
|
|
shutdown_requested = True
|
|
break
|
|
thread = connection.networking_thread
|
|
if thread is None:
|
|
break
|
|
thread.join(timeout=0.5)
|
|
if connection.connected:
|
|
connection.disconnect(immediate=True)
|
|
if connection.exception and not shutdown_requested:
|
|
raise connection.exception
|
|
print('Connection ended.', flush=True)
|
|
except KeyboardInterrupt:
|
|
shutdown_requested = True
|
|
print('Disconnecting...', flush=True)
|
|
connection.disconnect()
|
|
finally:
|
|
if connection.connected:
|
|
connection.disconnect(immediate=True)
|
|
if global_state is not None:
|
|
worker = global_state.get('movement_thread')
|
|
if worker and worker is not threading.current_thread():
|
|
worker.join(timeout=1.0)
|
|
global_state.connection = False
|
|
global_state.connected = False
|
|
global_state.connection_status = 'disconnected'
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|