Move start.py to examples dir, further improved documentation.

This commit is contained in:
skelmis 2020-08-28 00:43:48 +12:00
parent 5e984ffd8e
commit 88f2ebd73d
5 changed files with 216 additions and 134 deletions

View File

@ -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
See the Players_ file for the implementation

View File

@ -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:

View File

@ -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()

189
examples/start.py Normal file
View File

@ -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<host>[^\[\]:]+)|\[(?P<addr>[^\[\]]+)\])" r"(:(?P<port>\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()

133
start.py
View File

@ -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<host>[^\[\]:]+)|\[(?P<addr>[^\[\]]+)\])"
r"(:(?P<port>\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()