"""Chat adapter for the Minecraft 26.3 play protocol.""" import re import time from contextlib import nullcontext from minecraft.networking.modern import ( ChatCommand, ClientboundPlayerChatPacket, ServerboundChatPacket, SystemChat, ) def parse_incoming_private_message(message): """Return (sender_name, body) for common Minecraft incoming-DM forms.""" patterns = ( r'^([A-Za-z0-9_]{1,16}) whispers(?: to you)?:\s*(.*)$', r'^([A-Za-z0-9_]{1,16})\s*->\s*me:\s*(.*)$', r'^From ([A-Za-z0-9_]{1,16}):\s*(.*)$', ) for pattern in patterns: match = re.match(pattern, message, re.DOTALL) if match: return match.group(1), match.group(2) return None class ModernChatManager: def __init__(self, global_state): self.g = global_state self.handler = None self.g.connection.register_packet_listener( self.handle_player_chat, ClientboundPlayerChatPacket) def set_handler(self, callback): self.handler = callback def handle_player_chat(self, packet): sender = str(packet.sender) sender_name = self.g.get('player_names', {}).get(sender, sender) registry = getattr(self.g.connection, 'registry_ids', {}) chat_types = registry.get('minecraft:chat_type', {}) chat_type_id = getattr(packet, 'chat_type_id', None) chat_type = next((name for name, index in chat_types.items() if index == chat_type_id), None) is_dm = ( getattr(packet, 'network_target_name_present', False) or chat_type in ( 'minecraft:msg_command_incoming', 'minecraft:msg_command_outgoing', 'minecraft:team_msg_command_incoming', 'minecraft:team_msg_command_outgoing', ) ) label = '[DM] ' if is_dm else '' print('[chat] {}{}: {}'.format(label, sender_name, packet.message), flush=True) if self.handler: with self.g.get('reload_lock') or nullcontext(): if is_dm: text = '{} -> me: {}'.format(sender_name, packet.message) self.handler(('DM', sender, text, sender_name)) else: text = '{}: {}'.format(sender_name, packet.message) self.handler(('CHAT', sender, text)) def handle_system_chat(self, packet, message): incoming = parse_incoming_private_message(message) if incoming: sender_name, body = incoming print('[chat] [DM] {}: {}'.format(sender_name, body), flush=True) if self.handler: text = '{} -> me: {}'.format(sender_name, body) with self.g.get('reload_lock') or nullcontext(): self.handler(('DM', sender_name, text, sender_name)) return True if self.handler: with self.g.get('reload_lock') or nullcontext(): self.handler(('SYSTEM', None, message)) return False def send(self, message): if not message: return if message.startswith('/'): self.g.connection.write_packet( ChatCommand(command=message[1:]), force=True) else: packet = ServerboundChatPacket( message=message, timestamp=int(time.time() * 1000)) self.g.connection.write_packet(packet, force=True)