Merge pull request #4 from TargetedEntropy/Skelmis-master
Merging Skelmis master
This commit is contained in:
commit
742d7c5e57
|
@ -16,7 +16,7 @@ communication with a MineCraft server.
|
|||
Detailed information for developers can be found here:
|
||||
`<http://pycraft.readthedocs.org/en/latest/>`_.
|
||||
|
||||
``start.py`` is a basic example of a headless client using the library
|
||||
``start.py`` is a basic example of a headless client using the library that can be found under the `examples` folder.
|
||||
Use ``start.py --help`` for the options.
|
||||
|
||||
Supported Minecraft versions
|
||||
|
|
198
docs/Makefile
198
docs/Makefile
|
@ -1,192 +1,20 @@
|
|||
# Makefile for Sphinx documentation
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# User-friendly check for sphinx-build
|
||||
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
|
||||
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
|
||||
endif
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " applehelp to make an Apple Help Book"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " gettext to make PO message catalogs"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " xml to make Docutils-native XML files"
|
||||
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
@echo " coverage to run coverage check of the documentation (if enabled)"
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)/*
|
||||
.PHONY: help Makefile
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyCraft.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyCraft.qhc"
|
||||
|
||||
applehelp:
|
||||
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
|
||||
@echo
|
||||
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
|
||||
@echo "N.B. You won't be able to view it unless you put it in" \
|
||||
"~/Library/Documentation/Help or install it in your application" \
|
||||
"bundle."
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/pyCraft"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyCraft"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
latexpdfja:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through platex and dvipdfmx..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
|
||||
coverage:
|
||||
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
|
||||
@echo "Testing of coverage in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/coverage/python.txt."
|
||||
|
||||
xml:
|
||||
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
|
||||
@echo
|
||||
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
|
||||
|
||||
pseudoxml:
|
||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
||||
@echo
|
||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
|
128
docs/conf.py
128
docs/conf.py
|
@ -20,44 +20,45 @@ import shlex
|
|||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.insert(0, os.path.abspath('../'))
|
||||
sys.path.insert(0, os.path.abspath("../"))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.todo',
|
||||
'sphinx.ext.coverage',
|
||||
'sphinx.ext.viewcode',
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.intersphinx",
|
||||
"sphinx.ext.todo",
|
||||
"sphinx.ext.coverage",
|
||||
"sphinx.ext.viewcode",
|
||||
"sphinx.ext.napoleon",
|
||||
]
|
||||
|
||||
autoclass_content = 'both'
|
||||
autoclass_content = "both"
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
templates_path = ["_templates"]
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
source_suffix = ".rst"
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
# source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
master_doc = "index"
|
||||
|
||||
# General information about the project.
|
||||
project = u'pyCraft'
|
||||
copyright = u'2015, Ammar Askar'
|
||||
author = u'Ammar Askar'
|
||||
project = u"pyCraft"
|
||||
copyright = u"2015, Ammar Askar"
|
||||
author = u"Ammar Askar"
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
|
@ -77,37 +78,37 @@ language = None
|
|||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
# today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
exclude_patterns = ["_build"]
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
#default_role = None
|
||||
# default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
# add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
# add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
# show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
pygments_style = "sphinx"
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
# modindex_common_prefix = []
|
||||
|
||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||
#keep_warnings = False
|
||||
# keep_warnings = False
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = True
|
||||
|
@ -130,94 +131,94 @@ if os.environ.get("READTHEDOCS", "") != "True":
|
|||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
# html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
# html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
# html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
# html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
# html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
# html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
html_static_path = ["_static"]
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
#html_extra_path = []
|
||||
# html_extra_path = []
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
# html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
# html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
# html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
# html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
# html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
# html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
# html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
# html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
# html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
# html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
# html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
# html_file_suffix = None
|
||||
|
||||
# Language to be used for generating the HTML full-text search index.
|
||||
# Sphinx supports the following languages:
|
||||
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
|
||||
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
|
||||
#html_search_language = 'en'
|
||||
# html_search_language = 'en'
|
||||
|
||||
# A dictionary with options for the search language support, empty by default.
|
||||
# Now only 'ja' uses this config value
|
||||
#html_search_options = {'type': 'default'}
|
||||
# html_search_options = {'type': 'default'}
|
||||
|
||||
# The name of a javascript file (relative to the configuration directory) that
|
||||
# implements a search results scorer. If empty, the default will be used.
|
||||
#html_search_scorer = 'scorer.js'
|
||||
# html_search_scorer = 'scorer.js'
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'pyCraftdoc'
|
||||
htmlhelp_basename = "pyCraftdoc"
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
|
@ -245,36 +246,33 @@ latex_documents = [
|
|||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
# latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
# latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
# latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
# latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
# latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
# latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'pycraft', u'pyCraft Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
man_pages = [(master_doc, "pycraft", u"pyCraft Documentation", [author], 1)]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
# man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
@ -289,17 +287,17 @@ texinfo_documents = [
|
|||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
# texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
# texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
# texinfo_show_urls = 'footnote'
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
#texinfo_no_detailmenu = False
|
||||
# texinfo_no_detailmenu = False
|
||||
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {'https://docs.python.org/': None}
|
||||
intersphinx_mapping = {"https://docs.python.org/": None}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
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
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This implements all the required functionality to connect and maintain a connection
|
||||
to a given server. This also handles the parsing of chat and then prints it to the screen.
|
||||
|
||||
.. automodule:: examples.Player
|
||||
:members:
|
||||
|
||||
See the Players_ file for the implementation
|
|
@ -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:
|
||||
|
||||
|
@ -24,3 +28,4 @@ Contents:
|
|||
|
||||
authentication
|
||||
connecting
|
||||
example
|
||||
|
|
246
docs/make.bat
246
docs/make.bat
|
@ -1,62 +1,18 @@
|
|||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||
set I18NSPHINXOPTS=%SPHINXOPTS% .
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "help" (
|
||||
:help
|
||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||
echo. html to make standalone HTML files
|
||||
echo. dirhtml to make HTML files named index.html in directories
|
||||
echo. singlehtml to make a single large HTML file
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and a HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. devhelp to make HTML files and a Devhelp project
|
||||
echo. epub to make an epub
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. text to make text files
|
||||
echo. man to make manual pages
|
||||
echo. texinfo to make Texinfo files
|
||||
echo. gettext to make PO message catalogs
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. xml to make Docutils-native XML files
|
||||
echo. pseudoxml to make pseudoxml-XML files for display purposes
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
echo. coverage to run coverage check of the documentation if enabled
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||
del /q /s %BUILDDIR%\*
|
||||
goto end
|
||||
)
|
||||
|
||||
|
||||
REM Check if sphinx-build is available and fallback to Python version if any
|
||||
%SPHINXBUILD% 2> nul
|
||||
if errorlevel 9009 goto sphinx_python
|
||||
goto sphinx_ok
|
||||
|
||||
:sphinx_python
|
||||
|
||||
set SPHINXBUILD=python -m sphinx.__init__
|
||||
%SPHINXBUILD% 2> nul
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
|
@ -69,195 +25,11 @@ if errorlevel 9009 (
|
|||
exit /b 1
|
||||
)
|
||||
|
||||
:sphinx_ok
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "singlehtml" (
|
||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pyCraft.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pyCraft.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "devhelp" (
|
||||
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "epub" (
|
||||
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latexpdf" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
cd %BUILDDIR%/latex
|
||||
make all-pdf
|
||||
cd %~dp0
|
||||
echo.
|
||||
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latexpdfja" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
cd %BUILDDIR%/latex
|
||||
make all-pdf-ja
|
||||
cd %~dp0
|
||||
echo.
|
||||
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "text" (
|
||||
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The text files are in %BUILDDIR%/text.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "man" (
|
||||
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "texinfo" (
|
||||
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "gettext" (
|
||||
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.The overview file is in %BUILDDIR%/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in %BUILDDIR%/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "coverage" (
|
||||
%SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of coverage in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/coverage/python.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "xml" (
|
||||
%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The XML files are in %BUILDDIR%/xml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pseudoxml" (
|
||||
%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
|
||||
goto end
|
||||
)
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
import re
|
||||
import json
|
||||
|
||||
"""
|
||||
A file for Player utilities, focused around parsing chat and making it human readable.
|
||||
|
||||
The DefaultParser should be able to handle most situations currently,
|
||||
however, there are known weakness's in the approach but as it stands,
|
||||
it is better then other examples I have seen.
|
||||
|
||||
DefaultParser - Tested on mc-central, should work decent globally
|
||||
"""
|
||||
|
||||
# TODO Parse banner messages, example:
|
||||
# https://gyazo.com/c0a4cfee23a31fe8b6e4c7c7848e5e5a
|
||||
|
||||
|
||||
def DefaultParser(data):
|
||||
"""The default Player chat packet parser, designed to make chat human readable.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : Chat Packet
|
||||
The chat packet to be parsed.
|
||||
|
||||
Returns
|
||||
-------
|
||||
message : str
|
||||
The chat message in human readable form
|
||||
False : bool
|
||||
If the parser encounters an error during parsing
|
||||
|
||||
"""
|
||||
try:
|
||||
# Convert to valid python dict
|
||||
data = json.loads(data)
|
||||
|
||||
# Create the prefix & text
|
||||
prefixing = True
|
||||
data = data["extra"]
|
||||
stringDict = {"prefix": [], "message": []}
|
||||
dm = False
|
||||
|
||||
if isinstance(data[len(data) - 1], str):
|
||||
# Given the last item is a string, rather then dictionary
|
||||
# we can safely assume that this is in fact a /msg
|
||||
dm = True
|
||||
|
||||
for i, item in enumerate(data):
|
||||
# Remove minecraft character stuff
|
||||
if dm and i == len(data) - 1:
|
||||
stringDict["message"].append(item)
|
||||
continue
|
||||
|
||||
text = re.sub(
|
||||
r"\§c|\§f|\§b|\§d|\§a|\§1|\§2|\§3|\§4|\§5|\§6|\§7|\§8|\§9|\§0",
|
||||
"",
|
||||
item["text"],
|
||||
)
|
||||
|
||||
if text.lstrip().rstrip() == ":" and prefixing:
|
||||
# No longer need to handle the before message
|
||||
prefixing = False
|
||||
continue
|
||||
elif prefixing:
|
||||
stringDict["prefix"].append(text)
|
||||
elif not prefixing:
|
||||
if "extra" in item:
|
||||
# Chat parsing for text means this is most likely another
|
||||
# nested dict in list situation
|
||||
if len(item["extra"]) > 0:
|
||||
if "text" in item["extra"][0]:
|
||||
text = item["extra"][0]["text"]
|
||||
stringDict["message"].append(text)
|
||||
|
||||
prefix = "".join(stringDict["prefix"])
|
||||
text = " ".join(stringDict["message"]).rstrip().lstrip()
|
||||
|
||||
if len(prefix) > 0 and len(text) > 0:
|
||||
message = ": ".join([prefix, text])
|
||||
elif len(prefix) > 0:
|
||||
message = prefix
|
||||
elif len(text) > 0:
|
||||
message = text
|
||||
|
||||
message = message.lstrip().rstrip()
|
||||
|
||||
return message
|
||||
|
||||
except Exception as e:
|
||||
# print(f"Unable to parse: {data}\nException: {e}")
|
||||
return False
|
|
@ -0,0 +1,237 @@
|
|||
import re
|
||||
import time
|
||||
import asyncio
|
||||
from concurrent.futures.thread import ThreadPoolExecutor
|
||||
|
||||
from minecraft import authentication
|
||||
from minecraft.exceptions import YggdrasilError
|
||||
from minecraft.networking.connection import Connection
|
||||
from minecraft.networking.packets import serverbound, clientbound
|
||||
|
||||
from .Parsers import DefaultParser
|
||||
|
||||
|
||||
class Player:
|
||||
"""
|
||||
A class built to handle all required actions to maintain:
|
||||
- Gaining auth tokens, and connecting to online minecraft servers.
|
||||
- Clientbound chat
|
||||
- Serverbound chat
|
||||
|
||||
Warnings
|
||||
--------
|
||||
This class explicitly expects a username & password, then expects to
|
||||
be able to connect to a server in online mode.
|
||||
If you wish to add different functionality please view the example
|
||||
headless client, `start.py`, for how to implement it.
|
||||
"""
|
||||
|
||||
def __init__(self, username, password, *, admins=None):
|
||||
"""
|
||||
Init handles the following:
|
||||
- Client Authentication
|
||||
- Setting the current connection state
|
||||
- Setting the recognized 'admins' for this instance
|
||||
|
||||
Parameters
|
||||
----------
|
||||
username : String
|
||||
Used for authentication
|
||||
password : String
|
||||
Used for authentication
|
||||
admins : list, optional
|
||||
The minecraft accounts to auto accept tpa's requests from
|
||||
|
||||
Raises
|
||||
------
|
||||
YggdrasilError
|
||||
Username or Password was incorrect
|
||||
|
||||
"""
|
||||
self.kickout = False
|
||||
self.admins = [] if admins is None else admins
|
||||
|
||||
self.auth_token = authentication.AuthenticationToken()
|
||||
self.auth_token.authenticate(username, password)
|
||||
|
||||
def Parser(self, data):
|
||||
"""
|
||||
Converts the chat packet received from the server
|
||||
into human readable strings
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : JSON
|
||||
The chat data json receive from the server
|
||||
|
||||
Returns
|
||||
-------
|
||||
message : String
|
||||
The text received from the server in human readable form
|
||||
|
||||
"""
|
||||
message = DefaultParser(
|
||||
data) # This is where you would call other parsers
|
||||
|
||||
if not message:
|
||||
return False
|
||||
|
||||
if "teleport" in message.lower():
|
||||
self.HandleTpa(message)
|
||||
|
||||
return message
|
||||
|
||||
def HandleTpa(self, message):
|
||||
"""
|
||||
Using the given message, figure out whether or not to accept the tpa
|
||||
|
||||
Parameters
|
||||
----------
|
||||
message : String
|
||||
The current chat, where 'tpa' was found in message.lower()
|
||||
|
||||
"""
|
||||
try:
|
||||
found = re.search(
|
||||
"(.+?) has requested that you teleport to them.", message
|
||||
).group(1)
|
||||
if found in self.admins:
|
||||
self.SendChat("/tpyes")
|
||||
return
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
found = re.search(
|
||||
"(.+?) has requested to teleport to you.",
|
||||
message).group(1)
|
||||
if found in self.admins:
|
||||
self.SendChat("/tpyes")
|
||||
return
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def SendChat(self, msg):
|
||||
"""
|
||||
Send a given message to the server
|
||||
|
||||
Parameters
|
||||
----------
|
||||
msg : String
|
||||
The message to send to the server
|
||||
|
||||
"""
|
||||
msg = str(msg)
|
||||
if len(msg) > 0:
|
||||
packet = serverbound.play.ChatPacket()
|
||||
packet.message = msg
|
||||
self.connection.write_packet(packet)
|
||||
|
||||
def ReceiveChat(self, chat_packet):
|
||||
"""
|
||||
The listener for ClientboundChatPackets
|
||||
|
||||
Parameters
|
||||
----------
|
||||
chat_packet : ClientboundChatPacket
|
||||
The incoming chat packet
|
||||
chat_packet.json : JSON
|
||||
The chat packet to pass of to our Parser for handling
|
||||
|
||||
"""
|
||||
message = self.Parser(chat_packet.json_data)
|
||||
if not message:
|
||||
# This means our Parser failed lol
|
||||
print("Parser failed")
|
||||
return
|
||||
|
||||
print(message)
|
||||
|
||||
def SetServer(self, ip, port=25565, handler=None):
|
||||
"""
|
||||
Sets the server, ready for connection
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ip : str
|
||||
The server to connect to
|
||||
port : int, optional
|
||||
The port to connect on
|
||||
handler : Function pointer, optional
|
||||
Points to the function used to handle Clientbound chat packets
|
||||
|
||||
"""
|
||||
handler = handler or self.ReceiveChat
|
||||
|
||||
self.ip = ip
|
||||
self.port = port
|
||||
self.connection = Connection(
|
||||
ip, port, auth_token=self.auth_token, handle_exception=print
|
||||
)
|
||||
|
||||
self.connection.register_packet_listener(
|
||||
handler, clientbound.play.ChatMessagePacket
|
||||
)
|
||||
|
||||
self.connection.exception_handler(print)
|
||||
|
||||
def Connect(self):
|
||||
"""
|
||||
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()
|
||||
|
||||
print(f"Connected to server with: {self.auth_token.username}")
|
||||
|
||||
while True:
|
||||
time.sleep(1)
|
||||
if self.kickout:
|
||||
break
|
||||
|
||||
def Disconnect(self):
|
||||
"""
|
||||
In order to disconnect the client, and break the blocking loop
|
||||
this method must be called
|
||||
|
||||
"""
|
||||
self.kickout = True
|
||||
self.connection.disconnect()
|
||||
|
||||
|
||||
async def Main():
|
||||
try:
|
||||
player = Player("Account Email/Username", "Account Password")
|
||||
except YggdrasilError as e:
|
||||
# Authentication Error
|
||||
print("Incorrect Login", e)
|
||||
return
|
||||
|
||||
player.SetServer("Server to connect to.")
|
||||
|
||||
# We do this to ensure it is non blocking as Connect() is a
|
||||
# forever loop used to maintain a connection to a server
|
||||
executor = ThreadPoolExecutor()
|
||||
executor.submit(player.Connect)
|
||||
|
||||
# Forever do things unless the user wants us to logout
|
||||
while True:
|
||||
message = input("What should I do/say?\n")
|
||||
|
||||
# Disconnect the client from the server before finishing everything up
|
||||
if message.lower() in ["logout", "disconnected", "exit"]:
|
||||
player.Disconnect()
|
||||
print("Disconnected")
|
||||
return
|
||||
|
||||
# Send the message to the server via the player
|
||||
player.SendChat(message)
|
||||
|
||||
|
||||
# Simply run our program
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(Main())
|
|
@ -12,29 +12,66 @@ 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(
|
||||
"-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(
|
||||
"-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(
|
||||
"-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(
|
||||
"-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(
|
||||
"-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")
|
||||
parser.add_option(
|
||||
"-v",
|
||||
"--dump-unknown-packets",
|
||||
dest="dump_unknown",
|
||||
action="store_true",
|
||||
help="include unknown packets in --dump-packets output",
|
||||
)
|
||||
|
||||
parser.add_option(
|
||||
"-m",
|
||||
|
@ -55,11 +92,15 @@ def get_options():
|
|||
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): ")
|
||||
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)
|
||||
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")
|
||||
|
@ -69,12 +110,27 @@ def get_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)
|
||||
options.address, options.port, username=options.username
|
||||
)
|
||||
else:
|
||||
|
||||
try:
|
||||
|
@ -92,33 +148,36 @@ def main():
|
|||
"1.8")
|
||||
|
||||
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)
|
||||
print("--> [unknown packet] %s" % packet, file=sys.stderr)
|
||||
else:
|
||||
print('--> %s' % packet, file=sys.stderr)
|
||||
print("--> %s" % packet, file=sys.stderr)
|
||||
|
||||
def print_outgoing(packet):
|
||||
print('<-- %s' % packet, file=sys.stderr)
|
||||
print("<-- %s" % packet, file=sys.stderr)
|
||||
|
||||
connection.register_packet_listener(
|
||||
print_incoming, Packet, early=True)
|
||||
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.')
|
||||
print("Connected.")
|
||||
|
||||
connection.register_packet_listener(
|
||||
handle_join_game, clientbound.play.JoinGamePacket)
|
||||
handle_join_game, clientbound.play.JoinGamePacket
|
||||
)
|
||||
|
||||
def print_chat(chat_packet):
|
||||
print("Message (%s): %s" % (
|
||||
chat_packet.field_string('position'), chat_packet.json_data))
|
||||
print(
|
||||
"Message (%s): %s"
|
||||
% (chat_packet.field_string("position"), chat_packet.json_data)
|
||||
)
|
||||
|
||||
connection.register_packet_listener(
|
||||
print_chat, clientbound.play.ChatMessagePacket)
|
|
@ -32,7 +32,7 @@ class ConnectionContext(object):
|
|||
"""
|
||||
|
||||
def __init__(self, **kwds):
|
||||
self.protocol_version = kwds.get('protocol_version')
|
||||
self.protocol_version = kwds.get("protocol_version")
|
||||
|
||||
def protocol_earlier(self, other_pv):
|
||||
"""Returns True if the protocol version of this context was published
|
||||
|
@ -63,8 +63,13 @@ class ConnectionContext(object):
|
|||
|
||||
|
||||
class _ConnectionOptions(object):
|
||||
def __init__(self, address=None, port=None, compression_threshold=-1,
|
||||
compression_enabled=False):
|
||||
def __init__(
|
||||
self,
|
||||
address=None,
|
||||
port=None,
|
||||
compression_threshold=-1,
|
||||
compression_enabled=False,
|
||||
):
|
||||
self.address = address
|
||||
self.port = port
|
||||
self.compression_threshold = compression_threshold
|
||||
|
@ -94,41 +99,51 @@ class Connection(object):
|
|||
The connect method needs to be called in order to actually begin
|
||||
the connection
|
||||
|
||||
:param address: address of the server to connect to
|
||||
:param port(int): port of the server to connect to
|
||||
:param auth_token: :class:`minecraft.authentication.AuthenticationToken`
|
||||
object. If None, no authentication is attempted and
|
||||
the server is assumed to be running in offline mode.
|
||||
:param username: Username string; only applicable in offline mode.
|
||||
:param initial_version: A Minecraft version ID string or protocol
|
||||
version number to use if the server's protocol
|
||||
version cannot be determined. (Although it is
|
||||
now somewhat inaccurate, this name is retained
|
||||
for backward compatibility.)
|
||||
:param allowed_versions: A set of versions, each being a Minecraft
|
||||
version ID string or protocol version number,
|
||||
restricting the versions that the client may
|
||||
use in connecting to the server.
|
||||
:param handle_exception: The final exception handler. This is triggered
|
||||
when an exception occurs in the networking
|
||||
thread that is not caught normally. After
|
||||
any other user-registered exception handlers
|
||||
are run, the final exception (which may be the
|
||||
original exception or one raised by another
|
||||
handler) is passed, regardless of whether or
|
||||
not it was caught by another handler, to the
|
||||
final handler, which may be a function obeying
|
||||
the protocol of 'register_exception_handler';
|
||||
the value 'None', meaning that if the
|
||||
exception was otherwise uncaught, it is
|
||||
re-raised from the networking thread after
|
||||
closing the connection; or the value 'False',
|
||||
meaning that the exception is never re-raised.
|
||||
:param handle_exit: A function to be called when a connection to a
|
||||
server terminates, not caused by an exception,
|
||||
and not with the intention to automatically
|
||||
reconnect. Exceptions raised from this function
|
||||
will be handled by any matching exception handlers.
|
||||
Parameters
|
||||
----------
|
||||
address
|
||||
address of the server to connect to
|
||||
port : int
|
||||
port of the server to connect to
|
||||
auth_token : `minecraft.authentication.AuthenticationToken`
|
||||
If None, no authentication is attempted and
|
||||
the server is assumed to be running in offline mode.
|
||||
username : str
|
||||
Username string; only applicable in offline mode.
|
||||
initial_version
|
||||
A Minecraft version ID string or protocol
|
||||
version number to use if the server's protocol
|
||||
version cannot be determined. (Although it is
|
||||
now somewhat inaccurate, this name is retained
|
||||
for backward compatibility.)
|
||||
allowed_versions
|
||||
A set of versions, each being a Minecraft
|
||||
version ID string or protocol version number,
|
||||
restricting the versions that the client may
|
||||
use in connecting to the server.
|
||||
handle_exception
|
||||
The final exception handler. This is triggered
|
||||
when an exception occurs in the networking
|
||||
thread that is not caught normally. After
|
||||
any other user-registered exception handlers
|
||||
are run, the final exception (which may be the
|
||||
original exception or one raised by another
|
||||
handler) is passed, regardless of whether or
|
||||
not it was caught by another handler, to the
|
||||
final handler, which may be a function obeying
|
||||
the protocol of 'register_exception_handler';
|
||||
the value 'None', meaning that if the
|
||||
exception was otherwise uncaught, it is
|
||||
re-raised from the networking thread after
|
||||
closing the connection; or the value 'False',
|
||||
meaning that the exception is never re-raised.
|
||||
handle_exit
|
||||
A function to be called when a connection to a
|
||||
server terminates, not caused by an exception,
|
||||
and not with the intention to automatically
|
||||
reconnect. Exceptions raised from this function
|
||||
will be handled by any matching exception handlers.
|
||||
|
||||
""" # NOQA
|
||||
|
||||
# This lock is re-entrant because it may be acquired in a re-entrant
|
||||
|
@ -151,7 +166,7 @@ class Connection(object):
|
|||
else:
|
||||
proto_version = None
|
||||
if proto_version not in SUPPORTED_PROTOCOL_VERSIONS:
|
||||
raise ValueError('Unsupported version number: %r.' % version)
|
||||
raise ValueError("Unsupported version number: %r." % version)
|
||||
return proto_version
|
||||
|
||||
if allowed_versions is None:
|
||||
|
@ -187,10 +202,12 @@ class Connection(object):
|
|||
|
||||
def _start_network_thread(self):
|
||||
with self._write_lock:
|
||||
if self.networking_thread is not None and \
|
||||
not self.networking_thread.interrupt or \
|
||||
self.new_networking_thread is not None:
|
||||
raise InvalidState('A networking thread is already running.')
|
||||
if (
|
||||
self.networking_thread is not None
|
||||
and not self.networking_thread.interrupt
|
||||
or self.new_networking_thread is not None
|
||||
):
|
||||
raise InvalidState("A networking thread is already running.")
|
||||
elif self.networking_thread is None:
|
||||
self.networking_thread = NetworkingThread(self)
|
||||
self.networking_thread.start()
|
||||
|
@ -198,8 +215,9 @@ class Connection(object):
|
|||
# This thread will wait until the existing thread exits, and
|
||||
# then set 'networking_thread' to itself and
|
||||
# 'new_networking_thread' to None.
|
||||
self.new_networking_thread \
|
||||
= NetworkingThread(self, previous=self.networking_thread)
|
||||
self.new_networking_thread = NetworkingThread(
|
||||
self, previous=self.networking_thread
|
||||
)
|
||||
self.new_networking_thread.start()
|
||||
|
||||
def write_packet(self, packet, force=False):
|
||||
|
@ -211,8 +229,13 @@ class Connection(object):
|
|||
If force is false then the packet will be added to the end of the
|
||||
packet writing queue to be sent 'as soon as possible'
|
||||
|
||||
:param packet: The :class:`network.packets.Packet` to write
|
||||
:param force(bool): Specifies if the packet write should be immediate
|
||||
Parameters
|
||||
----------
|
||||
packet : network.packets.Packet
|
||||
The `network.packets.Packet` to write
|
||||
force : bool
|
||||
Specifies if the packet write should be immediate
|
||||
|
||||
"""
|
||||
packet.context = self.context
|
||||
if force:
|
||||
|
@ -222,13 +245,19 @@ class Connection(object):
|
|||
self._outgoing_packet_queue.append(packet)
|
||||
|
||||
def listener(self, *packet_types, **kwds):
|
||||
"""
|
||||
Shorthand decorator to register a function as a packet listener.
|
||||
"""Shorthand decorator to register a function as a packet listener.
|
||||
|
||||
Wraps :meth:`minecraft.networking.connection.register_packet_listener`
|
||||
:param packet_types: Packet types to listen for.
|
||||
:param kwds: Keyword arguments for `register_packet_listener`
|
||||
|
||||
Parameters
|
||||
----------
|
||||
packet_types
|
||||
Packet types to listen for.
|
||||
kwds
|
||||
Keyword arguments for `register_packet_listener`
|
||||
|
||||
"""
|
||||
|
||||
def listener_decorator(handler_func):
|
||||
self.register_packet_listener(handler_func, *packet_types, **kwds)
|
||||
return handler_func
|
||||
|
@ -239,6 +268,7 @@ class Connection(object):
|
|||
"""
|
||||
Shorthand decorator to register a function as an exception handler.
|
||||
"""
|
||||
|
||||
def exception_handler_decorator(handler_func):
|
||||
self.register_exception_handler(handler_func, *exc_types, **kwds)
|
||||
return handler_func
|
||||
|
@ -246,8 +276,7 @@ class Connection(object):
|
|||
return exception_handler_decorator
|
||||
|
||||
def register_packet_listener(self, method, *packet_types, **kwds):
|
||||
"""
|
||||
Registers a listener method which will be notified when a packet of
|
||||
"""Registers a listener method which will be notified when a packet of
|
||||
a selected type is received.
|
||||
|
||||
If :class:`minecraft.networking.connection.IgnorePacket` is raised from
|
||||
|
@ -258,23 +287,38 @@ class Connection(object):
|
|||
'outgoing=True', this will prevent the packet from being written to the
|
||||
network.
|
||||
|
||||
:param method: The method which will be called back with the packet
|
||||
:param packet_types: The packets to listen for
|
||||
:param outgoing: If 'True', this listener will be called on outgoing
|
||||
packets just after they are sent to the server, rather
|
||||
than on incoming packets.
|
||||
:param early: If 'True', this listener will be called before any
|
||||
built-in default action is carried out, and before any
|
||||
listeners with 'early=False' are called. If
|
||||
'outgoing=True', the listener will be called before the
|
||||
packet is written to the network, rather than afterwards.
|
||||
Parameters
|
||||
----------
|
||||
method
|
||||
The method which will be called back with the packet
|
||||
packet_types
|
||||
The packets to listen for
|
||||
outgoing
|
||||
If 'True', this listener will be called on outgoing
|
||||
packets just after they are sent to the server, rather
|
||||
than on incoming packets.
|
||||
early
|
||||
If 'True', this listener will be called before any
|
||||
built-in default action is carried out, and before any
|
||||
listeners with 'early=False' are called. If
|
||||
'outgoing=True', the listener will be called before the
|
||||
packet is written to the network, rather than afterwards.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
"""
|
||||
outgoing = kwds.pop('outgoing', False)
|
||||
early = kwds.pop('early', False)
|
||||
target = self.packet_listeners if not early and not outgoing \
|
||||
else self.early_packet_listeners if early and not outgoing \
|
||||
else self.outgoing_packet_listeners if not early \
|
||||
outgoing = kwds.pop("outgoing", False)
|
||||
early = kwds.pop("early", False)
|
||||
target = (
|
||||
self.packet_listeners
|
||||
if not early and not outgoing
|
||||
else self.early_packet_listeners
|
||||
if early and not outgoing
|
||||
else self.outgoing_packet_listeners
|
||||
if not early
|
||||
else self.early_outgoing_packet_listeners
|
||||
)
|
||||
target.append(packets.PacketListener(method, *packet_types, **kwds))
|
||||
|
||||
def register_exception_handler(self, handler_func, *exc_types, **kwds):
|
||||
|
@ -295,21 +339,24 @@ class Connection(object):
|
|||
be set as the 'exception' and 'exc_info' attributes of the
|
||||
'Connection'.
|
||||
|
||||
:param handler_func: A function taking two arguments: the exception
|
||||
object 'e' as in 'except Exception as e:', and the corresponding
|
||||
3-tuple given by 'sys.exc_info()'. The return value of the function is
|
||||
ignored, but any exception raised in it replaces the original
|
||||
exception, and may be passed to later exception handlers.
|
||||
|
||||
:param exc_types: The types of exceptions that this handler shall
|
||||
catch, as in 'except (exc_type_1, exc_type_2, ...) as e:'. If this is
|
||||
empty, the handler will catch all exceptions.
|
||||
|
||||
:param early: If 'True', the exception handler is registered before
|
||||
any existing exception handlers in the handling order.
|
||||
Parameters
|
||||
----------
|
||||
handler_func
|
||||
A function taking two arguments: the exception
|
||||
object 'e' as in 'except Exception as e:', and the corresponding
|
||||
3-tuple given by 'sys.exc_info()'. The return value of the function is
|
||||
ignored, but any exception raised in it replaces the original
|
||||
exception, and may be passed to later exception handlers.
|
||||
exc_types
|
||||
The types of exceptions that this handler shall
|
||||
catch, as in 'except (exc_type_1, exc_type_2, ...) as e:'. If this is
|
||||
empty, the handler will catch all exceptions.
|
||||
early
|
||||
If 'True', the exception handler is registered before
|
||||
any existing exception handlers in the handling order.
|
||||
"""
|
||||
early = kwds.pop('early', False)
|
||||
assert not kwds, 'Unexpected keyword arguments: %r' % (kwds,)
|
||||
early = kwds.pop("early", False)
|
||||
assert not kwds, "Unexpected keyword arguments: %r" % (kwds,)
|
||||
if early:
|
||||
self._exception_handlers.insert(0, (handler_func, exc_types))
|
||||
else:
|
||||
|
@ -350,14 +397,19 @@ class Connection(object):
|
|||
def status(self, handle_status=None, handle_ping=False):
|
||||
"""Issue a status request to the server and then disconnect.
|
||||
|
||||
:param handle_status: a function to be called with the status
|
||||
dictionary None for the default behaviour of
|
||||
printing the dictionary to standard output, or
|
||||
False to ignore the result.
|
||||
:param handle_ping: a function to be called with the measured latency
|
||||
in milliseconds, None for the default handler,
|
||||
which prints the latency to standard outout, or
|
||||
False, to prevent measurement of the latency.
|
||||
Parameters
|
||||
----------
|
||||
handle_status
|
||||
A function to be called with the status
|
||||
dictionary None for the default behaviour of
|
||||
printing the dictionary to standard output, or
|
||||
False to ignore the result.
|
||||
handle_ping
|
||||
A function to be called with the measured latency
|
||||
in milliseconds, None for the default handler,
|
||||
which prints the latency to standard outout, or
|
||||
False, to prevent measurement of the latency.
|
||||
|
||||
"""
|
||||
with self._write_lock: # pylint: disable=not-context-manager
|
||||
self._check_connection()
|
||||
|
@ -422,10 +474,12 @@ class Connection(object):
|
|||
self._start_network_thread()
|
||||
|
||||
def _check_connection(self):
|
||||
if self.networking_thread is not None and \
|
||||
not self.networking_thread.interrupt or \
|
||||
self.new_networking_thread is not None:
|
||||
raise InvalidState('There is an existing connection.')
|
||||
if (
|
||||
self.networking_thread is not None
|
||||
and not self.networking_thread.interrupt
|
||||
or self.new_networking_thread is not None
|
||||
):
|
||||
raise InvalidState("There is an existing connection.")
|
||||
|
||||
def _connect(self):
|
||||
# Connect a socket to the server and create a file object from the
|
||||
|
@ -436,8 +490,9 @@ class Connection(object):
|
|||
# the server.
|
||||
self._outgoing_packet_queue = deque()
|
||||
|
||||
info = socket.getaddrinfo(self.options.address, self.options.port,
|
||||
0, socket.SOCK_STREAM)
|
||||
info = socket.getaddrinfo(
|
||||
self.options.address, self.options.port, 0, socket.SOCK_STREAM
|
||||
)
|
||||
|
||||
# Prefer to use IPv4 (for backward compatibility with previous
|
||||
# versions that always resolved hostnames to IPv4 addresses),
|
||||
|
@ -456,7 +511,14 @@ class Connection(object):
|
|||
|
||||
def disconnect(self, immediate=False):
|
||||
"""Terminate the existing server connection, if there is one.
|
||||
If 'immediate' is True, do not attempt to write any packets.
|
||||
|
||||
If 'immediate' is True, do not attempt to write any packets.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
immediate : bool, optional
|
||||
Whether or not to terminate the existing connection immediately
|
||||
|
||||
"""
|
||||
with self._write_lock: # pylint: disable=not-context-manager
|
||||
self.connected = False
|
||||
|
@ -545,14 +607,20 @@ class Connection(object):
|
|||
server_protocol = KNOWN_MINECRAFT_VERSIONS.get(server_version)
|
||||
|
||||
if server_protocol is None:
|
||||
vs = 'version' if server_version is None else \
|
||||
('version of %s' % server_version)
|
||||
vs = (
|
||||
"version"
|
||||
if server_version is None
|
||||
else ("version of %s" % server_version)
|
||||
)
|
||||
else:
|
||||
vs = ('protocol version of %d' % server_protocol) + \
|
||||
('' if server_version is None else ' (%s)' % server_version)
|
||||
ss = 'supported, but not allowed for this connection' \
|
||||
if server_protocol in SUPPORTED_PROTOCOL_VERSIONS \
|
||||
else 'not supported'
|
||||
vs = ("protocol version of %d" % server_protocol) + (
|
||||
"" if server_version is None else " (%s)" % server_version
|
||||
)
|
||||
ss = (
|
||||
"supported, but not allowed for this connection"
|
||||
if server_protocol in SUPPORTED_PROTOCOL_VERSIONS
|
||||
else "not supported"
|
||||
)
|
||||
err = VersionMismatch("Server's %s is %s." % (vs, ss))
|
||||
err.server_protocol = server_protocol
|
||||
err.server_version = server_version
|
||||
|
@ -625,7 +693,8 @@ class NetworkingThread(threading.Thread):
|
|||
# Read and react to as many as 50 packets.
|
||||
while num_packets < 50 and not self.interrupt:
|
||||
packet = self.connection.reactor.read_packet(
|
||||
self.connection.file_object, timeout=read_timeout)
|
||||
self.connection.file_object, timeout=read_timeout
|
||||
)
|
||||
if not packet:
|
||||
break
|
||||
num_packets += 1
|
||||
|
@ -647,6 +716,7 @@ class PacketReactor(object):
|
|||
"""
|
||||
Reads and reacts to packets
|
||||
"""
|
||||
|
||||
state_name = None
|
||||
|
||||
# Handshaking is considered the "default" state
|
||||
|
@ -657,7 +727,8 @@ class PacketReactor(object):
|
|||
context = self.connection.context
|
||||
self.clientbound_packets = {
|
||||
packet.get_id(context): packet
|
||||
for packet in self.__class__.get_clientbound_packets(context)}
|
||||
for packet in self.__class__.get_clientbound_packets(context)
|
||||
}
|
||||
|
||||
def read_packet(self, stream, timeout=0):
|
||||
# Block for up to `timeout' seconds waiting for `stream' to become
|
||||
|
@ -671,8 +742,8 @@ class PacketReactor(object):
|
|||
packet_data.send(stream.read(length))
|
||||
# Ensure we read all the packet
|
||||
while len(packet_data.get_writable()) < length:
|
||||
packet_data.send(
|
||||
stream.read(length - len(packet_data.get_writable())))
|
||||
packet_data.send(stream.read(
|
||||
length - len(packet_data.get_writable())))
|
||||
packet_data.reset_cursor()
|
||||
|
||||
if self.connection.options.compression_enabled:
|
||||
|
@ -728,12 +799,14 @@ class LoginReactor(PacketReactor):
|
|||
|
||||
secret = encryption.generate_shared_secret()
|
||||
token, encrypted_secret = encryption.encrypt_token_and_secret(
|
||||
packet.public_key, packet.verify_token, secret)
|
||||
packet.public_key, packet.verify_token, secret
|
||||
)
|
||||
|
||||
# A server id of '-' means the server is in offline mode
|
||||
if packet.server_id != '-':
|
||||
if packet.server_id != "-":
|
||||
server_id = encryption.generate_verification_hash(
|
||||
packet.server_id, secret, packet.public_key)
|
||||
packet.server_id, secret, packet.public_key
|
||||
)
|
||||
if self.connection.auth_token is not None:
|
||||
self.connection.auth_token.join(server_id)
|
||||
|
||||
|
@ -750,25 +823,27 @@ class LoginReactor(PacketReactor):
|
|||
encryptor = cipher.encryptor()
|
||||
decryptor = cipher.decryptor()
|
||||
self.connection.socket = encryption.EncryptedSocketWrapper(
|
||||
self.connection.socket, encryptor, decryptor)
|
||||
self.connection.file_object = \
|
||||
encryption.EncryptedFileObjectWrapper(
|
||||
self.connection.file_object, decryptor)
|
||||
self.connection.socket, encryptor, decryptor
|
||||
)
|
||||
self.connection.file_object = encryption.EncryptedFileObjectWrapper(
|
||||
self.connection.file_object, decryptor)
|
||||
|
||||
elif packet.packet_name == "disconnect":
|
||||
# Receiving a disconnect packet in the login state indicates an
|
||||
# abnormal condition. Raise an exception explaining the situation.
|
||||
try:
|
||||
msg = json.loads(packet.json_data)['text']
|
||||
msg = json.loads(packet.json_data)["text"]
|
||||
except (ValueError, TypeError, KeyError):
|
||||
msg = packet.json_data
|
||||
match = re.match(r"Outdated (client! Please use|server!"
|
||||
r" I'm still on) (?P<ver>\S+)$", msg)
|
||||
match = re.match(
|
||||
r"Outdated (client! Please use|server!"
|
||||
r" I'm still on) (?P<ver>\S+)$", msg, )
|
||||
if match:
|
||||
ver = match.group('ver')
|
||||
ver = match.group("ver")
|
||||
self.connection._version_mismatch(server_version=ver)
|
||||
raise LoginDisconnect('The server rejected our login attempt '
|
||||
'with: "%s".' % msg)
|
||||
raise LoginDisconnect(
|
||||
"The server rejected our login attempt " 'with: "%s".' % msg
|
||||
)
|
||||
|
||||
elif packet.packet_name == "login success":
|
||||
self.connection.reactor = PlayingReactor(self.connection)
|
||||
|
@ -780,7 +855,9 @@ class LoginReactor(PacketReactor):
|
|||
elif packet.packet_name == "login plugin request":
|
||||
self.connection.write_packet(
|
||||
serverbound.login.PluginResponsePacket(
|
||||
message_id=packet.message_id, successful=False))
|
||||
message_id=packet.message_id, successful=False
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class PlayingReactor(PacketReactor):
|
||||
|
@ -846,7 +923,7 @@ class StatusReactor(PacketReactor):
|
|||
print(status_dict)
|
||||
|
||||
def handle_ping(self, latency_ms):
|
||||
print('Ping: %d ms' % latency_ms)
|
||||
print("Ping: %d ms" % latency_ms)
|
||||
|
||||
|
||||
class PlayingStatusReactor(StatusReactor):
|
||||
|
@ -858,15 +935,15 @@ class PlayingStatusReactor(StatusReactor):
|
|||
# This can occur when we connect to a Mojang server while it is
|
||||
# still initialising, so it must not cause the client to connect
|
||||
# with the default version.
|
||||
raise IOError('Invalid server status.')
|
||||
elif 'version' not in status or 'protocol' not in status['version']:
|
||||
raise IOError("Invalid server status.")
|
||||
elif "version" not in status or "protocol" not in status["version"]:
|
||||
return self.handle_failure()
|
||||
|
||||
proto = status['version']['protocol']
|
||||
proto = status["version"]["protocol"]
|
||||
if proto not in self.connection.allowed_proto_versions:
|
||||
self.connection._version_mismatch(
|
||||
server_protocol=proto,
|
||||
server_version=status['version'].get('name'))
|
||||
server_version=status["version"].get("name"))
|
||||
|
||||
self.handle_proto_version(proto)
|
||||
|
||||
|
|
Loading…
Reference in New Issue