From 88f2ebd73dc6f32e770b853cbe5199e8bd832162 Mon Sep 17 00:00:00 2001 From: skelmis Date: Fri, 28 Aug 2020 00:43:48 +1200 Subject: [PATCH] Move start.py to examples dir, further improved documentation. --- docs/example.rst | 20 ++++- docs/index.rst | 4 + examples/Player.py | 4 + examples/start.py | 189 +++++++++++++++++++++++++++++++++++++++++++++ start.py | 133 ------------------------------- 5 files changed, 216 insertions(+), 134 deletions(-) create mode 100644 examples/start.py delete mode 100755 start.py diff --git a/docs/example.rst b/docs/example.rst index e6c973f..d221a99 100644 --- a/docs/example.rst +++ b/docs/example.rst @@ -3,6 +3,24 @@ Example Implementations .. currentmodule:: examples.Player .. _Players: https://github.com/ammaraskar/pyCraft/blob/master/examples/Player.py +.. _Start: https://github.com/ammaraskar/pyCraft/blob/master/examples/start.py + +Both of these examples can be used to show how to go about initiating a simple +connection to a server using `pyCraft`. + +`Note: These implementations expect to be running in the root directory of this project. +That being one directory higher then they are on the GitHub repo.` + +Basic Headless Client +~~~~~~~~~~~~~~~~~~~~~~ + +Use `python start.py --help` for the available options. + +.. automodule:: examples.start + :members: + +See the Start_ file for the implementation + Simple Player Class ~~~~~~~~~~~~~~~~~~~~ @@ -13,4 +31,4 @@ to a given server. This also handles the parsing of chat and then prints it to t .. automodule:: examples.Player :members: -See Players_ file for the implementation \ No newline at end of file +See the Players_ file for the implementation \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index 51fe364..5e465e2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,6 +16,10 @@ account, edit profiles etc The Connection class under the networking package handles connecting to a server, sending packets, listening for packets etc +The example implementation show a couple different approaches to how +you can get started with the library. One from command line and the +other being more programmatically inclined. + Contents: diff --git a/examples/Player.py b/examples/Player.py index de6b0d0..a29ed13 100644 --- a/examples/Player.py +++ b/examples/Player.py @@ -178,6 +178,10 @@ class Player: """ Actually connect to the server for this player and maintain said connection + Notes + ----- + This is a blocking function and will not return until `Disconnect()` is called on said instance. + """ self.connection.connect() diff --git a/examples/start.py b/examples/start.py new file mode 100644 index 0000000..7554d59 --- /dev/null +++ b/examples/start.py @@ -0,0 +1,189 @@ +#!/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(): + """ + Using Pythons OptionParser, get the sys args and the corresponding + input parsed as required until there is enough input to proceed. + + Returns + ------- + options + The options to run this instance with + + """ + 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(): + """Our main function for running the simple pyCraft implementation. + + This function handles and maintains: + - Gaining authentication tokens & 'logging in' + - Connecting to the provided server, online or offline + - Prints the chat packet data to standard out on Clientbound Packet + - Writes Serverbound chat Packets when required + - Dumping all packets to standard out + + Notes + ----- + This is a blocking function. + + """ + 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() diff --git a/start.py b/start.py deleted file mode 100755 index 353a158..0000000 --- a/start.py +++ /dev/null @@ -1,133 +0,0 @@ -#!/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()