diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2a04f14 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,11 @@ +certifi==2020.6.20 +cffi==1.14.2 +chardet==3.0.4 +cryptography==3.1 +idna==2.10 +pycparser==2.20 +pyCraft @ git+https://github.com/ammaraskar/pyCraft.git@cf93923acc2dcfbc076379b43842228d77aea188 +PyNBT==3.0.0 +requests==2.24.0 +six==1.15.0 +urllib3==1.25.10 diff --git a/start.py b/start.py new file mode 100644 index 0000000..353a158 --- /dev/null +++ b/start.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python + +import getpass +import sys +import re +from optparse import OptionParser + +from minecraft import authentication +from minecraft.exceptions import YggdrasilError +from minecraft.networking.connection import Connection +from minecraft.networking.packets import Packet, clientbound, serverbound + + +def get_options(): + parser = OptionParser() + + parser.add_option("-u", "--username", dest="username", default=None, + help="username to log in with") + + parser.add_option("-p", "--password", dest="password", default=None, + help="password to log in with") + + parser.add_option("-s", "--server", dest="server", default=None, + help="server host or host:port " + "(enclose IPv6 addresses in square brackets)") + + parser.add_option("-o", "--offline", dest="offline", action="store_true", + help="connect to a server in offline mode " + "(no password required)") + + parser.add_option("-d", "--dump-packets", dest="dump_packets", + action="store_true", + help="print sent and received packets to standard error") + + parser.add_option("-v", "--dump-unknown-packets", dest="dump_unknown", + action="store_true", + help="include unknown packets in --dump-packets output") + + (options, args) = parser.parse_args() + + if not options.username: + options.username = input("Enter your username: ") + + if not options.password and not options.offline: + options.password = getpass.getpass("Enter your password (leave " + "blank for offline mode): ") + options.offline = options.offline or (options.password == "") + + if not options.server: + options.server = input("Enter server host or host:port " + "(enclose IPv6 addresses in square brackets): ") + # Try to split out port and address + match = re.match(r"((?P[^\[\]:]+)|\[(?P[^\[\]]+)\])" + r"(:(?P\d+))?$", options.server) + if match is None: + raise ValueError("Invalid server address: '%s'." % options.server) + options.address = match.group("host") or match.group("addr") + options.port = int(match.group("port") or 25565) + + return options + + +def main(): + options = get_options() + + if options.offline: + print("Connecting in offline mode...") + connection = Connection( + options.address, options.port, username=options.username) + else: + auth_token = authentication.AuthenticationToken() + try: + auth_token.authenticate(options.username, options.password) + except YggdrasilError as e: + print(e) + sys.exit() + print("Logged in as %s..." % auth_token.username) + connection = Connection( + options.address, options.port, auth_token=auth_token) + + if options.dump_packets: + def print_incoming(packet): + if type(packet) is Packet: + # This is a direct instance of the base Packet type, meaning + # that it is a packet of unknown type, so we do not print it + # unless explicitly requested by the user. + if options.dump_unknown: + print('--> [unknown packet] %s' % packet, file=sys.stderr) + else: + print('--> %s' % packet, file=sys.stderr) + + def print_outgoing(packet): + print('<-- %s' % packet, file=sys.stderr) + + connection.register_packet_listener( + print_incoming, Packet, early=True) + connection.register_packet_listener( + print_outgoing, Packet, outgoing=True) + + def handle_join_game(join_game_packet): + print('Connected.') + + connection.register_packet_listener( + handle_join_game, clientbound.play.JoinGamePacket) + + def print_chat(chat_packet): + print("Message (%s): %s" % ( + chat_packet.field_string('position'), chat_packet.json_data)) + + connection.register_packet_listener( + print_chat, clientbound.play.ChatMessagePacket) + + connection.connect() + + while True: + try: + text = input() + if text == "/respawn": + print("respawning...") + packet = serverbound.play.ClientStatusPacket() + packet.action_id = serverbound.play.ClientStatusPacket.RESPAWN + connection.write_packet(packet) + else: + packet = serverbound.play.ChatPacket() + packet.message = text + connection.write_packet(packet) + except KeyboardInterrupt: + print("Bye!") + sys.exit() + + +if __name__ == "__main__": + main()