Full Code of py-mine/mcstatus for AI

master 87ccc41cc588 cached
101 files
407.1 KB
108.9k tokens
734 symbols
1 requests
Download .txt
Showing preview only (435K chars total). Download the full file or copy to clipboard to get everything.
Repository: py-mine/mcstatus
Branch: master
Commit: 87ccc41cc588
Files: 101
Total size: 407.1 KB

Directory structure:
gitextract_h1f5vhjq/

├── .coveragerc
├── .github/
│   ├── CODEOWNERS
│   ├── renovate.json5
│   └── workflows/
│       ├── main.yml
│       ├── publish.yml
│       ├── status_embed.yml
│       ├── unit-tests.yml
│       └── validation.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .readthedocs.yaml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── docs/
│   ├── Makefile
│   ├── api/
│   │   ├── basic.rst
│   │   ├── internal.rst
│   │   └── motd_parsing.rst
│   ├── conf.py
│   ├── examples/
│   │   ├── code/
│   │   │   ├── ping_as_java_and_bedrock_in_one_time.py
│   │   │   ├── ping_many_servers_at_once.py
│   │   │   └── player_list_from_query_with_fallback_on_status.py
│   │   ├── examples.rst
│   │   ├── ping_as_java_and_bedrock_in_one_time.rst
│   │   ├── ping_many_servers_at_once.rst
│   │   └── player_list_from_query_with_fallback_on_status.rst
│   ├── index.rst
│   ├── pages/
│   │   ├── contributing.rst
│   │   ├── faq.rst
│   │   └── versioning.rst
│   └── pyproject.toml
├── mcstatus/
│   ├── __init__.py
│   ├── __main__.py
│   ├── _compat/
│   │   ├── README.md
│   │   ├── __init__.py
│   │   ├── forge_data.py
│   │   ├── motd_transformers.py
│   │   └── status_response.py
│   ├── _net/
│   │   ├── __init__.py
│   │   ├── address.py
│   │   └── dns.py
│   ├── _protocol/
│   │   ├── __init__.py
│   │   ├── bedrock_client.py
│   │   ├── connection.py
│   │   ├── java_client.py
│   │   ├── legacy_client.py
│   │   └── query_client.py
│   ├── _utils/
│   │   ├── __init__.py
│   │   ├── deprecation.py
│   │   ├── general.py
│   │   └── retry.py
│   ├── motd/
│   │   ├── __init__.py
│   │   ├── _simplifies.py
│   │   ├── _transformers.py
│   │   └── components.py
│   ├── py.typed
│   ├── responses/
│   │   ├── __init__.py
│   │   ├── _raw.py
│   │   ├── base.py
│   │   ├── bedrock.py
│   │   ├── forge.py
│   │   ├── java.py
│   │   ├── legacy.py
│   │   └── query.py
│   └── server.py
├── pyproject.toml
└── tests/
    ├── __init__.py
    ├── helpers.py
    ├── motd/
    │   ├── __init__.py
    │   ├── conftest.py
    │   ├── test_components.py
    │   ├── test_motd.py
    │   ├── test_simplifies.py
    │   └── test_transformers.py
    ├── net/
    │   ├── __init__.py
    │   └── test_address.py
    ├── protocol/
    │   ├── __init__.py
    │   ├── test_async_support.py
    │   ├── test_bedrock_client.py
    │   ├── test_connection.py
    │   ├── test_java_client.py
    │   ├── test_java_client_async.py
    │   ├── test_legacy_client.py
    │   ├── test_query_client.py
    │   ├── test_query_client_async.py
    │   └── test_timeout.py
    ├── responses/
    │   ├── __init__.py
    │   ├── conftest.py
    │   ├── test_base.py
    │   ├── test_bedrock.py
    │   ├── test_forge_data.py
    │   ├── test_java.py
    │   ├── test_legacy.py
    │   └── test_query.py
    ├── test_cli.py
    ├── test_compat.py
    ├── test_server.py
    └── utils/
        ├── __init__.py
        ├── test_deprecation.py
        ├── test_general.py
        └── test_retry.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .coveragerc
================================================
[report]
exclude_lines =
    pragma: no cover
    ((t|typing)\.)?TYPE_CHECKING
    ^\s\.\.\.\s$
    def __repr__
    class .*\bProtocol\):
    @(abc\.)?abstractmethod
    raise NotImplementedError
    if __name__ == "__main__":


================================================
FILE: .github/CODEOWNERS
================================================
# Devops & CI workflows
.github/dependabot.yml      @ItsDrike
.github/workflows/**        @ItsDrike
.pre-commit-config.yaml     @ItsDrike


================================================
FILE: .github/renovate.json5
================================================
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    ":automergeMinor",
    ":automergePr",
    ":combinePatchMinorReleases",
    ":configMigration",
    ":dependencyDashboard",
    ":ignoreModulesAndTests",
    ":prHourlyLimitNone",
    ":semanticCommitsDisabled",
    "group:allNonMajor",
    "mergeConfidence:all-badges",
    "replacements:all",
    "schedule:daily",
    "workarounds:all",
  ],

  "labels": ["a: dependencies"],
  "packageRules": [
    {
      "groupName": "GitHub Actions",
      "matchManagers": ["github-actions"],
      "addLabels": ["a: devops"],
    },
    {
      "groupName": "Python Dependencies",
      "matchCategories": ["python"],
    },
  ],
  "lockFileMaintenance": {
    "enabled": true,
  },
}


================================================
FILE: .github/workflows/main.yml
================================================
name: CI

on:
  push:
    branches:
      - master
  pull_request:
  workflow_dispatch:

# Cancel already running workflows if new ones are scheduled
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  validation:
    uses: ./.github/workflows/validation.yml

  unit-tests:
    uses: ./.github/workflows/unit-tests.yml

  # Produce a pull request payload artifact with various data about the
  # pull-request event (such as the PR number, title, author, ...).
  # This data is then be picked up by status-embed.yml action.
  pr_artifact:
    name: Produce Pull Request payload artifact
    runs-on: ubuntu-latest

    steps:
      - name: Prepare Pull Request Payload artifact
        id: prepare-artifact
        if: always() && github.event_name == 'pull_request'
        continue-on-error: true
        run: cat $GITHUB_EVENT_PATH | jq '.pull_request' > pull_request_payload.json

      - name: Upload a Build Artifact
        if: always() && steps.prepare-artifact.outcome == 'success'
        continue-on-error: true
        uses: actions/upload-artifact@v7
        with:
          name: pull-request-payload
          path: pull_request_payload.json


================================================
FILE: .github/workflows/publish.yml
================================================
---
name: Publish to PyPI

on:
  push:
    tags:
      # This pattern is not a typical regular expression, see:
      # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet
      - "v*"
    branches:
      # Also run on every commit to master. This allows us to test the build & release pipeline and eventually leads to a
      # Test PyPI release. Unlike with a tag push, this will not release a full PyPI release, nor create a GitHub release.
      - master

permissions:
  contents: read

env:
  PYTHON_VERSION: "3.14"

jobs:
  build:
    name: "Build the project"
    runs-on: ubuntu-latest

    outputs:
      version: ${{ steps.check-version.outputs.version }}
      tagged_release: ${{ steps.check-version.outputs.tagged_release }}

    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          # Do a full clone for uv-dynamic-versioning to pick up the git version
          fetch-depth: 0

      - name: Setup uv
        uses: astral-sh/setup-uv@v7
        with:
          version: "latest"
          python-version: ${{ env.PYTHON_VERSION }}
          activate-environment: true
          enable-cache: true
          cache-suffix: "build"

      - name: Install dependencies
        run: |
          uv sync --no-default-groups --group release

      - name: Check version status
        id: check-version
        run: |
          version="$(hatchling version)"

          echo "Project version: $version"
          echo "version=$version" >> "$GITHUB_OUTPUT"

          # Determine whether we're doing a tagged release e.g. this workflow
          # was triggered by a git tag ref that matches the project's current
          # version, so a full PyPI release should be made, alongside all of
          # the other release steps. If this isn't the case, only a Test PyPI
          # release will be performed.
          if [[ "${GITHUB_REF}" == "refs/tags/v${version}" ]]; then
            echo "This is a new tagged release"
            echo "tagged_release=true" >> "$GITHUB_OUTPUT"
          else
            echo "This is an untagged dev release"
            echo "tagged_release=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Build project for distribution
        run: uv build --all-packages

      - name: Upload build files
        uses: actions/upload-artifact@v7
        with:
          name: "dist"
          path: "dist/"
          if-no-files-found: error
          retention-days: 5

  publish-test-pypi:
    name: "Publish to Test PyPI"
    # No if condition here, publish both tagged and untagged releases to Test PyPI.
    needs: build
    runs-on: ubuntu-latest
    environment: test-pypi # no approval
    permissions:
      # Used to authenticate to Test PyPI via OIDC.
      id-token: write

    steps:
      - name: Download the distribution files from build artifact
        uses: actions/download-artifact@v8
        with:
          name: "dist"
          path: "dist/"

      - name: Upload to Test PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          # the "legacy" in the URL doesn't mean it's deprecated
          repository-url: https://test.pypi.org/legacy/
          # Enable verbose mode for easy debugging
          verbose: true

  publish-pypi:
    name: "Publish to PyPI"
    if: needs.build.outputs.tagged_release == 'true' # only publish to PyPI on tagged releases
    needs: build
    runs-on: ubuntu-latest
    environment: release # requires approval
    permissions:
      # Used to authenticate to PyPI via OIDC.
      id-token: write

    steps:
      - name: Download the distribution files from build artifact
        uses: actions/download-artifact@v8
        with:
          name: "dist"
          path: "dist/"

      # This uses PyPI's trusted publishing, so no token is required
      - name: Release to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1


================================================
FILE: .github/workflows/status_embed.yml
================================================
name: Status Embed

on:
  workflow_run:
    workflows:
      - CI
    types:
      - completed

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  status_embed:
    name: Send Status Embed to Discord
    runs-on: ubuntu-latest
    steps:
      # A workflow_run event does not contain all the information
      # we need for a PR embed. That's why we upload an artifact
      # with that information in the Lint workflow.
      - name: Get Pull Request Information
        id: pr_info
        if: github.event.workflow_run.event == 'pull_request'
        run: |
          curl -s -H "Authorization: token $GITHUB_TOKEN" ${{ github.event.workflow_run.artifacts_url }} > artifacts.json
          DOWNLOAD_URL=$(cat artifacts.json | jq -r '.artifacts[] | select(.name == "pull-request-payload") | .archive_download_url')
          [ -z "$DOWNLOAD_URL" ] && exit 1
          curl -sSL -H "Authorization: token $GITHUB_TOKEN" -o pull_request_payload.zip $DOWNLOAD_URL || exit 2
          unzip -p pull_request_payload.zip > pull_request_payload.json
          [ -s pull_request_payload.json ] || exit 3
          echo "pr_author_login=$(jq -r '.user.login // empty' pull_request_payload.json)" >> $GITHUB_OUTPUT
          echo "pr_number=$(jq -r '.number // empty' pull_request_payload.json)" >> $GITHUB_OUTPUT
          echo "pr_title=$(jq -r '.title // empty' pull_request_payload.json)" >> $GITHUB_OUTPUT
          echo "pr_source=$(jq -r '.head.label // empty' pull_request_payload.json)" >> $GITHUB_OUTPUT
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      # Send an informational status embed to Discord instead of the
      # standard embeds that Discord sends. This embed will contain
      # more information and we can fine tune when we actually want
      # to send an embed.
      - name: GitHub Actions Status Embed for Discord
        uses: SebastiaanZ/github-status-embed-for-discord@v0.3.0
        with:
          # Our GitHub Actions webhook
          webhook_id: "942940470059892796"
          webhook_token: ${{ secrets.webhook_token }}

          # We need to provide the information of the workflow that
          # triggered this workflow instead of this workflow.
          workflow_name: ${{ github.event.workflow_run.name }}
          run_id: ${{ github.event.workflow_run.id }}
          run_number: ${{ github.event.workflow_run.run_number }}
          status: ${{ github.event.workflow_run.conclusion }}
          sha: ${{ github.event.workflow_run.head_sha }}

          # Now we can use the information extracted in the previous step:
          pr_author_login: ${{ steps.pr_info.outputs.pr_author_login }}
          pr_number: ${{ steps.pr_info.outputs.pr_number }}
          pr_title: ${{ steps.pr_info.outputs.pr_title }}
          pr_source: ${{ steps.pr_info.outputs.pr_source }}


================================================
FILE: .github/workflows/unit-tests.yml
================================================
name: Unit-Tests

on: workflow_call

jobs:
  unit-tests:
    runs-on: ${{ matrix.platform }}

    strategy:
      fail-fast: false # Allows for matrix sub-jobs to fail without cancelling the rest
      matrix:
        platform: [ubuntu-latest, macos-latest, windows-latest]
        python-version: ["3.10", "3.14"]

    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Setup uv
        uses: astral-sh/setup-uv@v7
        with:
          version: "latest"
          python-version: ${{ matrix.python-version }}
          enable-cache: true
          cache-suffix: "test-ci"
          activate-environment: true

      - name: Install dependencies
        run: |
          uv sync --no-default-groups --group test

      - name: Run pytest
        shell: bash
        run: pytest -vv

  # This job is used purely to provide a workflow status, which we can mark as a
  # required action in branch protection rules. This is a better option than marking
  # the tox-test jobs manually, since their names change as the supported python
  # versions change. This job provides an easy single action that can be marked required.
  tests-done:
    needs: [unit-tests]
    if: always() && !cancelled()
    runs-on: ubuntu-latest

    steps:
      - name: Set status based on required jobs
        env:
          RESULTS: ${{ join(needs.*.result, ' ') }}
        run: |
          for result in $RESULTS; do
            if [ "$result" != "success" ]; then
              exit 1
            fi
          done


================================================
FILE: .github/workflows/validation.yml
================================================
name: Validation

on: workflow_call

env:
  PYTHON_VERSION: "3.14"
  PRE_COMMIT_HOME: "/home/runner/.cache/pre-commit"

jobs:
  lint:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Setup uv
        uses: astral-sh/setup-uv@v7
        with:
          version: "latest"
          python-version: ${{ env.PYTHON_VERSION }}
          enable-cache: true
          cache-suffix: "validation-ci"
          activate-environment: true

      - name: Install dependencies
        run: |
          # We need the test & docs groups to allow pyright to type-check the code in tests/ & docs/
          uv sync --no-default-groups --group lint --group test --group docs

      - name: Get precommit version
        id: precommit_version
        run: |
          PACKAGE_VERSION=$(pip show pre-commit | grep -i "version:" | awk '{print $2}')
          echo "version=$PACKAGE_VERSION" >> $GITHUB_ENV

      - name: Pre-commit Environment Caching
        uses: actions/cache@v5
        with:
          path: ${{ env.PRE_COMMIT_HOME }}
          key:
            "precommit-${{ runner.os }}-${{ env.PYTHON_VERSION }}-${{ steps.precommit_version.outputs.version }}-\
            ${{ hashFiles('./.pre-commit-config.yaml') }}"
          # Restore keys allows us to perform a cache restore even if the full cache key wasn't matched.
          # That way we still end up saving new cache, but we can still make use of the cache from previous
          # version.
          restore-keys: "precommit-${{ runner.os }}-${{ env.PYTHON_VERSION }}-"

      - name: Run pre-commit hooks
        run: SKIP=black,isort,ruff,pyright,uv-lockfile pre-commit run --all-files

      - name: Run ruff linter
        run: ruff check --output-format=github --show-fixes --exit-non-zero-on-fix .

      - name: Run ruff formatter
        run: ruff format --diff .

      - name: Run pyright type checker
        run: pyright .

      - name: Check UV Lockfile
        run: uv lock --check


================================================
FILE: .gitignore
================================================
.python-version

# Created by http://www.gitignore.io

### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]

# C extensions
*.so

# Distribution / packaging
.Python
env/
venv/
build/
develop-eggs/
dist/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Pyenv local version specification
.python-version

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.coverage*
!.coveragerc
.cache
nosetests.xml
coverage.xml

# Translations
*.mo
*.pot

# Django stuff:
*.log

# Sphinx documentation
docs/_build/

# PyBuilder
target/


### PyCharm ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm

## Directory-based project format
.idea/
/*.iml
# if you remove the above rule, at least ignore user-specific stuff:
# .idea/workspace.xml
# .idea/tasks.xml
# .idea/dictionaries
# and these sensitive or high-churn files:
# .idea/dataSources.ids
# .idea/dataSources.xml
# .idea/sqlDataSources.xml
# .idea/dynamic.xml
# and, if using gradle::
# .idea/gradle.xml
# .idea/libraries

## File-based project format
*.ipr
*.iws

## Additional for IntelliJ
out/

# generated by mpeltonen/sbt-idea plugin
.idea_modules/

# generated by JIRA plugin
atlassian-ide-plugin.xml

# generated by Crashlytics plugin (for Android Studio and Intellij)
com_crashlytics_export_strings.xml


### SublimeText ###
# workspace files are user-specific
*.sublime-workspace

# project files should be checked into the repository, unless a significant
# proportion of contributors will probably not be using SublimeText
# *.sublime-project

#sftp configuration file
sftp-config.json

### Visual Studio Code ###
.vscode


================================================
FILE: .pre-commit-config.yaml
================================================
ci:
  autofix_commit_msg: "[pre-commit.ci] auto fixes from pre-commit.com hooks"
  autofix_prs: true
  autoupdate_commit_msg: "[pre-commit.ci] pre-commit autoupdate"
  autoupdate_schedule: weekly
  submodules: false

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: check-merge-conflict
      - id: check-toml # For pyproject.toml
      - id: check-yaml # For workflows
      - id: end-of-file-fixer
      - id: trailing-whitespace
        args: [--markdown-linebreak-ext=md]
      - id: end-of-file-fixer
      - id: mixed-line-ending
      - id: sort-simple-yaml

  - repo: local
    hooks:
      - id: ruff
        name: Ruff Linter
        description: Run ruff checks on the code
        entry: ruff check --force-exclude
        language: system
        types: [python]
        require_serial: true
        args: [--fix, --exit-non-zero-on-fix]

  - repo: local
    hooks:
      - id: ruff-ruff
        name: Ruff Formatter
        description: Ruf ruff auto-formatter
        entry: ruff format
        language: system
        types: [python]
        require_serial: true

  - repo: local
    hooks:
      - id: pyright
        name: Pyright
        description: Run pyright type checker
        entry: pyright
        language: system
        types: [python]
        pass_filenames: false # pyright runs for the entire project, it can't run for single files

  - repo: local
    hooks:
      - id: uv-lockfile
        name: UV Lockfile
        description: Check if the UV lockfile is up to date with pyproject.toml
        entry: uv lock --check
        language: system
        files: '^pyproject\.toml$|^uv\.lock$'
        pass_filenames: false


================================================
FILE: .readthedocs.yaml
================================================
version: 2

build:
  os: ubuntu-22.04
  tools:
    python: "3.13"

  jobs:
    post_create_environment:
      - python -m pip install uv
    post_install:
      - UV_PROJECT_ENVIRONMENT=$READTHEDOCS_VIRTUALENV_PATH uv sync --all-extras --group docs --link-mode=copy

sphinx:
  builder: dirhtml
  configuration: "docs/conf.py"
  fail_on_warning: true


================================================
FILE: CONTRIBUTING.md
================================================
## Contributing

See [the documentation page](https://mcstatus.readthedocs.io/en/stable/pages/contributing/).
The documentation itself is built from the docs/ directory.


================================================
FILE: LICENSE
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
# <img src="https://i.imgur.com/nPCcxts.png" height="25" style="height: 25px"> MCStatus

[![discord chat](https://img.shields.io/discord/936788458939224094.svg?logo=Discord)](https://discord.gg/C2wX7zduxC)
![supported python versions](https://img.shields.io/pypi/pyversions/mcstatus.svg)
[![current PyPI version](https://img.shields.io/pypi/v/mcstatus.svg)](https://pypi.org/project/mcstatus/)
[![Docs](https://img.shields.io/readthedocs/mcstatus?label=Docs)](https://mcstatus.readthedocs.io/)
[![CI status](https://github.com/py-mine/mcstatus/actions/workflows/main.yml/badge.svg)](https://github.com/py-mine/mcstatus/actions/workflows/main.yml)

Mcstatus provides an API and command line script to fetch publicly available data from Minecraft servers. Specifically, mcstatus retrieves data by using these protocols: [Server List Ping](https://minecraft.wiki/w/Java_Edition_protocol/Server_List_Ping) and [Query](https://minecraft.wiki/w/Query). Because of mcstatus, you do not need to fully understand those protocols and can instead skip straight to retrieving minecraft server data quickly in your own programs.

## Installation

Mcstatus is available on [PyPI](https://pypi.org/project/mcstatus/), and can be installed trivially with:

```bash
python3 -m pip install mcstatus
```

## Usage

### Python API

#### Java Edition (1.7+)

```python
from mcstatus import JavaServer

# You can pass the same address you'd enter into the address field in minecraft into the 'lookup' function
# If you know the host and port, you may skip this and use JavaServer("example.org", 1234)
server = JavaServer.lookup("example.org:1234")

# 'status' is supported by all Minecraft servers that are version 1.7 or higher.
# Don't expect the player list to always be complete, because many servers run
# plugins that hide this information or limit the number of players returned or even
# alter this list to contain fake players for purposes of having a custom message here.
status = server.status()
print(f"The server has {status.players.online} player(s) online and replied in {status.latency} ms")

# 'ping' is supported by all Minecraft servers that are version 1.7 or higher.
# It is included in a 'status' call, but is also exposed separate if you do not require the additional info.
latency = server.ping()
print(f"The server replied in {latency} ms")

# 'query' has to be enabled in a server's server.properties file!
# It may give more information than a ping, such as a full player list or mod information.
query = server.query()
print(f"The server has the following players online: {', '.join(query.players.names)}")
```

#### Java Edition (Beta 1.8-1.6)

```python
from mcstatus import LegacyServer

# You can pass the same address you'd enter into the address field in minecraft into the 'lookup' function
# If you know the host and port, you may skip this and use LegacyServer("example.org", 1234)
server = LegacyServer.lookup("example.org:1234")

# 'status' is supported by all Minecraft servers.
status = server.status()
print(f"The server has {status.players.online} player(s) online and replied in {status.latency} ms")
```

#### Bedrock Edition

```python
from mcstatus import BedrockServer

# You can pass the same address you'd enter into the address field in minecraft into the 'lookup' function
# If you know the host and port, you may skip this and use BedrockServer("example.org", 19132)
server = BedrockServer.lookup("example.org:19132")

# 'status' is the only feature that is supported by Bedrock at this time.
# In this case status includes players.online, latency, motd, map, gamemode, and players.max. (ex: status.gamemode)
status = server.status()
print(f"The server has {status.players.online} players online and replied in {status.latency} ms")
```

See the [documentation](https://mcstatus.readthedocs.io) to find what you can do with our library!

### Command Line Interface

The mcstatus library includes a simple CLI. Once installed, it can be used through:

```bash
python3 -m mcstatus --help
```

## License

Mcstatus is licensed under the Apache 2.0 license. See LICENSE for full text.


================================================
FILE: docs/Makefile
================================================
# Minimal makefile for Sphinx documentation
#

# 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

# Put it first so that "make" without argument is like "make help".
help:
	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile

# 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)


================================================
FILE: docs/api/basic.rst
================================================
Basic Usage
===========

We are small package, so our API is not so big. There are only few classes, which are suggested for a basic usage.


Request Classes
---------------

These are classes, that you use to send a request to server.


.. autoclass:: mcstatus.server.MCServer
    :members:
    :undoc-members:
    :show-inheritance:

.. autoclass:: mcstatus.server.BaseJavaServer
    :members:
    :undoc-members:
    :show-inheritance:

.. autoclass:: mcstatus.server.JavaServer
    :members:
    :undoc-members:
    :show-inheritance:

.. autoclass:: mcstatus.server.LegacyServer
    :members:
    :undoc-members:
    :show-inheritance:

.. autoclass:: mcstatus.server.BedrockServer
    :members:
    :undoc-members:
    :show-inheritance:


Response Objects
----------------

These are the classes that you get back after making a request.

For Java Server (1.7+)
**********************

.. module:: mcstatus.responses.java

.. autoclass:: mcstatus.responses.java.JavaStatusResponse()
    :members:
    :undoc-members:
    :inherited-members:
    :exclude-members: build

.. autoclass:: mcstatus.responses.java.JavaStatusPlayers()
    :members:
    :undoc-members:
    :inherited-members:
    :exclude-members: build

.. autoclass:: mcstatus.responses.java.JavaStatusPlayer()
    :members:
    :undoc-members:
    :inherited-members:
    :exclude-members: build

.. autoclass:: mcstatus.responses.java.JavaStatusVersion()
    :members:
    :undoc-members:
    :inherited-members:
    :exclude-members: build

.. module:: mcstatus.responses.query
    :no-index:

.. autoclass:: mcstatus.responses.query.QueryResponse()
    :members:
    :undoc-members:
    :inherited-members:
    :exclude-members: build

.. autoclass:: mcstatus.responses.query.QueryPlayers()
    :members:
    :undoc-members:
    :inherited-members:
    :exclude-members: build

.. autoclass:: mcstatus.responses.query.QuerySoftware()
    :members:
    :undoc-members:
    :inherited-members:
    :exclude-members: build

Forge Data
**********

Forge mod metadata is available on :attr:`status.forge_data <mcstatus.responses.java.JavaStatusResponse.forge_data>`.

.. module:: mcstatus.responses.forge
    :no-index:

.. autoclass:: mcstatus.responses.forge.ForgeData()
    :members:
    :undoc-members:
    :inherited-members:
    :exclude-members: build

.. autoclass:: mcstatus.responses.forge.ForgeDataChannel()
    :members:
    :undoc-members:
    :inherited-members:
    :exclude-members: build, decode

.. autoclass:: mcstatus.responses.forge.ForgeDataMod()
    :members:
    :undoc-members:
    :inherited-members:
    :exclude-members: build, decode

For Java Server (Beta 1.8-1.6)
******************************

.. versionadded:: 12.1.0

.. versionadded:: 13.0.0
   Support for Beta 1.8+ (before was 1.4+)

.. module:: mcstatus.responses.legacy
    :no-index:

.. autoclass:: mcstatus.responses.legacy.LegacyStatusResponse()
    :members:
    :undoc-members:
    :inherited-members:
    :exclude-members: build

.. autoclass:: mcstatus.responses.legacy.LegacyStatusPlayers()
    :members:
    :undoc-members:
    :inherited-members:
    :exclude-members: build

.. autoclass:: mcstatus.responses.legacy.LegacyStatusVersion()
    :members:
    :undoc-members:
    :inherited-members:
    :exclude-members: build


For Bedrock Servers
*******************

.. module:: mcstatus.responses.bedrock
    :no-index:

.. autoclass:: mcstatus.responses.bedrock.BedrockStatusResponse()
    :members:
    :undoc-members:
    :inherited-members:
    :exclude-members: build

.. autoclass:: mcstatus.responses.bedrock.BedrockStatusPlayers()
    :members:
    :undoc-members:
    :inherited-members:
    :exclude-members: build

.. autoclass:: mcstatus.responses.bedrock.BedrockStatusVersion()
    :members:
    :undoc-members:
    :inherited-members:
    :exclude-members: build


Conclusion
----------

That is all! See also our :doc:`examples </examples/examples>`!


================================================
FILE: docs/api/internal.rst
================================================
Internal Data
=============

This page contains some internal objects, classes, functions, etc. These **are not a part of the Public API** and
you **should not use them**, as we do not guarantee their backwards compatibility between different library
versions. They are only documented here for linkable reference to them.


.. autoclass:: mcstatus._protocol.java_client.JavaClient
    :members:
    :undoc-members:
    :show-inheritance:

.. autoclass:: mcstatus._protocol.java_client.AsyncJavaClient
    :members:
    :undoc-members:
    :show-inheritance:

.. autoclass:: mcstatus._protocol.legacy_client.LegacyClient
    :members:
    :undoc-members:
    :show-inheritance:

.. autoclass:: mcstatus._protocol.legacy_client.AsyncLegacyClient
    :members:
    :undoc-members:
    :show-inheritance:

.. autoclass:: mcstatus._protocol.bedrock_client.BedrockClient
    :members:
    :undoc-members:
    :show-inheritance:

.. automodule:: mcstatus._net.address
    :members:
    :exclude-members: Address
    :undoc-members:
    :show-inheritance:

    .. autoclass:: Address
        :members:
        :undoc-members:
        :show-inheritance:

        .. attribute:: host
            :type: str
            :canonical: mcstatus._net.address.Address.host

            The hostname or IP address of the server.

        .. attribute:: port
            :type: int
            :canonical: mcstatus._net.address.Address.port

            The port of the server.

.. automodule:: mcstatus._net.dns
   :members:
   :undoc-members:
   :show-inheritance:

.. autoclass:: mcstatus.responses.base.BaseStatusResponse
    :members:
    :undoc-members:
    :show-inheritance:

.. autoclass:: mcstatus.responses.base.BaseStatusPlayers
    :members:
    :undoc-members:
    :show-inheritance:

.. autoclass:: mcstatus.responses.base.BaseStatusVersion
    :members:
    :undoc-members:
    :show-inheritance:


================================================
FILE: docs/api/motd_parsing.rst
================================================
MOTD Parsing
============

We provide a really powerful system to parse servers MOTDs.


The main class
--------------

Firstly there is the main class, which you get directly from :meth:`status <mcstatus.server.MCStatus.status>` methods.

.. autoclass:: mcstatus.motd.Motd
    :members:
    :undoc-members:


Components
----------

Those are used in :attr:`~mcstatus.motd.Motd.parsed` field.

.. automodule:: mcstatus.motd.components
    :members:
    :undoc-members:

    .. py:type:: ParsedMotdComponent
      :canonical: Formatting | MinecraftColor | WebColor | TranslationTag | str


================================================
FILE: docs/conf.py
================================================
"""Configuration file for the Sphinx documentation builder.

This file does only contain a selection of the most common options. For a
full list see the documentation:
http://www.sphinx-doc.org/en/master/config
"""

# -- Path setup --------------------------------------------------------------

# 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.

from __future__ import annotations

import os
import sys
from importlib.metadata import version as importlib_version
from typing import TYPE_CHECKING

from packaging.version import Version, parse as parse_version

if TYPE_CHECKING:
    from typing_extensions import override
else:
    override = lambda f: f  # noqa: E731

sys.path.insert(0, os.path.abspath(".."))  # noqa: PTH100


# -- Project information -----------------------------------------------------


def _get_version() -> Version:
    return parse_version(importlib_version("mcstatus"))


project = "mcstatus"
copyright = "mcstatus, py-mine"
author = "Dinnerbone"

parsed_version = _get_version()

# The short X.Y version
version = parsed_version.base_version
# The full version, including alpha/beta/rc tags
release = str(parsed_version)


# -- General configuration ---------------------------------------------------

# 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.doctest",
    "sphinx.ext.todo",
    "sphinx.ext.coverage",
    "sphinx.ext.viewcode",
    "sphinx.ext.autosummary",
    "sphinx.ext.autosectionlabel",
    # Used to reference for third party projects:
    "sphinx.ext.intersphinx",
    # Used to include .md files:
    "m2r2",
]

autoclass_content = "both"
autodoc_member_order = "bysource"

autodoc_default_flags = {
    "members": "",
    "undoc-members": "code,error_template",
    "exclude-members": "__dict__,__weakref__",
}

# Automatically generate section labels:
autosectionlabel_prefix_document = True

# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:

source_suffix = [".rst", ".md"]

# The master toctree document.
master_doc = "index"

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = "en"

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = "sphinx"

add_module_names = False

# -- Options for HTML output -------------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
html_theme = "furo"

# 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 = {
    "navigation_with_keys": True,
}

# -- Extension configuration -------------------------------------------------

# Third-party projects documentation references:
intersphinx_mapping = {
    "python": ("https://docs.python.org/3", None),
    "dns": ("https://dnspython.readthedocs.io/en/stable/", None),
}


# -- Options for todo extension ----------------------------------------------

# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True


# Mocks
def mock_autodoc() -> None:
    """Mock autodoc to not add ``Bases: object`` to the classes, that do not have super classes.

    See also https://stackoverflow.com/a/75041544/20952782.
    """
    from sphinx.ext import autodoc  # noqa: PLC0415

    class MockedClassDocumenter(autodoc.ClassDocumenter):
        @override
        def add_line(self, line: str, source: str, *lineno: int) -> None:
            if line == "   Bases: :py:class:`object`":
                return
            super().add_line(line, source, *lineno)

    autodoc.ClassDocumenter = MockedClassDocumenter


def delete_doc_for_address_base() -> None:
    """``__new__`` is appended to the docstring of ``AddressBase``.

    And we do not want autogenerated nonsense docstring there.
    """  # noqa: D401 # imperative mood
    from mcstatus._net.address import _AddressBase  # noqa: PLC0415

    del _AddressBase.__new__.__doc__


mock_autodoc()
delete_doc_for_address_base()


================================================
FILE: docs/examples/code/ping_as_java_and_bedrock_in_one_time.py
================================================
from __future__ import annotations

import asyncio

from mcstatus import BedrockServer, JavaServer
from mcstatus.responses.bedrock import BedrockStatusResponse
from mcstatus.responses.java import JavaStatusResponse


async def status(host: str) -> JavaStatusResponse | BedrockStatusResponse:
    """Get status from server, which can be Java or Bedrock.

    The function will ping server as Java and as Bedrock in one time, and return the first response.
    """
    success_task = await handle_exceptions(
        *(
            await asyncio.wait(
                {
                    asyncio.create_task(handle_java(host), name="Get status as Java"),
                    asyncio.create_task(handle_bedrock(host), name="Get status as Bedrock"),
                },
                return_when=asyncio.FIRST_COMPLETED,
            )
        )
    )

    if success_task is None:
        raise ValueError("No tasks were successful. Is server offline?")

    return success_task.result()


async def handle_exceptions(done: set[asyncio.Task], pending: set[asyncio.Task]) -> asyncio.Task | None:
    """Handle exceptions from tasks.

    Also, cancel all pending tasks, if found the correct one.
    """
    if len(done) == 0:
        raise ValueError("No tasks was given to `done` set.")

    for i, task in enumerate(done):
        if task.exception() is not None:
            if len(pending) == 0:
                continue

            if i == len(done) - 1:  # firstly check all items from `done` set, and then handle pending set
                return await handle_exceptions(*(await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)))
        else:
            for pending_task in pending:
                pending_task.cancel()
            return task
    return None


async def handle_java(host: str) -> JavaStatusResponse:
    """Wrap mcstatus, to compress lookup and status into one function."""
    return await (await JavaServer.async_lookup(host)).async_status()


async def handle_bedrock(host: str) -> BedrockStatusResponse:
    """Wrap mcstatus, to compress lookup and status into one function."""
    # note: `BedrockServer` doesn't have `async_lookup` method, see it's docstring
    return await BedrockServer.lookup(host).async_status()


================================================
FILE: docs/examples/code/ping_many_servers_at_once.py
================================================
import asyncio

from mcstatus import JavaServer


async def ping_server(ip: str) -> None:
    try:
        status = await (await JavaServer.async_lookup(ip)).async_status()
    except Exception:
        return

    print(f"{ip} - {status.latency}ms")  # handle somehow responses here


async def ping_ips(ips: list[str]) -> None:
    to_process: list[str] = []

    for ip in ips:
        if len(to_process) <= 10:  # 10 means here how many servers will be pinged at once
            to_process.append(ip)
            continue

        await asyncio.wait({asyncio.create_task(ping_server(ip_to_ping)) for ip_to_ping in to_process})
        to_process = []


def main() -> None:
    ips = ["hypixel.net", "play.hivemc.com", "play.cubecraft.net", ...]  # insert here your ips!
    asyncio.run(ping_ips(ips))


if __name__ == "__main__":
    main()


================================================
FILE: docs/examples/code/player_list_from_query_with_fallback_on_status.py
================================================
from mcstatus import JavaServer

server = JavaServer.lookup("play.hypixel.net")
query = server.query()

if query.players.list:
    print("Players online:", ", ".join(query.players.list))
else:
    status = server.status()

    if not status.players.sample:
        print("Cant find players list, no one online or the server disabled this.")
    else:
        print("Players online:", ", ".join([player.name for player in status.players.sample]))


================================================
FILE: docs/examples/examples.rst
================================================
Examples
========

We have these examples at the moment:


.. toctree::
	:maxdepth: 1
	:caption: Examples

	ping_as_java_and_bedrock_in_one_time.rst
	ping_many_servers_at_once.rst
	player_list_from_query_with_fallback_on_status.rst


Feel free to propose us more examples, we will be happy to add them to the list!


================================================
FILE: docs/examples/ping_as_java_and_bedrock_in_one_time.rst
================================================
Ping as Java and as Bedrock in one time
=======================================

You can easily ping a server as a Java server and as a Bedrock server in one time.

.. literalinclude:: code/ping_as_java_and_bedrock_in_one_time.py

As you can see in the code, ``status`` function returns
:class:`~mcstatus.responses.java.JavaStatusResponse` or
:class:`~mcstatus.responses.bedrock.BedrockStatusResponse` object. You can use
:func:`isinstance` checks to access attributes that are only in one of the
objects.

.. code-block:: python

    response = await status("hypixel.net")

    if isinstance(response, BedrockStatusResponse):
        map_name = response.map_name
    else:
        map_name = None
    # or
    map_name = response.map_name if isinstance(response, BedrockStatusResponse) else None

    print(f"Server map name is: {map_name}")


================================================
FILE: docs/examples/ping_many_servers_at_once.rst
================================================
Ping many servers at once
=========================

You can ping many servers at once with mcstatus async methods, just look at

.. literalinclude:: code/ping_many_servers_at_once.py


================================================
FILE: docs/examples/player_list_from_query_with_fallback_on_status.rst
================================================
Get player list from query, while falling back on status
========================================================

.. literalinclude:: code/player_list_from_query_with_fallback_on_status.py


================================================
FILE: docs/index.rst
================================================
.. mdinclude:: ../README.md

Content
-------

.. toctree::
	:maxdepth: 1
	:caption: Pages

	pages/faq.rst
	pages/contributing.rst
	pages/versioning.rst
	examples/examples.rst

.. toctree::
	:maxdepth: 1
	:caption: API Documentation

	api/basic.rst
	api/motd_parsing.rst
	api/internal.rst


Indices and tables
------------------

* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`


================================================
FILE: docs/pages/contributing.rst
================================================
Contributing
============

Setup
-----

.. code-block:: sh

   pipx install uv
   uv sync

   # The following command will depend on your operating system and shell.
   # For MacOS/Linux:
   . .venv/bin/activate
   # For Windows CMD:
   .venv\Scripts\activate.bat
   # For Windows PowerShell:
   .venv\Scripts\Activate.ps1

   pre-commit install

In addition to this, you may also want to install ruff and pyright (pylance) plugins for your IDE.

Expectations
------------

When making changes to code that results in new behavior, it is expected that
automated tests are submitted as well to prevent the behavior from breaking in
the future. It matters not whether those changes are for a bugfix or a new
feature; all behavior changes require tests.

Feel free to hop on the `Discord server <https://discord.gg/C2wX7zduxC>`_ to
chat with other maintainers if you are unsure about something. We would
definitely rather have a conversation first before consuming unnecessary time
for something that someone else could already be working on or any other reason
that might make the work unnecessary. It is a community effort to maintain and
grow mcstatus. Much discussion happens on the `Discord server
<https://discord.gg/C2wX7zduxC>`_ to collaborate ideas together.

Once you have all the checks passing and any new behavior changes are tested,
feel free to open a pull request. Pull requests are how GitHub allows forks to
submit branches for consideration to be merged into the original repo.

Common development tasks
------------------------

.. code-block:: sh

   # Activating the virtual environment, allowing you to work with the project's dependencies
   # installed there by uv. This command is OS and shell dependent.
   # For MacOS/Linux:
   . .venv/bin/activate
   # For Windows CMD:
   .venv\Scripts\activate.bat
   # For Windows PowerShell:
   .venv\Scripts\Activate.ps1

   poe docs  # Renders documentation from docs/ folder
   poe format  # Executes automatic formatter for style consistency
   poe lint  # Executes linting tools that help increase code quality
   poe test  # Executes unit tests

Listing available tasks
-----------------------

.. code-block:: sh

   poe

Being fancy with tasks
----------------------

You may pass extra arguments to the underlying tasks. Here's an example that
tells the underlying ``pytest`` to execute only the query tests with maximum
verbosity.

.. code-block:: sh

   poe test -vvv -k TestQuery


================================================
FILE: docs/pages/faq.rst
================================================
Frequently Asked Questions
==========================


Why doesn't :class:`~mcstatus.server.BedrockServer` have an async :meth:`~mcstatus.server.MCServer.lookup` method?
------------------------------------------------------------------------------------------------------------------

With Java servers, to find the server, we sometimes end up performing an SRV
DNS lookup. This means making a request to your DNS server and waiting for an
answer, making that lookup a blocking operation (during which other things can
be done).

.. note::
    An SRV record allows the server to have an address like: ``hypixel.net``,
    that points to a some specified IP/Host and port, depending on this record.

    That way, even if the server is hosted on a non-standard port (other than
    25565, say 8855), you won't need to use ``myserver.com:8855``, since the
    port number will simply be stored in the SRV record, so people can still
    connect simply with ``myserver.com``.

    On top of that, it also allows to specify a different IP/Host, which means
    you don't need to use the same server to run both the website, and the
    minecraft server. Instead, the SRV record can simply point to a different
    IP/Host address (like ``mc.hypixel.net``, or ``209.222.114.112``).

However with Bedrock servers, no such lookups are required (Bedrock doesn't
support SRV records), and so there is no blocking I/O operation being made,
that would justify having an async version.

In fact, all that the bedrock lookup does is parsing the ``host:port`` address,
and obtaining the ``host`` and ``port`` parts out of it (with some error
handling, and support for default ports).


Incorrect encoding
------------------

In Query protocol, Minecraft uses ISO 8859-1 for encoding all text (like MOTD,
server name, etc.). This can cause problems with non-latin characters. To fix
such error, you can re-encode text into UTF-8.

.. code-block:: python

    >>> query = JavaServer.lookup("my-server-ip.com").query()
    >>> query.motd.to_minecraft()
    'Ð\x9fÑ\x80ивÑ\x96Ñ\x82!'
    >>> query.motd.to_minecraft().encode("iso-8859-1").decode("utf-8")
    'Привіт!'

:attr:`query.motd <mcstatus.responses.query.QueryResponse.motd>` here can be
anything, that contains incorrect encoding.


How to get server image?
------------------------

On Bedrock, only official servers have a server image. There is no way to get
or set an icon to a custom server. For Java servers, you can use
:attr:`status.icon <mcstatus.responses.java.JavaStatusResponse.icon>`
attribute. It will return `Base64 <https://en.wikipedia.org/wiki/Base64>`_
encoded PNG image. If you wish to save this image into a file, this is how:

.. code-block:: python

    import base64
    from mcstatus import JavaServer

    server = JavaServer.lookup("hypixel.net")
    status = server.status()

    decoded_icon = base64.b64decode(status.icon.removeprefix("data:image/png;base64,"))
    with open("server-icon.png", "wb") as f:
        f.write(decoded_icon)

.. note::
    Most modern browsers support simply pasting the raw Base64 image into the
    URL bar, which will open it as an image preview, allowing you to take a
    quick look at it without having to use file saving from Python.

    See `How to display Base64 image <https://stackoverflow.com/questions/8499633>`_
    and `Base64 Images: Support table <https://caniuse.com/atob-btoa>`_.


================================================
FILE: docs/pages/versioning.rst
================================================
Versioning Practices & Guarantees
=================================

This page explains what you can expect when upgrading mcstatus, and what
changes may occur in major, minor, and patch releases.

mcstatus follows the `Semantic Versioning <https://semver.org>`_ model in terms
of **versioning guarantees and expectations**, using the familiar
``MAJOR.MINOR.PATCH`` structure.

Internally and for distribution, mcstatus version numbers follow `PEP 440
<https://peps.python.org/pep-0440/>`_. This primarily affects the exact format
of pre-releases, post-releases, and development releases, but does not change
the meaning of major, minor, or patch version increments.

- **MAJOR**: incompatible (breaking) changes to the public API
- **MINOR**: backwards-compatible features and improvements
- **PATCH**: backwards-compatible bug fixes

What is "public API"?
---------------------

For mcstatus, the **public API** is defined by what is **documented in the
public API pages**.

- Anything documented under :doc:`/api/basic` and :doc:`/api/motd_parsing` is
  public API.
- Anything documented under :doc:`/api/internal` is **not** public API.
- Anything not documented at all is also **not** public API.
- Any module, package, attribute, or symbol whose name starts with an
  underscore (``_``) is considered internal and may change at any time.

Release types and guarantees
----------------------------

.. warning::

   **Bug fixes are generally not backported.**

   mcstatus primarily supports the **latest released version** of the library.
   Bugs are fixed only in that version, and fixes are not backported to older
   releases.

   This includes most bug fixes and the vast majority of security-related
   fixes. Backporting fixes significantly increases maintenance overhead and
   often requires maintaining multiple diverging code paths, which is not
   sustainable for this project.

   In exceptional cases, a truly critical issue may be addressed via a hotfix
   release. Such cases are rare and handled on a best-effort basis. If you rely
   on older versions of mcstatus, you may encounter bugs that will not be fixed
   unless you upgrade.

Patch releases (x.y.PATCH)
~~~~~~~~~~~~~~~~~~~~~~~~~~

Patch releases contain:

- bug fixes
- documentation changes of any kind (e.g. new docs pages, or also changes to
  docstrings of some public API objects in the codebase)
- improvements that do not change the public API contract
- breaking changes in private API
- dependency updates (including major dependency updates), as long as the
  public contract of this library remains compatible
- changes in library's public API typing behavior (see
  :ref:`typing-stability-guarantees`)

Patch releases do **not** contain breaking changes to the public API. They also
generally don't introduce any new behavior, other than for purposes of
resolving existing bugs, or internal updates.

Note that in some cases, if you are relying on behavior which we consider to be
a bug, it is possible that we might end up changing this behavior in a `PATCH`
release, in an effort to fix the unintentional, wrong behavior, breaking your
dependency. Bug-fixes pretty much always happen without any deprecations.

.. admonition:: Example

  To understand what constitutes a bug-fix with such breakage potential, as an
  example, if mcstatus incorrectly parses a MOTD format that some server sends,
  fixing that parsing is a bug fix, even if it changes the output format for
  that specific broken MOTD.

Another important note to mention is that mcstatus will not make any efforts to
delay its runtime dependency version updates to align them with minor or major
releases. Transitive breakages caused by dependency updates are considered
acceptable as long as mcstatus's documented public API remains compatible.

Minor releases (x.MINOR.z)
~~~~~~~~~~~~~~~~~~~~~~~~~~

Generally, minor releases exist to introduce new features to the library in a
non-breaking way. They may include:

- new public features (new classes, functions, parameters, constants) without
  affecting existing behavior
- new optional arguments in public functions/class constructors with sensible
  defaults that don't change existing usage
- **new deprecations** (introduced as warnings containing new replacements /
  deprecation reasons)
- backwards-compatible behavior improvements
- dropping support for a Python version (e.g. dropping Python 3.9 because it is
  past its `end-of-life <https://devguide.python.org/versions/>`_)
- any additional changes that patch releases can contain


Minor releases do **not** intentionally introduce breaking changes to the
documented public API.

Deprecations in minor releases
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

New deprecations may be introduced in minor releases for behavior that is
scheduled for removal in the next major version. These deprecations are emitted
as warnings and do not immediately break existing code. For more information
about our deprecation handling, see the :ref:`deprecations-and-removals`
section.

Major releases (MAJOR.y.z)
~~~~~~~~~~~~~~~~~~~~~~~~~~

Major releases may include breaking changes to the documented public API. These
will be called out in the changelog and typically include one or more of:

- removing a documented public class, function, or attribute
- renaming a public API without a deprecated alias
- changing the default value or meaning of a function parameter in a way that
  changes original usage
- removing a deprecated alias or deprecated import path
- any additional changes that minor and patch releases can contain

We generally try to avoid immediate breaking changes that didn't go through at
least a brief deprecation period of at least 1 release cycle (e.g. deprecation
will first be introduced in v12.4.0, before the old behavior is removed in
v13).

If a major refactor of the library was performed, or just generally one that is
expected to make a lot of our users to face deprecations, we try to introduce
these deprecations in a major release, instead of a minor one (with them
staying in the project until the next major release after this one). Though
this is not a guarantee.

We can still choose to not go through deprecations at all and introduce
entirely new breaking changes in a new major release. We will however try to
avoid doing so unless we have a very strong reason to do so.

mcstatus and Minecraft versions
-------------------------------

mcstatus is somewhat coupled to Minecraft server protocols and behaviors. As
Minecraft evolves, mcstatus may need to make changes that are "breaking" at the
library level, even if they are driven by protocol or ecosystem changes.

mcstatus aims to:

- remain compatible with widely used Minecraft versions and server
  implementations
- release updates in a timely manner when protocol behavior or common server
  responses change
- provide support for legacy Minecraft versions (within reason), if the latest
  protocol for obtaining status is no longer compatible with the previous one

Fortunately, breaking changes in the protocol when it comes to obtaining server
status are very uncommon. But it is possible that Minecraft introduces a change
that our library cannot process at the time of introduction, this might or
might not cause hard failures on mcstatus part, even if older Minecraft clients
can process these information, mcstatus might not be able to, until we release
a new version to support it.

Because mcstatus is maintained by volunteers, timing may vary, but we try to
keep mcstatus working with the latest Minecraft releases and fix critical bugs
quickly.

.. _typing-stability-guarantees:

Typing stability guarantees
---------------------------

mcstatus is a strongly typed library which actively supports and encourages the
use of type-checkers.

However, typing definitions occasionally need to change alongside internal
refactors so that mcstatus itself remains internally type-correct, and Python's
typing system unfortunately does not really provide a practical way to
deprecate types gracefully.

For this reason, **typing breakages may occur even in patch releases**.

We actively try to avoid typing breakages or postpone them to minor or even
major releases when possible, but if doing so would significantly slow down our
ability to deliver a necessary bug-fix or feature, we do not consider
maintaining the stability of the public typing interface significant enough to
prevent us from shipping such a change.

.. admonition:: Example

   To understand what we meant by breaking changes in the public typing
   interface, it can include things like:

   - Adding a ``@final`` decorator to one of our classes
   - Making a class generic
   - Introducing an additional type parameter to already generic class
   - Removing a generic type parameter / making a class no longer generic
   - Adding a convenience type-alias variable
   - Adding a new type into a union of types in an exposed convenience
     type-alias

   Any of these changes can occur even in a patch release.

.. _deprecations-and-removals:

Deprecations and removals
-------------------------

When we deprecate something, we aim to emit a ``DeprecationWarning`` with a
message containing:

- what was deprecated
- what to use instead (if a replacement is available)
- a target removal version

When we deprecate something, we generally aim to remove it in a **major
release**, after it has been deprecated for at least one release cycle. (E.g. a
deprecation introduced in ``v12.2.0`` will most likely have its removal
scheduled in ``v13.0.0`` ). For some more significant changes, we can sometimes
keep a deprecation around for longer though (e.g. a deprecation introduced in
``v12.2.0`` with removal scheduled for ``v14.0.0``).

We will **always** explicitly include the removal version, until which the
deprecated behavior will still be guaranteed to remain functional.

Post-removal deprecation handling
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Once a deprecated feature has passed its stated removal version, its use will
result in a breaking change. This is guaranteed to happen in the stated removal
version.

.. warning::

   **Relying on deprecated behavior after its removal is unsupported.**

   Where feasible, mcstatus will explicitly raise the corresponding
   ``DeprecationWarning`` as a hard exception, rather than allowing the removal
   to manifest as a less clear runtime failures (such as ``AttributeError``).
   This is a deliberate best-effort attempt to provide clearer diagnostics and
   improve the upgrade experience.

   This behavior is **not part of the versioning guarantees**. Any post-removal
   deprecation handling is considered **temporary by design** and may be
   intentionally removed after some time, including in patch releases, once the
   breakage has been in effect for a reasonable period (typically 1-5 months).

   Users must not rely on the presence, wording, or longevity of post-removal
   deprecation handling. After removal, failures may surface as generic runtime
   errors without any direct reference to the original deprecation.

For this reason, you should always pay attention to deprecation warnings and
resolve them ahead of time, ideally after any minor updates, but at the very
least before upgrading to a new major version, to avoid unclear hard breakages.


================================================
FILE: docs/pyproject.toml
================================================
[project]
name = "docs"
version = "0.0.0"
license = "Apache-2.0"
requires-python = ">=3.12"
dependencies = [
  "sphinx~=9.1.0",
  "sphinx-autodoc-typehints~=3.10.0",
  "furo>=2025.7.19",
  "m2r2~=0.3.4",
  "packaging~=26.0",
  "uv-dynamic-versioning~=0.14.0",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
bypass-selection = true


================================================
FILE: mcstatus/__init__.py
================================================
from mcstatus.server import BedrockServer, JavaServer, LegacyServer, MCServer

__all__ = [
    "BedrockServer",
    "JavaServer",
    "LegacyServer",
    "MCServer",
]


================================================
FILE: mcstatus/__main__.py
================================================
# ruff: noqa: T201 # usage of `print`
from __future__ import annotations

import argparse
import json
import socket
import sys
from typing import Any, TYPE_CHECKING, TypeAlias

import dns.resolver

from mcstatus import BedrockServer, JavaServer, LegacyServer
from mcstatus.responses import JavaStatusResponse

if TYPE_CHECKING:
    from mcstatus.motd import Motd

SupportedServers: TypeAlias = "JavaServer | LegacyServer | BedrockServer"

PING_PACKET_FAIL_WARNING = (
    "warning: contacting {address} failed with a 'ping' packet but succeeded with a 'status' packet,\n"
    "         this is likely a bug in the server-side implementation.\n"
    '         (note: ping packet failed due to "{ping_exc}")\n'
    "         for more details, see: https://mcstatus.readthedocs.io/en/stable/pages/faq/\n"
)

QUERY_FAIL_WARNING = (
    "The server did not respond to the query protocol."
    "\nPlease ensure that the server has enable-query turned on,"
    " and that the necessary port (same as server-port unless query-port is set) is open in any firewall(s)."
    "\nSee https://minecraft.wiki/w/Query for further information."
)


def _motd(motd: Motd) -> str:
    """Format MOTD for human-readable output, with leading line break if multiline."""
    s = motd.to_ansi()
    return f"\n{s}" if "\n" in s else f" {s}"


def _kind(serv: SupportedServers) -> str:
    if isinstance(serv, JavaServer):
        return "Java"
    if isinstance(serv, LegacyServer):
        return "Java (pre-1.7)"
    if isinstance(serv, BedrockServer):
        return "Bedrock"
    raise ValueError(f"unsupported server for kind: {serv}")


def _ping_with_fallback(server: SupportedServers) -> float:
    # only Java has ping method
    if not isinstance(server, JavaServer):
        return server.status().latency

    # try faster ping packet first, falling back to status with a warning.
    ping_exc = None
    try:
        return server.ping(tries=1)
    except Exception as e:  # noqa: BLE001 # blindly catching Exception
        ping_exc = e

    latency = server.status().latency

    address = f"{server.address.host}:{server.address.port}"
    print(
        PING_PACKET_FAIL_WARNING.format(address=address, ping_exc=ping_exc),
        file=sys.stderr,
    )

    return latency


def ping_cmd(server: SupportedServers) -> int:
    print(_ping_with_fallback(server))
    return 0


def status_cmd(server: SupportedServers) -> int:
    response = server.status()

    java_res = response if isinstance(response, JavaStatusResponse) else None

    if java_res and java_res.players.sample:
        player_sample = "\n  " + "\n  ".join(f"{player.name} ({player.id})" for player in java_res.players.sample)
    else:
        player_sample = ""

    print(f"version: {_kind(server)} {response.version.name} (protocol {response.version.protocol})")
    print(f"motd:{_motd(response.motd)}")
    print(f"players: {response.players.online}/{response.players.max}{player_sample}")
    print(f"ping: {response.latency:.2f} ms")
    return 0


def json_cmd(server: SupportedServers) -> int:
    data: dict[str, Any] = {"online": False, "kind": _kind(server)}

    status_res = query_res = exn = None
    try:
        status_res = server.status(tries=1)
    except Exception as e:  # noqa: BLE001 # blindly catching Exception
        exn = exn or e

    try:
        if isinstance(server, JavaServer):
            query_res = server.query(tries=1)
    except Exception as e:  # noqa: BLE001 # blindly catching Exception
        exn = exn or e

    # construct 'data' dict outside try/except to ensure data processing errors
    # are noticed.
    data["online"] = bool(status_res or query_res)
    if not data["online"]:
        assert exn, "server offline but no exception?"
        data["error"] = str(exn)

    if status_res is not None:
        data["status"] = status_res.as_dict()
    if query_res is not None:
        data["query"] = query_res.as_dict()

    json.dump(data, sys.stdout)
    return 0


def query_cmd(server: SupportedServers) -> int:
    if not isinstance(server, JavaServer):
        print("The 'query' protocol is only supported by Java servers.", file=sys.stderr)
        return 1

    try:
        response = server.query()
    except TimeoutError:
        print(QUERY_FAIL_WARNING, file=sys.stderr)
        return 1

    print(f"host: {response.raw['hostip']}:{response.raw['hostport']}")
    print(f"software: {_kind(server)} {response.software.version} {response.software.brand}")
    print(f"motd:{_motd(response.motd)}")
    print(f"plugins: {response.software.plugins}")
    print(f"players: {response.players.online}/{response.players.max} {response.players.list}")
    return 0


def main(argv: list[str] = sys.argv[1:]) -> int:
    parser = argparse.ArgumentParser(
        "mcstatus",
        description="""
        mcstatus provides an easy way to query Minecraft servers for any information
        they can expose. It provides three modes of access: query, status, ping and json.
        """,
    )

    parser.add_argument("address", help="The address of the server.")
    group = parser.add_mutually_exclusive_group()
    group.add_argument("--bedrock", help="Specifies that 'address' is a Bedrock server (default: Java).", action="store_true")
    group.add_argument(
        "--legacy", help="Specifies that 'address' is a pre-1.7 Java server (default: 1.7+).", action="store_true"
    )

    subparsers = parser.add_subparsers(title="commands", description="Command to run, defaults to 'status'.")
    parser.set_defaults(func=status_cmd)

    subparsers.add_parser("ping", help="Ping server for latency.").set_defaults(func=ping_cmd)
    subparsers.add_parser("status", help="Prints server status.").set_defaults(func=status_cmd)
    subparsers.add_parser(
        "query", help="Prints detailed server information. Must be enabled in servers' server.properties file."
    ).set_defaults(func=query_cmd)
    subparsers.add_parser(
        "json",
        help="Prints server status and query in json.",
    ).set_defaults(func=json_cmd)

    args = parser.parse_args(argv)
    if args.bedrock:
        lookup = BedrockServer.lookup
    elif args.legacy:
        lookup = LegacyServer.lookup
    else:
        lookup = JavaServer.lookup

    try:
        server = lookup(args.address)
        return args.func(server)
    except (socket.gaierror, dns.resolver.NoNameservers, ConnectionError, TimeoutError) as e:
        # catch and hide traceback for expected user-facing errors
        print(f"Error: {e!r}", file=sys.stderr)
        return 1


if __name__ == "__main__":
    sys.exit(main())


================================================
FILE: mcstatus/_compat/README.md
================================================
# Compatibility Shims

This directory holds compatibility shims for deprecated public modules.

These modules are not part of the main source tree. They are mapped into their
deprecated import paths during packaging via Hatchling `force-include` entries
in `pyproject.toml` for the sdist (wheels are built from the sdist).

Example:

```toml
[tool.hatch.build.targets.sdist.force-include]
"mcstatus/_compat/status_response.py" = "mcstatus/status_response.py"
```

This means that the build system will include these for us in the actual built
packages, without the files having to clutter the actual source tree of the
project, making development cleaner and less confusing. As an additional
benefit, it prevents us from accidentally importing these deprecated utils from
within mcstatus, as they will simply not be present.

> [!WARNING]
> This approach does mean that people using mcstatus directly through a git
> submodule or otherwise attempt to run the mcstatus code from it's direct
> source, rather than going through the proper python module installation will
> NOT be able to utilize these deprecations.
>
> This isn't really a supported method of utilizing mcstatus though, and people
> that do so should expect to face issues.


================================================
FILE: mcstatus/_compat/__init__.py
================================================


================================================
FILE: mcstatus/_compat/forge_data.py
================================================
from mcstatus._utils import deprecation_warn
from mcstatus.responses.forge import ForgeData, ForgeDataChannel, ForgeDataMod

__all__ = [
    "ForgeData",
    "ForgeDataChannel",
    "ForgeDataMod",
]

deprecation_warn(
    obj_name="mcstatus.forge_data",
    removal_version="14.0.0",
    replacement="mcstatus.responses.forge",
)


================================================
FILE: mcstatus/_compat/motd_transformers.py
================================================
from mcstatus._utils import deprecation_warn
from mcstatus.motd._transformers import (
    AnsiTransformer,
    HtmlTransformer,
    MinecraftTransformer,
    PlainTransformer,
    _BaseTransformer as BaseTransformer,
    _NothingTransformer as NothingTransformer,
)

__all__ = [
    "AnsiTransformer",
    "BaseTransformer",
    "HtmlTransformer",
    "MinecraftTransformer",
    "NothingTransformer",
    "PlainTransformer",
]

deprecation_warn(
    obj_name="mcstatus.motd.transformers",
    removal_version="13.0.0",
    extra_msg="MOTD Transformers are no longer a part of mcstatus public API",
)


================================================
FILE: mcstatus/_compat/status_response.py
================================================
from mcstatus._utils import deprecation_warn
from mcstatus.responses import (
    BaseStatusPlayers,
    BaseStatusResponse,
    BaseStatusVersion,
    BedrockStatusPlayers,
    BedrockStatusResponse,
    BedrockStatusVersion,
    JavaStatusPlayer,
    JavaStatusPlayers,
    JavaStatusResponse,
    JavaStatusVersion,
)

__all__ = [
    "BaseStatusPlayers",
    "BaseStatusResponse",
    "BaseStatusVersion",
    "BedrockStatusPlayers",
    "BedrockStatusResponse",
    "BedrockStatusVersion",
    "JavaStatusPlayer",
    "JavaStatusPlayers",
    "JavaStatusResponse",
    "JavaStatusVersion",
]

deprecation_warn(
    obj_name="mcstatus.status_response",
    removal_version="13.0.0",
    replacement="mcstatus.responses",
)


================================================
FILE: mcstatus/_net/__init__.py
================================================


================================================
FILE: mcstatus/_net/address.py
================================================
from __future__ import annotations

import ipaddress
import sys
import warnings
from typing import NamedTuple, TYPE_CHECKING
from urllib.parse import urlparse

import dns.resolver

from mcstatus._net import dns as mc_dns

if TYPE_CHECKING:
    from pathlib import Path

    from typing_extensions import Self


__all__ = [
    "Address",
    "async_minecraft_srv_address_lookup",
    "minecraft_srv_address_lookup",
]


def _valid_urlparse(address: str) -> tuple[str, int | None]:
    """Parse a string address like 127.0.0.1:25565 into host and port parts.

    If the address doesn't have a specified port, None will be returned instead.

    :raises ValueError:
        Unable to resolve hostname of given address
    """
    tmp = urlparse("//" + address)
    if not tmp.hostname:
        raise ValueError(f"Invalid address {address!r}, can't parse.")

    return tmp.hostname, tmp.port


class _AddressBase(NamedTuple):
    """Intermediate NamedTuple class representing an address.

    We can't extend this class directly, since NamedTuples are slotted and
    read-only, however child classes can extend __new__, allowing us do some
    needed processing on child classes derived from this base class.
    """

    host: str
    port: int


class Address(_AddressBase):
    """Extension of a :class:`~typing.NamedTuple` of :attr:`.host` and :attr:`.port`, for storing addresses.

    This class inherits from :class:`tuple`, and is fully compatible with all functions
    which require pure ``(host, port)`` address tuples, but on top of that, it includes
    some neat functionalities, such as validity ensuring, alternative constructors
    for easy quick creation and methods handling IP resolving.

    .. note::
        The class is not a part of a Public API, but attributes :attr:`host` and :attr:`port` are a part of Public API.
    """

    def __init__(self, host: str, port: int) -> None:  # noqa: ARG002 # unused arguments
        # We don't pass the host & port args to super's __init__, because NamedTuples handle
        # everything from __new__ and the passed self already has all of the parameters set.
        super().__init__()

        self._cached_ip: ipaddress.IPv4Address | ipaddress.IPv6Address | None = None

        # Make sure the address is valid
        self._ensure_validity(self.host, self.port)

    @staticmethod
    def _ensure_validity(host: object, port: object) -> None:
        if not isinstance(host, str):
            raise TypeError(f"Host must be a string address, got {type(host)} ({host!r})")
        if not isinstance(port, int):
            raise TypeError(f"Port must be an integer port number, got {type(port)} ({port!r})")
        if port > 65535 or port < 0:
            raise ValueError(f"Port must be within the allowed range (0-2^16), got {port!r}")

    @classmethod
    def from_tuple(cls, tup: tuple[str, int]) -> Self:
        """Construct the class from a regular tuple of ``(host, port)``, commonly used for addresses."""
        return cls(host=tup[0], port=tup[1])

    @classmethod
    def from_path(cls, path: Path, *, default_port: int | None = None) -> Self:
        """Construct the class from a :class:`~pathlib.Path` object.

        If path has a port specified, use it, if not fall back to ``default_port`` kwarg.
        In case ``default_port`` isn't provided and port wasn't specified, raise :exc:`ValueError`.
        """
        address = str(path)
        return cls.parse_address(address, default_port=default_port)

    @classmethod
    def parse_address(cls, address: str, *, default_port: int | None = None) -> Self:
        """Parse a string address like ``127.0.0.1:25565`` into :attr:`.host` and :attr:`.port` parts.

        If the address has a port specified, use it, if not, fall back to ``default_port`` kwarg.

        :raises ValueError:
            Either the address isn't valid and can't be parsed,
            or it lacks a port and ``default_port`` wasn't specified.
        """
        hostname, port = _valid_urlparse(address)
        if port is None:
            if default_port is not None:
                port = default_port
            else:
                raise ValueError(
                    f"Given address {address!r} doesn't contain port and default_port wasn't specified, can't parse."
                )
        return cls(host=hostname, port=port)

    def resolve_ip(self, lifetime: float | None = None) -> ipaddress.IPv4Address | ipaddress.IPv6Address:
        """Resolve a hostname's A record into an IP address.

        If the host is already an IP, this resolving is skipped
        and host is returned directly.

        :param lifetime:
            How many seconds a query should run before timing out.
            Default value for this is inherited from :func:`dns.resolver.resolve`.
        :raises dns.exception.DNSException:
            One of the exceptions possibly raised by :func:`dns.resolver.resolve`.
            Most notably this will be :exc:`dns.exception.Timeout` and :exc:`dns.resolver.NXDOMAIN`
        """
        if self._cached_ip is not None:
            return self._cached_ip

        host = self.host
        if self.host == "localhost" and sys.platform == "darwin":
            host = "127.0.0.1"
            warnings.warn(
                "On macOS because of some mysterious reasons we can't resolve localhost into IP. "
                "Please, replace 'localhost' with '127.0.0.1' (or '::1' for IPv6) in your code to remove this warning.",
                category=RuntimeWarning,
                stacklevel=2,
            )

        try:
            ip = ipaddress.ip_address(host)
        except ValueError:
            # ValueError is raised if the given address wasn't valid
            # this means it's a hostname and we should try to resolve
            # the A record
            ip_addr = mc_dns.resolve_a_record(self.host, lifetime=lifetime)
            ip = ipaddress.ip_address(ip_addr)

        self._cached_ip = ip
        return self._cached_ip

    async def async_resolve_ip(self, lifetime: float | None = None) -> ipaddress.IPv4Address | ipaddress.IPv6Address:
        """Resolve a hostname's A record into an IP address.

        See the docstring for :meth:`.resolve_ip` for further info. This function is purely
        an async alternative to it.
        """
        if self._cached_ip is not None:
            return self._cached_ip

        host = self.host
        if self.host == "localhost" and sys.platform == "darwin":
            host = "127.0.0.1"
            warnings.warn(
                "On macOS because of some mysterious reasons we can't resolve localhost into IP. "
                "Please, replace 'localhost' with '127.0.0.1' (or '::1' for IPv6) in your code to remove this warning.",
                category=RuntimeWarning,
                stacklevel=2,
            )

        try:
            ip = ipaddress.ip_address(host)
        except ValueError:
            # ValueError is raised if the given address wasn't valid
            # this means it's a hostname and we should try to resolve
            # the A record
            ip_addr = await mc_dns.async_resolve_a_record(self.host, lifetime=lifetime)
            ip = ipaddress.ip_address(ip_addr)

        self._cached_ip = ip
        return self._cached_ip


def minecraft_srv_address_lookup(
    address: str,
    *,
    default_port: int | None = None,
    lifetime: float | None = None,
) -> Address:
    """Lookup the SRV record for a Minecraft server.

    Firstly it parses the address, if it doesn't include port, tries SRV record, and if it's not there,
    falls back on ``default_port``.

    This function essentially mimics the address field of a Minecraft Java server. It expects an address like
    ``192.168.0.100:25565``, if this address does contain a port, it will simply use it. If it doesn't, it will try
    to perform an SRV lookup, which if found, will contain the info on which port to use. If there's no SRV record,
    this will fall back to the given ``default_port``.

    :param address:
        The same address which would be used in minecraft's server address field.
        Can look like: ``127.0.0.1``, or ``192.168.0.100:12345``, or ``mc.hypixel.net``, or ``example.com:12345``.
    :param lifetime:
        How many seconds a query should run before timing out.
        Default value for this is inherited from :func:`dns.resolver.resolve`.
    :raises ValueError:
        Either the address isn't valid and can't be parsed,
        or it lacks a port, SRV record isn't present, and ``default_port`` wasn't specified.
    """
    host, port = _valid_urlparse(address)

    # If we found a port in the address, there's nothing more we need
    if port is not None:
        return Address(host, port)

    # Otherwise, try to check for an SRV record, pointing us to the
    # port which we should use. If there's no such record, fall back
    # to the default_port (if it's defined).
    try:
        host, port = mc_dns.resolve_mc_srv(host, lifetime=lifetime)
    except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer) as e:
        if default_port is None:
            raise ValueError(
                f"Given address {address!r} doesn't contain port, doesn't have an SRV record pointing to a port,"
                " and default_port wasn't specified, can't parse."
            ) from e
        port = default_port

    return Address(host, port)


async def async_minecraft_srv_address_lookup(
    address: str,
    *,
    default_port: int | None = None,
    lifetime: float | None = None,
) -> Address:
    """Just an async alternative to :func:`.minecraft_srv_address_lookup`, check it for more details."""
    host, port = _valid_urlparse(address)

    # If we found a port in the address, there's nothing more we need
    if port is not None:
        return Address(host, port)

    # Otherwise, try to check for an SRV record, pointing us to the
    # port which we should use. If there's no such record, fall back
    # to the default_port (if it's defined).
    try:
        host, port = await mc_dns.async_resolve_mc_srv(host, lifetime=lifetime)
    except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer) as e:
        if default_port is None:
            raise ValueError(
                f"Given address {address!r} doesn't contain port, doesn't have an SRV record pointing to a port,"
                " and default_port wasn't specified, can't parse."
            ) from e
        port = default_port

    return Address(host, port)


================================================
FILE: mcstatus/_net/dns.py
================================================
from __future__ import annotations

from typing import TYPE_CHECKING, cast

import dns.asyncresolver
import dns.resolver
from dns.rdatatype import RdataType

if TYPE_CHECKING:
    from dns.rdtypes.IN.A import A as ARecordAnswer
    from dns.rdtypes.IN.SRV import SRV as SRVRecordAnswer  # noqa: N811 # constant imported as non constant (it's class)

__all__ = [
    "async_resolve_a_record",
    "async_resolve_mc_srv",
    "async_resolve_srv_record",
    "resolve_a_record",
    "resolve_mc_srv",
    "resolve_srv_record",
]


def resolve_a_record(hostname: str, lifetime: float | None = None) -> str:
    """Perform a DNS resolution for an A record to given hostname.

    :param hostname: The address to resolve for.
    :return: The resolved IP address from the A record
    :raises dns.exception.DNSException:
        One of the exceptions possibly raised by :func:`dns.resolver.resolve`.
        Most notably this will be :exc:`dns.exception.Timeout`, :exc:`dns.resolver.NXDOMAIN`
        and :exc:`dns.resolver.NoAnswer`
    """
    answers = dns.resolver.resolve(hostname, RdataType.A, lifetime=lifetime, search=True)
    # There should only be one answer here, though in case the server
    # does actually point to multiple IPs, we just pick the first one
    answer = cast("ARecordAnswer", answers[0])
    ip = str(answer).rstrip(".")
    return ip


async def async_resolve_a_record(hostname: str, lifetime: float | None = None) -> str:
    """Asynchronous alternative to :func:`.resolve_a_record`.

    For more details, check it.
    """
    answers = await dns.asyncresolver.resolve(hostname, RdataType.A, lifetime=lifetime, search=True)
    # There should only be one answer here, though in case the server
    # does actually point to multiple IPs, we just pick the first one
    answer = cast("ARecordAnswer", answers[0])
    ip = str(answer).rstrip(".")
    return ip


def resolve_srv_record(query_name: str, lifetime: float | None = None) -> tuple[str, int]:
    """Perform a DNS resolution for SRV record pointing to the Java Server.

    :param query_name: The address to resolve for.
    :return: A tuple of host string and port number
    :raises dns.exception.DNSException:
        One of the exceptions possibly raised by :func:`dns.resolver.resolve`.
        Most notably this will be :exc:`dns.exception.Timeout`, :exc:`dns.resolver.NXDOMAIN`
        and :exc:`dns.resolver.NoAnswer`
    """
    answers = dns.resolver.resolve(query_name, RdataType.SRV, lifetime=lifetime, search=True)
    # There should only be one answer here, though in case the server
    # does actually point to multiple IPs, we just pick the first one
    answer = cast("SRVRecordAnswer", answers[0])
    host = str(answer.target).rstrip(".")
    port = int(answer.port)
    return host, port


async def async_resolve_srv_record(query_name: str, lifetime: float | None = None) -> tuple[str, int]:
    """Asynchronous alternative to :func:`.resolve_srv_record`.

    For more details, check it.
    """
    answers = await dns.asyncresolver.resolve(query_name, RdataType.SRV, lifetime=lifetime, search=True)
    # There should only be one answer here, though in case the server
    # does actually point to multiple IPs, we just pick the first one
    answer = cast("SRVRecordAnswer", answers[0])
    host = str(answer.target).rstrip(".")
    port = int(answer.port)
    return host, port


def resolve_mc_srv(hostname: str, lifetime: float | None = None) -> tuple[str, int]:
    """Resolve SRV record for a minecraft server on given hostname.

    :param str hostname: The address, without port, on which an SRV record is present.
    :return: Obtained target and port from the SRV record, on which the server should live on.
    :raises dns.exception.DNSException:
        One of the exceptions possibly raised by :func:`dns.resolver.resolve`.
        Most notably this will be :exc:`dns.exception.Timeout`, :exc:`dns.resolver.NXDOMAIN`
        and :exc:`dns.resolver.NoAnswer`.
    """
    return resolve_srv_record("_minecraft._tcp." + hostname, lifetime=lifetime)


async def async_resolve_mc_srv(hostname: str, lifetime: float | None = None) -> tuple[str, int]:
    """Asynchronous alternative to :func:`.resolve_mc_srv`.

    For more details, check it.
    """
    return await async_resolve_srv_record("_minecraft._tcp." + hostname, lifetime=lifetime)


================================================
FILE: mcstatus/_protocol/__init__.py
================================================


================================================
FILE: mcstatus/_protocol/bedrock_client.py
================================================
from __future__ import annotations

import asyncio
import socket
import struct
from time import perf_counter
from typing import TYPE_CHECKING

import asyncio_dgram

from mcstatus.responses import BedrockStatusResponse

if TYPE_CHECKING:
    from mcstatus._net.address import Address

__all__ = ["BedrockClient"]


class BedrockClient:
    request_status_data = bytes.fromhex(
        # see https://minecraft.wiki/w/RakNet#Unconnected_Ping
        "01" + "0000000000000000" + "00ffff00fefefefefdfdfdfd12345678" + "0000000000000000"
    )

    def __init__(self, address: Address, timeout: float = 3) -> None:
        self.address = address
        self.timeout = timeout

    @staticmethod
    def parse_response(data: bytes, latency: float) -> BedrockStatusResponse:
        data = data[1:]
        name_length = struct.unpack(">H", data[32:34])[0]
        decoded_data = data[34 : 34 + name_length].decode().split(";")

        return BedrockStatusResponse.build(decoded_data, latency)

    def read_status(self) -> BedrockStatusResponse:
        start = perf_counter()
        data = self._read_status()
        end = perf_counter()
        return self.parse_response(data, (end - start) * 1000)

    def _read_status(self) -> bytes:
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.settimeout(self.timeout)

        s.sendto(self.request_status_data, self.address)
        data, _ = s.recvfrom(2048)

        return data

    async def read_status_async(self) -> BedrockStatusResponse:
        start = perf_counter()
        data = await self._read_status_async()
        end = perf_counter()

        return self.parse_response(data, (end - start) * 1000)

    async def _read_status_async(self) -> bytes:
        stream = None
        try:
            conn = asyncio_dgram.connect(self.address)
            stream = await asyncio.wait_for(conn, timeout=self.timeout)

            await asyncio.wait_for(stream.send(self.request_status_data), timeout=self.timeout)
            data, _ = await asyncio.wait_for(stream.recv(), timeout=self.timeout)
        finally:
            if stream is not None:
                stream.close()

        return data


================================================
FILE: mcstatus/_protocol/connection.py
================================================
from __future__ import annotations

import asyncio
import errno
import socket
import struct
from abc import ABC, abstractmethod
from ctypes import c_int32 as signed_int32, c_int64 as signed_int64, c_uint32 as unsigned_int32, c_uint64 as unsigned_int64
from ipaddress import ip_address
from typing import TYPE_CHECKING, TypeAlias, cast

import asyncio_dgram

if TYPE_CHECKING:
    from collections.abc import Iterable

    from typing_extensions import Self, SupportsIndex

    from mcstatus._net.address import Address

__all__ = [
    "BaseAsyncConnection",
    "BaseAsyncReadSyncWriteConnection",
    "BaseConnection",
    "BaseReadAsync",
    "BaseReadSync",
    "BaseSyncConnection",
    "BaseWriteAsync",
    "BaseWriteSync",
    "Connection",
    "SocketConnection",
    "TCPAsyncSocketConnection",
    "TCPSocketConnection",
    "UDPAsyncSocketConnection",
    "UDPSocketConnection",
]

BytesConvertable: TypeAlias = "SupportsIndex | Iterable[SupportsIndex]"


def _ip_type(address: int | str) -> int | None:
    """Determine the IP version (IPv4 or IPv6).

    :param address:
        A string or integer, the IP address. Either IPv4 or IPv6 addresses may be supplied.
        Integers less than 2**32 will be considered to be IPv4 by default.
    :return: ``4`` or ``6`` if the IP is IPv4 or IPv6, respectively. :obj:`None` if the IP is invalid.
    """
    try:
        return ip_address(address).version
    except ValueError:
        return None


class BaseWriteSync(ABC):
    """Base synchronous write class."""

    __slots__ = ()

    @abstractmethod
    def write(self, data: Connection | str | bytearray | bytes) -> None:
        """Write data to ``self``."""

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} Object>"

    @staticmethod
    def _pack(format_: str, data: int) -> bytes:
        """Pack data in with format in big-endian mode."""
        return struct.pack(">" + format_, data)

    def write_varint(self, value: int) -> None:
        """Write varint with value ``value`` to ``self``.

        :param value: Maximum is ``2 ** 31 - 1``, minimum is ``-(2 ** 31)``.
        :raises ValueError: If value is out of range.
        """
        remaining = unsigned_int32(value).value
        for _ in range(5):
            if not remaining & -0x80:  # remaining & ~0x7F == 0:
                self.write(struct.pack("!B", remaining))
                if value > 2**31 - 1 or value < -(2**31):
                    break
                return
            self.write(struct.pack("!B", remaining & 0x7F | 0x80))
            remaining >>= 7
        raise ValueError(f'The value "{value}" is too big to send in a varint')

    def write_varlong(self, value: int) -> None:
        """Write varlong with value ``value`` to ``self``.

        :param value: Maximum is ``2 ** 63 - 1``, minimum is ``-(2 ** 63)``.
        :raises ValueError: If value is out of range.
        """
        remaining = unsigned_int64(value).value
        for _ in range(10):
            if not remaining & -0x80:  # remaining & ~0x7F == 0:
                self.write(struct.pack("!B", remaining))
                if value > 2**63 - 1 or value < -(2**31):
                    break
                return
            self.write(struct.pack("!B", remaining & 0x7F | 0x80))
            remaining >>= 7
        raise ValueError(f'The value "{value}" is too big to send in a varlong')

    def write_utf(self, value: str) -> None:
        """Write varint of length of ``value`` up to 32767 bytes, then write ``value`` encoded with ``UTF-8``."""
        self.write_varint(len(value))
        self.write(bytearray(value, "utf8"))

    def write_ascii(self, value: str) -> None:
        """Write value encoded with ``ISO-8859-1``, then write an additional ``0x00`` at the end."""
        self.write(bytearray(value, "ISO-8859-1"))
        self.write(bytearray.fromhex("00"))

    def write_short(self, value: int) -> None:
        """Write 2 bytes for value ``-32768 - 32767``."""
        self.write(self._pack("h", value))

    def write_ushort(self, value: int) -> None:
        """Write 2 bytes for value ``0 - 65535 (2 ** 16 - 1)``."""
        self.write(self._pack("H", value))

    def write_int(self, value: int) -> None:
        """Write 4 bytes for value ``-2147483648 - 2147483647``."""
        self.write(self._pack("i", value))

    def write_uint(self, value: int) -> None:
        """Write 4 bytes for value ``0 - 4294967295 (2 ** 32 - 1)``."""
        self.write(self._pack("I", value))

    def write_long(self, value: int) -> None:
        """Write 8 bytes for value ``-9223372036854775808 - 9223372036854775807``."""
        self.write(self._pack("q", value))

    def write_ulong(self, value: int) -> None:
        """Write 8 bytes for value ``0 - 18446744073709551613 (2 ** 64 - 1)``."""
        self.write(self._pack("Q", value))

    def write_bool(self, value: bool) -> None:  # noqa: FBT001 # Boolean positional argument
        """Write 1 byte for boolean `True` or `False`."""
        self.write(self._pack("?", value))

    def write_buffer(self, buffer: Connection) -> None:
        """Flush buffer, then write a varint of the length of the buffer's data, then write buffer data."""
        data = buffer.flush()
        self.write_varint(len(data))
        self.write(data)


class BaseWriteAsync(ABC):
    """Base synchronous write class."""

    __slots__ = ()

    @abstractmethod
    async def write(self, data: Connection | str | bytearray | bytes) -> None:
        """Write data to ``self``."""

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} Object>"

    @staticmethod
    def _pack(format_: str, data: int) -> bytes:
        """Pack data in with format in big-endian mode."""
        return struct.pack(">" + format_, data)

    async def write_varint(self, value: int) -> None:
        """Write varint with value ``value`` to ``self``.

        :param value: Maximum is ``2 ** 31 - 1``, minimum is ``-(2 ** 31)``.
        :raises ValueError: If value is out of range.
        """
        remaining = unsigned_int32(value).value
        for _ in range(5):
            if not remaining & -0x80:  # remaining & ~0x7F == 0:
                await self.write(struct.pack("!B", remaining))
                if value > 2**31 - 1 or value < -(2**31):
                    break
                return
            await self.write(struct.pack("!B", remaining & 0x7F | 0x80))
            remaining >>= 7
        raise ValueError(f'The value "{value}" is too big to send in a varint')

    async def write_varlong(self, value: int) -> None:
        """Write varlong with value ``value`` to ``self``.

        :param value: Maximum is ``2 ** 63 - 1``, minimum is ``-(2 ** 63)``.
        :raises ValueError: If value is out of range.
        """
        remaining = unsigned_int64(value).value
        for _ in range(10):
            if not remaining & -0x80:  # remaining & ~0x7F == 0:
                await self.write(struct.pack("!B", remaining))
                if value > 2**63 - 1 or value < -(2**31):
                    break
                return
            await self.write(struct.pack("!B", remaining & 0x7F | 0x80))
            remaining >>= 7
        raise ValueError(f'The value "{value}" is too big to send in a varlong')

    async def write_utf(self, value: str) -> None:
        """Write varint of length of ``value`` up to 32767 bytes, then write ``value`` encoded with ``UTF-8``."""
        await self.write_varint(len(value))
        await self.write(bytearray(value, "utf8"))

    async def write_ascii(self, value: str) -> None:
        """Write value encoded with ``ISO-8859-1``, then write an additional ``0x00`` at the end."""
        await self.write(bytearray(value, "ISO-8859-1"))
        await self.write(bytearray.fromhex("00"))

    async def write_short(self, value: int) -> None:
        """Write 2 bytes for value ``-32768 - 32767``."""
        await self.write(self._pack("h", value))

    async def write_ushort(self, value: int) -> None:
        """Write 2 bytes for value ``0 - 65535 (2 ** 16 - 1)``."""
        await self.write(self._pack("H", value))

    async def write_int(self, value: int) -> None:
        """Write 4 bytes for value ``-2147483648 - 2147483647``."""
        await self.write(self._pack("i", value))

    async def write_uint(self, value: int) -> None:
        """Write 4 bytes for value ``0 - 4294967295 (2 ** 32 - 1)``."""
        await self.write(self._pack("I", value))

    async def write_long(self, value: int) -> None:
        """Write 8 bytes for value ``-9223372036854775808 - 9223372036854775807``."""
        await self.write(self._pack("q", value))

    async def write_ulong(self, value: int) -> None:
        """Write 8 bytes for value ``0 - 18446744073709551613 (2 ** 64 - 1)``."""
        await self.write(self._pack("Q", value))

    async def write_bool(self, value: bool) -> None:  # noqa: FBT001 # Boolean positional argument
        """Write 1 byte for boolean `True` or `False`."""
        await self.write(self._pack("?", value))

    async def write_buffer(self, buffer: Connection) -> None:
        """Flush buffer, then write a varint of the length of the buffer's data, then write buffer data."""
        data = buffer.flush()
        await self.write_varint(len(data))
        await self.write(data)


class BaseReadSync(ABC):
    """Base synchronous read class."""

    __slots__ = ()

    @abstractmethod
    def read(self, length: int, /) -> bytearray:
        """Read length bytes from ``self``, and return a byte array."""

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} Object>"

    @staticmethod
    def _unpack(format_: str, data: bytes) -> int:
        """Unpack data as bytes with format in big-endian."""
        return struct.unpack(">" + format_, bytes(data))[0]

    def read_varint(self) -> int:
        """Read varint from ``self`` and return it.

        :param value: Maximum is ``2 ** 31 - 1``, minimum is ``-(2 ** 31)``.
        :raises IOError: If varint received is out of range.
        """
        result = 0
        for i in range(5):
            part = self.read(1)[0]
            result |= (part & 0x7F) << (7 * i)
            if not part & 0x80:
                return signed_int32(result).value
        raise OSError("Received varint is too big!")

    def read_varlong(self) -> int:
        """Read varlong from ``self`` and return it.

        :param value: Maximum is ``2 ** 63 - 1``, minimum is ``-(2 ** 63)``.
        :raises IOError: If varint received is out of range.
        """
        result = 0
        for i in range(10):
            part = self.read(1)[0]
            result |= (part & 0x7F) << (7 * i)
            if not part & 0x80:
                return signed_int64(result).value
        raise OSError("Received varlong is too big!")

    def read_utf(self) -> str:
        """Read up to 32767 bytes by reading a varint, then decode bytes as ``UTF-8``."""
        length = self.read_varint()
        return self.read(length).decode("utf8")

    def read_ascii(self) -> str:
        """Read ``self`` until last value is not zero, then return that decoded with ``ISO-8859-1``."""
        result = bytearray()
        while len(result) == 0 or result[-1] != 0:
            result.extend(self.read(1))
        return result[:-1].decode("ISO-8859-1")

    def read_short(self) -> int:
        """Return ``-32768 - 32767``. Read 2 bytes."""
        return self._unpack("h", self.read(2))

    def read_ushort(self) -> int:
        """Return ``0 - 65535 (2 ** 16 - 1)``. Read 2 bytes."""
        return self._unpack("H", self.read(2))

    def read_int(self) -> int:
        """Return ``-2147483648 - 2147483647``. Read 4 bytes."""
        return self._unpack("i", self.read(4))

    def read_uint(self) -> int:
        """Return ``0 - 4294967295 (2 ** 32 - 1)``. 4 bytes read."""
        return self._unpack("I", self.read(4))

    def read_long(self) -> int:
        """Return ``-9223372036854775808 - 9223372036854775807``. Read 8 bytes."""
        return self._unpack("q", self.read(8))

    def read_ulong(self) -> int:
        """Return ``0 - 18446744073709551613 (2 ** 64 - 1)``. Read 8 bytes."""
        return self._unpack("Q", self.read(8))

    def read_bool(self) -> bool:
        """Return `True` or `False`. Read 1 byte."""
        return cast("bool", self._unpack("?", self.read(1)))

    def read_buffer(self) -> Connection:
        """Read a varint for length, then return a new connection from length read bytes."""
        length = self.read_varint()
        result = Connection()
        result.receive(self.read(length))
        return result


class BaseReadAsync(ABC):
    """Asynchronous Read connection base class."""

    __slots__ = ()

    @abstractmethod
    async def read(self, length: int, /) -> bytearray:
        """Read length bytes from ``self``, return a byte array."""

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} Object>"

    @staticmethod
    def _unpack(format_: str, data: bytes) -> int:
        """Unpack data as bytes with format in big-endian."""
        return struct.unpack(">" + format_, bytes(data))[0]

    async def read_varint(self) -> int:
        """Read varint from ``self`` and return it.

        :param value: Maximum is ``2 ** 31 - 1``, minimum is ``-(2 ** 31)``.
        :raises IOError: If varint received is out of range.
        """
        result = 0
        for i in range(5):
            part = (await self.read(1))[0]
            result |= (part & 0x7F) << 7 * i
            if not part & 0x80:
                return signed_int32(result).value
        raise OSError("Received a varint that was too big!")

    async def read_varlong(self) -> int:
        """Read varlong from ``self`` and return it.

        :param value: Maximum is ``2 ** 63 - 1``, minimum is ``-(2 ** 63)``.
        :raises IOError: If varint received is out of range.
        """
        result = 0
        for i in range(10):
            part = (await self.read(1))[0]
            result |= (part & 0x7F) << (7 * i)
            if not part & 0x80:
                return signed_int64(result).value
        raise OSError("Received varlong is too big!")

    async def read_utf(self) -> str:
        """Read up to 32767 bytes by reading a varint, then decode bytes as ``UTF-8``."""
        length = await self.read_varint()
        return (await self.read(length)).decode("utf8")

    async def read_ascii(self) -> str:
        """Read ``self`` until last value is not zero, then return that decoded with ``ISO-8859-1``."""
        result = bytearray()
        while len(result) == 0 or result[-1] != 0:
            result.extend(await self.read(1))
        return result[:-1].decode("ISO-8859-1")

    async def read_short(self) -> int:
        """Return ``-32768 - 32767``. Read 2 bytes."""
        return self._unpack("h", await self.read(2))

    async def read_ushort(self) -> int:
        """Return ``0 - 65535 (2 ** 16 - 1)``. Read 2 bytes."""
        return self._unpack("H", await self.read(2))

    async def read_int(self) -> int:
        """Return ``-2147483648 - 2147483647``. Read 4 bytes."""
        return self._unpack("i", await self.read(4))

    async def read_uint(self) -> int:
        """Return ``0 - 4294967295 (2 ** 32 - 1)``. 4 bytes read."""
        return self._unpack("I", await self.read(4))

    async def read_long(self) -> int:
        """Return ``-9223372036854775808 - 9223372036854775807``. Read 8 bytes."""
        return self._unpack("q", await self.read(8))

    async def read_ulong(self) -> int:
        """Return ``0 - 18446744073709551613 (2 ** 64 - 1)``. Read 8 bytes."""
        return self._unpack("Q", await self.read(8))

    async def read_bool(self) -> bool:
        """Return `True` or `False`. Read 1 byte."""
        return cast("bool", self._unpack("?", await self.read(1)))

    async def read_buffer(self) -> Connection:
        """Read a varint for length, then return a new connection from length read bytes."""
        length = await self.read_varint()
        result = Connection()
        result.receive(await self.read(length))
        return result


class BaseConnection:
    """Base Connection class. Implements flush, receive, and remaining."""

    __slots__ = ()

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} Object>"

    def flush(self) -> bytearray:
        """Raise :exc:`TypeError`, unsupported."""
        raise TypeError(f"{self.__class__.__name__} does not support flush()")

    def receive(self, _data: BytesConvertable | bytearray) -> None:
        """Raise :exc:`TypeError`, unsupported."""
        raise TypeError(f"{self.__class__.__name__} does not support receive()")

    def remaining(self) -> int:
        """Raise :exc:`TypeError`, unsupported."""
        raise TypeError(f"{self.__class__.__name__} does not support remaining()")


class BaseSyncConnection(BaseConnection, BaseReadSync, BaseWriteSync):
    """Base synchronous read and write class."""

    __slots__ = ()


class BaseAsyncReadSyncWriteConnection(BaseConnection, BaseReadAsync, BaseWriteSync):
    """Base asynchronous read and synchronous write class."""

    __slots__ = ()


class BaseAsyncConnection(BaseConnection, BaseReadAsync, BaseWriteAsync):
    """Base asynchronous read and write class."""

    __slots__ = ()


class Connection(BaseSyncConnection):
    """Base connection class."""

    __slots__ = ("received", "sent")

    def __init__(self) -> None:
        self.sent = bytearray()
        self.received = bytearray()

    def read(self, length: int, /) -> bytearray:
        """Return :attr:`.received` up to length bytes, then cut received up to that point."""
        if len(self.received) < length:
            raise OSError(f"Not enough data to read! {len(self.received)} < {length}")

        result = self.received[:length]
        self.received = self.received[length:]
        return result

    def write(self, data: Connection | str | bytearray | bytes) -> None:
        """Extend :attr:`.sent` from ``data``."""
        if isinstance(data, Connection):
            data = data.flush()
        if isinstance(data, str):
            data = bytearray(data, "utf-8")
        self.sent.extend(data)

    def receive(self, data: BytesConvertable | bytearray) -> None:
        """Extend :attr:`.received` with ``data``."""
        if not isinstance(data, bytearray):
            data = bytearray(data)
        self.received.extend(data)

    def remaining(self) -> int:
        """Return length of :attr:`.received`."""
        return len(self.received)

    def flush(self) -> bytearray:
        """Return :attr:`.sent`, also clears :attr:`.sent`."""
        result, self.sent = self.sent, bytearray()
        return result

    def copy(self) -> Connection:
        """Return a copy of ``self``."""
        new = self.__class__()
        new.receive(self.received)
        new.write(self.sent)
        return new


class SocketConnection(BaseSyncConnection):
    """Socket connection."""

    __slots__ = ("socket",)

    def __init__(self) -> None:
        # These will only be None until connect is called, ignore the None type assignment
        self.socket: socket.socket = None  # pyright: ignore[reportAttributeAccessIssue]

    def close(self) -> None:
        """Close :attr:`.socket`."""
        if self.socket is not None:  # If initialized
            try:
                self.socket.shutdown(socket.SHUT_RDWR)
            except OSError as exception:  # Socket wasn't connected (nothing to shut down)
                if exception.errno != errno.ENOTCONN:
                    raise

            self.socket.close()

    def __enter__(self) -> Self:
        return self

    def __exit__(self, *_: object) -> None:
        self.close()


class TCPSocketConnection(SocketConnection):
    """TCP Connection to address. Timeout defaults to 3 seconds."""

    __slots__ = ()

    def __init__(self, addr: tuple[str | None, int], timeout: float = 3) -> None:
        super().__init__()
        self.socket = socket.create_connection(addr, timeout=timeout)
        self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)

    def read(self, length: int, /) -> bytearray:
        """Return length bytes read from :attr:`.socket`. Raises :exc:`IOError` when server doesn't respond."""
        result = bytearray()
        while len(result) < length:
            new = self.socket.recv(length - len(result))
            if len(new) == 0:
                raise OSError("Server did not respond with any information!")
            result.extend(new)
        return result

    def write(self, data: Connection | str | bytes | bytearray) -> None:
        """Send data on :attr:`.socket`."""
        if isinstance(data, Connection):
            data = bytearray(data.flush())
        elif isinstance(data, str):
            data = bytearray(data, "utf-8")
        self.socket.send(data)


class UDPSocketConnection(SocketConnection):
    """UDP Connection class."""

    __slots__ = ("addr",)

    def __init__(self, addr: Address, timeout: float = 3) -> None:
        super().__init__()
        self.addr = addr
        self.socket = socket.socket(
            socket.AF_INET if _ip_type(addr[0]) == 4 else socket.AF_INET6,
            socket.SOCK_DGRAM,
        )
        self.socket.settimeout(timeout)

    def remaining(self) -> int:
        """Always return ``65535`` (``2 ** 16 - 1``)."""  # noqa: D401 # imperative mood
        return 65535

    def read(self, _length: int, /) -> bytearray:
        """Return up to :meth:`.remaining` bytes. Length does nothing here."""
        result = bytearray()
        while len(result) == 0:
            result.extend(self.socket.recvfrom(self.remaining())[0])
        return result

    def write(self, data: Connection | str | bytes | bytearray) -> None:
        """Use :attr:`.socket` to send data to :attr:`.addr`."""
        if isinstance(data, Connection):
            data = bytearray(data.flush())
        elif isinstance(data, str):
            data = bytearray(data, "utf-8")
        self.socket.sendto(data, self.addr)


class TCPAsyncSocketConnection(BaseAsyncReadSyncWriteConnection):
    """Asynchronous TCP Connection class."""

    __slots__ = ("_addr", "reader", "timeout", "writer")

    def __init__(self, addr: Address, timeout: float = 3) -> None:
        # These will only be None until connect is called, ignore the None type assignment
        self.reader: asyncio.StreamReader = None  # pyright: ignore[reportAttributeAccessIssue]
        self.writer: asyncio.StreamWriter = None  # pyright: ignore[reportAttributeAccessIssue]
        self.timeout: float = timeout
        self._addr = addr

    async def connect(self) -> None:
        """Use :mod:`asyncio` to open a connection to address. Timeout is in seconds."""
        conn = asyncio.open_connection(*self._addr)
        self.reader, self.writer = await asyncio.wait_for(conn, timeout=self.timeout)
        if self.writer is not None:  # it might be None in unittest
            sock: socket.socket = self.writer.transport.get_extra_info("socket")
            sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)

    async def read(self, length: int, /) -> bytearray:
        """Read up to ``length`` bytes from :attr:`.reader`."""
        result = bytearray()
        while len(result) < length:
            new = await asyncio.wait_for(self.reader.read(length - len(result)), timeout=self.timeout)
            if len(new) == 0:
                raise OSError("Socket did not respond with any information!")
            result.extend(new)
        return result

    def write(self, data: Connection | str | bytes | bytearray) -> None:
        """Write data to :attr:`.writer`."""
        if isinstance(data, Connection):
            data = bytearray(data.flush())
        elif isinstance(data, str):
            data = bytearray(data, "utf-8")
        self.writer.write(data)

    def close(self) -> None:
        """Close :attr:`.writer`."""
        if self.writer is not None:  # If initialized
            self.writer.close()

    async def __aenter__(self) -> Self:
        await self.connect()
        return self

    async def __aexit__(self, *_: object) -> None:
        self.close()


class UDPAsyncSocketConnection(BaseAsyncConnection):
    """Asynchronous UDP Connection class."""

    __slots__ = ("_addr", "stream", "timeout")

    def __init__(self, addr: Address, timeout: float = 3) -> None:
        # This will only be None until connect is called, ignore the None type assignment
        self.stream: asyncio_dgram.aio.DatagramClient = None  # pyright: ignore[reportAttributeAccessIssue]
        self.timeout: float = timeout
        self._addr = addr

    async def connect(self) -> None:
        """Connect to address. Timeout is in seconds."""
        conn = asyncio_dgram.connect(self._addr)
        self.stream = await asyncio.wait_for(conn, timeout=self.timeout)

    def remaining(self) -> int:
        """Always return ``65535`` (``2 ** 16 - 1``)."""  # noqa: D401 # imperative mood
        return 65535

    async def read(self, _length: int, /) -> bytearray:
        """Read from :attr:`.stream`. Length does nothing here."""
        data, _remote_addr = await asyncio.wait_for(self.stream.recv(), timeout=self.timeout)
        return bytearray(data)

    async def write(self, data: Connection | str | bytes | bytearray) -> None:
        """Send data with :attr:`.stream`."""
        if isinstance(data, Connection):
            data = bytearray(data.flush())
        elif isinstance(data, str):
            data = bytearray(data, "utf-8")
        await self.stream.send(data)

    def close(self) -> None:
        """Close :attr:`.stream`."""
        if self.stream is not None:  # If initialized
            self.stream.close()

    async def __aenter__(self) -> Self:
        await self.connect()
        return self

    async def __aexit__(self, *_: object) -> None:
        self.close()


================================================
FILE: mcstatus/_protocol/java_client.py
================================================
from __future__ import annotations

import json
import random
from abc import ABC, abstractmethod
from dataclasses import dataclass
from time import perf_counter
from typing import TYPE_CHECKING, final

from mcstatus._protocol.connection import Connection, TCPAsyncSocketConnection, TCPSocketConnection
from mcstatus.responses import JavaStatusResponse

if TYPE_CHECKING:
    from collections.abc import Awaitable

    from mcstatus._net.address import Address
    from mcstatus.responses._raw import RawJavaResponse

__all__ = ["AsyncJavaClient", "JavaClient"]


@dataclass
class _BaseJavaClient(ABC):
    connection: TCPSocketConnection | TCPAsyncSocketConnection
    address: Address
    version: int
    """Version of the client."""
    ping_token: int = None  # pyright: ignore[reportAssignmentType]
    """Token that is used for the request, default is random number."""

    def __post_init__(self) -> None:
        if self.ping_token is None:
            self.ping_token = random.randint(0, (1 << 63) - 1)

    def handshake(self) -> None:
        """Write the initial handshake packet to the connection."""
        packet = Connection()
        packet.write_varint(0)
        packet.write_varint(self.version)
        packet.write_utf(self.address.host)
        packet.write_ushort(self.address.port)
        packet.write_varint(1)  # Intention to query status

        self.connection.write_buffer(packet)

    @abstractmethod
    def read_status(self) -> JavaStatusResponse | Awaitable[JavaStatusResponse]:
        """Make a status request and parse the response."""
        raise NotImplementedError

    @abstractmethod
    def test_ping(self) -> float | Awaitable[float]:
        """Send a ping token and measure the latency."""
        raise NotImplementedError

    def _handle_status_response(self, response: Connection, start: float, end: float) -> JavaStatusResponse:
        """Given a response buffer (already read from connection), parse and build the JavaStatusResponse."""
        if response.read_varint() != 0:
            raise OSError("Received invalid status response packet.")
        try:
            raw: RawJavaResponse = json.loads(response.read_utf())
        except ValueError as e:
            raise OSError("Received invalid JSON") from e

        try:
            latency_ms = (end - start) * 1000
            return JavaStatusResponse.build(raw, latency=latency_ms)
        except KeyError as e:
            raise OSError("Received invalid status response") from e

    def _handle_ping_response(self, response: Connection, start: float, end: float) -> float:
        """Given a ping response buffer, validate token and compute latency."""
        if response.read_varint() != 1:
            raise OSError("Received invalid ping response packet.")
        received_token = response.read_long()
        if received_token != self.ping_token:
            raise OSError(f"Received mangled ping response (expected token {self.ping_token}, got {received_token})")
        return (end - start) * 1000


@final
@dataclass
class JavaClient(_BaseJavaClient):
    connection: TCPSocketConnection  # pyright: ignore[reportIncompatibleVariableOverride]

    def read_status(self) -> JavaStatusResponse:
        """Send the status request and read the response."""
        request = Connection()
        request.write_varint(0)  # Request status
        self.connection.write_buffer(request)

        start = perf_counter()
        response = self.connection.read_buffer()
        end = perf_counter()
        return self._handle_status_response(response, start, end)

    def test_ping(self) -> float:
        """Send a ping token and measure the latency."""
        request = Connection()
        request.write_varint(1)  # Test ping
        request.write_long(self.ping_token)
        start = perf_counter()
        self.connection.write_buffer(request)

        response = self.connection.read_buffer()
        end = perf_counter()
        return self._handle_ping_response(response, start, end)


@final
@dataclass
class AsyncJavaClient(_BaseJavaClient):
    connection: TCPAsyncSocketConnection  # pyright: ignore[reportIncompatibleVariableOverride]

    async def read_status(self) -> JavaStatusResponse:
        """Send the status request and read the response."""
        request = Connection()
        request.write_varint(0)  # Request status
        self.connection.write_buffer(request)

        start = perf_counter()
        response = await self.connection.read_buffer()
        end = perf_counter()
        return self._handle_status_response(response, start, end)

    async def test_ping(self) -> float:
        """Send a ping token and measure the latency."""
        request = Connection()
        request.write_varint(1)  # Test ping
        request.write_long(self.ping_token)
        start = perf_counter()
        self.connection.write_buffer(request)

        response = await self.connection.read_buffer()
        end = perf_counter()
        return self._handle_ping_response(response, start, end)


================================================
FILE: mcstatus/_protocol/legacy_client.py
================================================
from time import perf_counter

from mcstatus._protocol.connection import BaseAsyncReadSyncWriteConnection, BaseSyncConnection
from mcstatus.responses import LegacyStatusResponse

__all__ = ["AsyncLegacyClient", "LegacyClient"]


class _BaseLegacyClient:
    request_status_data = bytes.fromhex(
        # see https://minecraft.wiki/w/Java_Edition_protocol/Server_List_Ping#Client_to_server
        "fe01fa"
    )

    @staticmethod
    def parse_response(data: bytes, latency: float) -> LegacyStatusResponse:
        decoded_data: list[str] = data.decode("UTF-16BE").split("\0")
        if decoded_data[0] != "§1":
            # kick packets before 1.4 (12w42a) did not start with §1 and did
            # not included information about server and protocol version
            decoded_data = ["§1", "-1", "<1.4", *decoded_data[0].split("§")]
            if len(decoded_data) != 6:
                raise OSError("Received invalid kick packet reason")
        return LegacyStatusResponse.build(decoded_data[1:], latency)


class LegacyClient(_BaseLegacyClient):
    def __init__(self, connection: BaseSyncConnection) -> None:
        self.connection = connection

    def read_status(self) -> LegacyStatusResponse:
        """Send the status request and read the response."""
        start = perf_counter()
        self.connection.write(self.request_status_data)
        id = self.connection.read(1)
        if id != b"\xff":
            raise OSError("Received invalid packet ID")
        length = self.connection.read_ushort()
        data = self.connection.read(length * 2)
        end = perf_counter()
        return self.parse_response(data, (end - start) * 1000)


class AsyncLegacyClient(_BaseLegacyClient):
    def __init__(self, connection: BaseAsyncReadSyncWriteConnection) -> None:
        self.connection = connection

    async def read_status(self) -> LegacyStatusResponse:
        """Send the status request and read the response."""
        start = perf_counter()
        self.connection.write(self.request_status_data)
        id = await self.connection.read(1)
        if id != b"\xff":
            raise OSError("Received invalid packet ID")
        length = await self.connection.read_ushort()
        data = await self.connection.read(length * 2)
        end = perf_counter()
        return self.parse_response(data, (end - start) * 1000)


================================================
FILE: mcstatus/_protocol/query_client.py
================================================
from __future__ import annotations

import random
import re
import struct
from abc import abstractmethod
from dataclasses import dataclass, field
from typing import ClassVar, TYPE_CHECKING, final

from mcstatus._protocol.connection import Connection, UDPAsyncSocketConnection, UDPSocketConnection
from mcstatus.responses import QueryResponse
from mcstatus.responses._raw import RawQueryResponse

__all__ = ["AsyncQueryClient", "QueryClient"]

if TYPE_CHECKING:
    from collections.abc import Awaitable


@dataclass
class _BaseQueryClient:
    MAGIC_PREFIX: ClassVar = bytearray.fromhex("FEFD")
    PADDING: ClassVar = bytearray.fromhex("00000000")
    PACKET_TYPE_CHALLENGE: ClassVar = 9
    PACKET_TYPE_QUERY: ClassVar = 0

    connection: UDPSocketConnection | UDPAsyncSocketConnection
    challenge: int = field(init=False, default=0)

    @staticmethod
    def _generate_session_id() -> int:
        # minecraft only supports lower 4 bits
        return random.randint(0, 2**31) & 0x0F0F0F0F

    def _create_packet(self) -> Connection:
        packet = Connection()
        packet.write(self.MAGIC_PREFIX)
        packet.write(struct.pack("!B", self.PACKET_TYPE_QUERY))
        packet.write_uint(self._generate_session_id())
        packet.write_int(self.challenge)
        packet.write(self.PADDING)
        return packet

    def _create_handshake_packet(self) -> Connection:
        packet = Connection()
        packet.write(self.MAGIC_PREFIX)
        packet.write(struct.pack("!B", self.PACKET_TYPE_CHALLENGE))
        packet.write_uint(self._generate_session_id())
        return packet

    @abstractmethod
    def _read_packet(self) -> Connection | Awaitable[Connection]:
        raise NotImplementedError

    @abstractmethod
    def handshake(self) -> None | Awaitable[None]:
        raise NotImplementedError

    @abstractmethod
    def read_query(self) -> QueryResponse | Awaitable[QueryResponse]:
        raise NotImplementedError

    def _parse_response(self, response: Connection) -> tuple[RawQueryResponse, list[str]]:
        """Transform the connection object (the result) into dict which is passed to the QueryResponse constructor.

        :return: A tuple with two elements. First is `raw` answer and second is list of players.
        """
        response.read(len("splitnum") + 3)
        data = {}

        while True:
            key = response.read_ascii()
            if key == "hostname":  # hostname is actually motd in the query protocol
                match = re.search(
                    b"(.*?)\x00(hostip|hostport|game_id|gametype|map|maxplayers|numplayers|plugins|version)",
                    response.received,
                    flags=re.DOTALL,
                )
                motd = match.group(1) if match else ""
                # Since the query protocol does not properly support unicode, the motd is still not resolved
                # correctly; however, this will avoid other parameter parsing errors.
                data[key] = response.read(len(motd)).decode("ISO-8859-1")
                response.read(1)  # ignore null byte
            elif len(key) == 0:
                response.read(1)
                break
            else:
                value = response.read_ascii()
                data[key] = value

        response.read(len("player_") + 2)

        players_list = []
        while True:
            player = response.read_ascii()
            if len(player) == 0:
                break
            players_list.append(player)

        return RawQueryResponse(**data), players_list


@final
@dataclass
class QueryClient(_BaseQueryClient):
    connection: UDPSocketConnection  # pyright: ignore[reportIncompatibleVariableOverride]

    def _read_packet(self) -> Connection:
        packet = Connection()
        packet.receive(self.connection.read(self.connection.remaining()))
        packet.read(1 + 4)
        return packet

    def handshake(self) -> None:
        self.connection.write(self._create_handshake_packet())

        packet = self._read_packet()
        self.challenge = int(packet.read_ascii())

    def read_query(self) -> QueryResponse:
        request = self._create_packet()
        self.connection.write(request)

        response = self._read_packet()
        return QueryResponse.build(*self._parse_response(response))


@final
@dataclass
class AsyncQueryClient(_BaseQueryClient):
    connection: UDPAsyncSocketConnection  # pyright: ignore[reportIncompatibleVariableOverride]

    async def _read_packet(self) -> Connection:
        packet = Connection()
        packet.receive(await self.connection.read(self.connection.remaining()))
        packet.read(1 + 4)
        return packet

    async def handshake(self) -> None:
        await self.connection.write(self._create_handshake_packet())

        packet = await self._read_packet()
        self.challenge = int(packet.read_ascii())

    async def read_query(self) -> QueryResponse:
        request = self._create_packet()
        await self.connection.write(request)

        response = await self._read_packet()
        return QueryResponse.build(*self._parse_response(response))


================================================
FILE: mcstatus/_utils/__init__.py
================================================
from mcstatus._utils.deprecation import deprecated, deprecation_warn
from mcstatus._utils.general import or_none
from mcstatus._utils.retry import retry

__all__ = ["deprecated", "deprecation_warn", "or_none", "retry"]


================================================
FILE: mcstatus/_utils/deprecation.py
================================================
from __future__ import annotations

import functools
import importlib.metadata
import re
import warnings
from functools import wraps
from typing import ParamSpec, Protocol, TYPE_CHECKING, TypeVar

if TYPE_CHECKING:
    from collections.abc import Callable


__all__ = ["deprecated", "deprecation_warn"]

LIB_NAME = "mcstatus"

# This comes from the python packaging docs (PEP 440 compliant versioning):
# https://packaging.python.org/en/latest/specifications/version-specifiers/#appendix-parsing-version-strings-with-regular-expressions
VERSION_PATTERN_FULL = re.compile(
    r"""^\s*
    v?
    (?:
        (?:(?P<epoch>[0-9]+)!)?                           # epoch
        (?P<release>[0-9]+(?:\.[0-9]+)*)                  # release segment
        (?P<pre>                                          # pre-release
            [-_\.]?
            (?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
            [-_\.]?
            (?P<pre_n>[0-9]+)?
        )?
        (?P<post>                                         # post release
            (?:-(?P<post_n1>[0-9]+))
            |
            (?:
                [-_\.]?
                (?P<post_l>post|rev|r)
                [-_\.]?
                (?P<post_n2>[0-9]+)?
            )
        )?
        (?P<dev>                                          # dev release
            [-_\.]?
            (?P<dev_l>dev)
            [-_\.]?
            (?P<dev_n>[0-9]+)?
        )?
    )
    (?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version
    \s*$""",
    re.VERBOSE | re.IGNORECASE,
)
# Intentionally restricted to X.Y.Z, unlike PEP 440 release segments.
# Used only for user-supplied removal_version values parsing.
REMOVAL_VERSION_RE = re.compile(r"(\d+)\.(\d+)\.(\d+)")
DEPRECATED_DIRECTIVE_RE = re.compile(r"^\s*\.\.\s+deprecated::\s*", flags=re.MULTILINE)

P = ParamSpec("P")
R = TypeVar("R")


@functools.lru_cache(maxsize=1)
def _get_project_version() -> tuple[int, int, int]:
    """Return the installed project version normalized to a 3-part release tuple.

    The project version is obtained from :mod:`importlib.metadata` and parsed using the official PEP 440
    version parsing regular expression.

    All non-release components of the version (pre-releases, post-releases, development releases, and local
    version identifiers) are intentionally ignored. The release version segment of the version is then
    normalized to 3 components, padding with zeros if the actual version has less components, or truncating
    if it has more. Any performed normalizing will emit a :exc:`RuntimeWarning`.

    If the project version cannot be determined or parsed, ``(0, 0, 0)`` is returned and a runtime warning
    is emitted.
    """
    try:
        _project_version = importlib.metadata.version(LIB_NAME)
    except importlib.metadata.PackageNotFoundError:
        # v0.0.0 will never mark things as already deprecated (removal_version will always be newer)
        warnings.warn(f"Failed to get {LIB_NAME} project version, assuming v0.0.0", category=RuntimeWarning, stacklevel=1)
        return (0, 0, 0)

    m = VERSION_PATTERN_FULL.fullmatch(_project_version)
    if m is None:
        # This should never happen
        warnings.warn(
            f"Failed to parse {LIB_NAME} project version ({_project_version}), assuming v0.0.0",
            category=RuntimeWarning,
            stacklevel=1,
        )
        return (0, 0, 0)

    if m["epoch"] is not None:
        # we're not using epoch, and we don't expect to start doing so. If we do, the rest of this
        # implementation would likely need to be changed anyways. Generally, this should never happen.
        warnings.warn(f"Failed to parse {LIB_NAME} project version, assuming v0.0.0", category=RuntimeWarning, stacklevel=1)
        return (0, 0, 0)

    release = m["release"]
    nums = [int(p) for p in release.split(".")]

    if len(nums) < 3:
        warnings.warn(
            f"{LIB_NAME} version '{release}' has less than 3 release components; remaining components will become zeroes",
            category=RuntimeWarning,
            stacklevel=2,
        )
        nums.extend([0] * (3 - len(nums)))
    elif len(nums) > 3:
        warnings.warn(
            f"{LIB_NAME} version '{release}' has more than 3 release components; extra components are ignored",
            category=RuntimeWarning,
            stacklevel=2,
        )
        nums = nums[:3]

    return nums[0], nums[1], nums[2]


def deprecation_warn(
    *,
    obj_name: str,
    removal_version: str | tuple[int, int, int],
    replacement: str | None = None,
    extra_msg: str | None = None,
    stack_level: int = 2,
) -> None:
    """Produce an appropriate deprecation warning given the parameters.

    If the currently installed project version is already past the specified deprecation version,
    a :exc:`DeprecationWarning` will be raised as a full exception. Otherwise it will just get
    emitted as a warning.

    The deprecation message used will be constructed dynamically based on the input parameters.

    :param obj_name: Name of the object that got deprecated (such as ``my_function``).
    :param removal_version: Version at which this object should be considered as deprecated and should no longer be used.
    :param replacement: A new alternative to this (now deprecated) object.
    :param extra_msg: Additional message included in the deprecation warning/exception at the end.
    :param stack_level: Stack level at which the warning is emitted.

    .. note:
        If the project version contains any additional qualifiers (e.g. pre-release, post-release, dev/local versions),
        they will be ignored and the project version will be treated a simple stable (major, minor, micro) version.
    """
    if isinstance(removal_version, str):
        if m := REMOVAL_VERSION_RE.fullmatch(removal_version):
            removal_version = (int(m[1]), int(m[2]), int(m[3]))
        else:
            raise ValueError(f"removal_version must follow regex pattern of: {REMOVAL_VERSION_RE.pattern}")

    project_version = _get_project_version()
    already_deprecated = project_version >= removal_version

    msg = f"{obj_name}"
    removal_version_str = ".".join(str(num) for num in removal_version)
    if already_deprecated:
        msg += f" is passed its removal version ({removal_version_str})"
    else:
        msg += f" is deprecated and scheduled for removal in {removal_version_str}"

    if replacement is not None:
        msg += f", use {replacement} instead"

    msg += "."
    if extra_msg is not None:
        msg += f" ({extra_msg})"

    if already_deprecated:
        raise DeprecationWarning(msg)

    warnings.warn(msg, category=DeprecationWarning, stacklevel=stack_level)


class DecoratorFunction(Protocol):
    def __call__(self, /, func: Callable[P, R]) -> Callable[P, R]: ...


def deprecated(
    *,
    removal_version: str | tuple[int, int, int],
    display_name: str | None = None,
    replacement: str | None = None,
    extra_msg: str | None = None,
    no_docstring_check: bool = False,
) -> DecoratorFunction:
    """Mark an object as deprecated.

    Decorator version of :func:`deprecation_warn` function.

    If the currently installed project version is already past the specified deprecation version,
    a :exc:`DeprecationWarning` will be raised as a full exception. Otherwise it will just get
    emitted as a warning.

    The deprecation message used will be constructed based on the input parameters.

    :param display_name:
            Name of the object that got deprecated (such as `my_function`).

            By default, the object name is obtained automatically from ``__qualname__`` (falling back
            to ``__name__``) of the decorated object. Setting this explicitly will override this obtained
            name and the `display_name` will be used instead.
    :param removal_version: Version at which this object should be considered as deprecated and should no longer be used.
    :param replacement: A new alternative to this (now deprecated) object.
    :param extra_msg: Additional message included in the deprecation warning/exception at the end.
    :param no_docstring_check:
        Disable a runtime check for the docstring of the decorated object containing ``.. deprecated::``.

    .. note:
        If the project version contains any additional qualifiers (e.g. pre-release, post-release, dev/local versions),
        they will be ignored and the project version will be treated a simple stable (major, minor, micro) version.
    """

    def inner(func: Callable[P, R]) -> Callable[P, R]:
        obj_name = getattr(func, "__qualname__", func.__name__) if display_name is None else display_name

        if not no_docstring_check:
            obj_doc = func.__doc__ or ""
            if DEPRECATED_DIRECTIVE_RE.search(obj_doc) is None:
                raise ValueError("Deprecated object does not contain '.. deprecated::' sphinx directive in its docstring")

        @wraps(func)
        def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
            deprecation_warn(
                obj_name=obj_name,
                removal_version=removal_version,
                replacement=replacement,
                extra_msg=extra_msg,
                stack_level=3,
            )
            return func(*args, **kwargs)

        return wrapper

    return inner


================================================
FILE: mcstatus/_utils/general.py
================================================
from typing import TypeVar

__all__ = ["or_none"]


T = TypeVar("T")


def or_none(*args: T) -> T | None:
    """Return the first non-None argument.

    This function is similar to the standard inline ``or`` operator, while
    treating falsey values (such as ``0``, ``''``, or ``False``) as valid
    results rather than skipping them. It only skips ``None`` values.

    This is useful when selecting between optional values that may be empty
    but still meaningful.

    Example:
        .. code-block:: py
            >>> or_none("", 0, "fallback")
            ''
            >>> or_none(None, None, "value")
            'value'
            >>> or_none(None, None)
            None

        This is often useful when working with dict.get, e.g.:

        .. code-block:: py
            >>> mydict = {"a": ""}
            >>> mydict.get("a") or mydict.get("b")
            None  # expected ''!
            >>> or_none(mydict.get("a"), mydict.get("b"))
            ''
    """
    for arg in args:
        if arg is not None:
            return arg
    return None


================================================
FILE: mcstatus/_utils/retry.py
================================================
from __future__ import annotations

import inspect
from functools import wraps
from typing import ParamSpec, TYPE_CHECKING, TypeVar, cast

if TYPE_CHECKING:
    from collections.abc import Callable

__all__ = ["retry"]

T = TypeVar("T")
R = TypeVar("R")
P = ParamSpec("P")
P2 = ParamSpec("P2")


def retry(tries: int, exceptions: tuple[type[BaseException]] = (Exception,)) -> Callable[[Callable[P, R]], Callable[P, R]]:
    """Decorator that re-runs given function ``tries`` times if error occurs.

    The amount of tries will either be the value given to the decorator,
    or if tries is present in keyword arguments on function call, this
    specified value will take precedence.

    If the function fails even after all the retries, raise the last
    exception that the function raised.

    .. note::
        Even if the previous failures caused a different exception, this will only raise the last one.
    """  # noqa: D401 # imperative mood

    def decorate(func: Callable[P, R]) -> Callable[P, R]:
        @wraps(func)
        async def async_wrapper(
            *args: P.args,
            tries: int = tries,  # pyright: ignore[reportGeneralTypeIssues] # No support for adding kw-only args
            **kwargs: P.kwargs,
        ) -> R:
            last_exc: BaseException
            for _ in range(tries):
                try:
                    return await func(*args, **kwargs)  # pyright: ignore[reportGeneralTypeIssues] # We know func is awaitable here
                except exceptions as exc:  # noqa: PERF203 # try-except within a loop
                    last_exc = exc
            # This won't actually be unbound
            raise last_exc  # pyright: ignore[reportGeneralTypeIssues,reportPossiblyUnboundVariable]

        @wraps(func)
        def sync_wrapper(
            *args: P.args,
            tries: int = tries,  # pyright: ignore[reportGeneralTypeIssues] # No support for adding kw-only args
            **kwargs: P.kwargs,
        ) -> R:
            last_exc: BaseException
            for _ in range(tries):
                try:
                    return func(*args, **kwargs)
                except exceptions as exc:  # noqa: PERF203 # try-except within a loop
                    last_exc = exc
            # This won't actually be unbound
            raise last_exc  # pyright: ignore[reportGeneralTypeIssues,reportPossiblyUnboundVariable]

        # We cast here since pythons typing doesn't support adding keyword-only arguments to signature
        # (Support for this was a rejected idea https://peps.python.org/pep-0612/#concatenating-keyword-parameters)
        if inspect.iscoroutinefunction(func):
            return cast("Callable[P, R]", async_wrapper)
        return cast("Callable[P, R]", sync_wrapper)

    return decorate


================================================
FILE: mcstatus/motd/__init__.py
================================================
from __future__ import annotations

import re
import typing as t
from dataclasses import dataclass

from mcstatus.motd._simplifies import get_unused_elements, squash_nearby_strings
from mcstatus.motd._transformers import AnsiTransformer, HtmlTransformer, MinecraftTransformer, PlainTransformer
from mcstatus.motd.components import Formatting, MinecraftColor, ParsedMotdComponent, TranslationTag, WebColor

if t.TYPE_CHECKING:
    from typing_extensions import Self

    from mcstatus.responses._raw import RawJavaResponseMotd, RawJavaResponseMotdWhenDict

__all__ = ["Motd"]

_MOTD_COLORS_RE = re.compile(r"([\xA7|&][0-9A-FK-OR])", re.IGNORECASE)


@dataclass(frozen=True)
class Motd:
    """Represents parsed MOTD."""

    parsed: list[ParsedMotdComponent]
    """Parsed MOTD, which then will be transformed.

    Bases on this attribute, you can easily write your own MOTD-to-something parser.
    """
    raw: RawJavaResponseMotd
    """MOTD in raw format, returning back the received server response unmodified."""
    bedrock: bool = False
    """Is the server Bedrock Edition?"""

    @classmethod
    def parse(
        cls,
        raw: RawJavaResponseMotd,  # pyright: ignore[reportRedeclaration] # later, we overwrite the type
        *,
        bedrock: bool = False,
    ) -> Self:
        """Parse a raw MOTD to less raw MOTD (:attr:`.parsed` attribute).

        :param raw: Raw MOTD, directly from server.
        :param bedrock: Is server Bedrock Edition? Nothing changes here, just sets attribute.
        :returns: New :class:`.Motd` instance.
        """
        original_raw = raw.copy() if hasattr(raw, "copy") else raw  # pyright: ignore[reportAttributeAccessIssue] # Cannot access "copy" for type "str"
        if isinstance(raw, list):
            raw: RawJavaResponseMotdWhenDict = {"extra": raw}

        if isinstance(raw, str):
            parsed = cls._parse_as_str(raw, bedrock=bedrock)
        elif isinstance(raw, dict):
            parsed = cls._parse_as_dict(raw, bedrock=bedrock)
        else:
            raise TypeError(f"Expected list, string or dict data, got {raw.__class__!r} ({raw!r}), report this!")

        return cls(parsed, original_raw, bedrock)

    @staticmethod
    def _parse_as_str(raw: str, *, bedrock: bool = False) -> list[ParsedMotdComponent]:
        """Parse a MOTD when it's string.

        .. note:: This method returns a lot of empty strings, use :meth:`Motd.simplify` to remove them.

        :param raw: Raw MOTD, directly from server.
        :param bedrock: Is server Bedrock Edition?
            Ignores :attr:`MinecraftColor.MINECOIN_GOLD` if it's :obj:`False`.
        :returns: :obj:`ParsedMotdComponent` list, which need to be passed to ``__init__``.
        """
        parsed_motd: list[ParsedMotdComponent] = []

        split_raw = _MOTD_COLORS_RE.split(raw)
        for element in split_raw:
            clean_element = element.lstrip("&§").lower()
            standardized_element = element.replace("&", "§").lower()

            if standardized_element == "§g" and not bedrock:
                parsed_motd.append(element)  # minecoin_gold on java server, treat as string
                continue

            if standardized_element.startswith("§"):
                try:
                    parsed_motd.append(MinecraftColor(clean_element))
                except ValueError:
                    try:
                        parsed_motd.append(Formatting(clean_element))
                    except ValueError:
                        # just a text
                        parsed_motd.append(element)
            else:
                parsed_motd.append(element)

        return parsed_motd

    @classmethod
    def _parse_as_dict(
        cls,
        item: RawJavaResponseMotdWhenDict,
        *,
        bedrock: bool = False,
        auto_add: list[ParsedMotdComponent] | None = None,
    ) -> list[ParsedMotdComponent]:
        """Parse a MOTD when it's dict.

        :param item: :class:`dict` directly from the server.
        :param bedrock: Is the server Bedrock Edition?
            Nothing does here, just going to :meth:`._parse_as_str` while parsing ``text`` field.
        :param auto_add: Values to add on this item.
            Most time, this is :class:`Formatting` from top level.
        :returns: :obj:`ParsedMotdComponent` list, which need to be passed to ``__init__``.
        """
        parsed_motd: list[ParsedMotdComponent] = auto_add if auto_add is not None else []

        if (color := item.get("color")) is not None:
            parsed_motd.append(cls._parse_color(color))

        for style_key, style_val in Formatting.__members__.items():
            lowered_style_key = style_key.lower()
            if item.get(lowered_style_key) is False:
                try:
                    parsed_motd.remove(style_val)
                except ValueError:
                    # some servers set the formatting keys to false here, even without it ever being set to true before
                    continue
            elif item.get(lowered_style_key) is not None:
                parsed_motd.append(style_val)

        if (text := item.get("text")) is not None:
            parsed_motd.extend(cls._parse_as_str(text, bedrock=bedrock))
        if (translate := item.get("translate")) is not None:
            parsed_motd.append(TranslationTag(translate))
        parsed_motd.append(Formatting.RESET)

        if "extra" in item:
            auto_add = list(filter(lambda e: type(e) is Formatting and e != Formatting.RESET, parsed_motd))

            for element in item["extra"]:
                parsed_motd.extend(
                    cls._parse_as_dict(element, auto_add=auto_add.copy())
                    if isinstance(element, dict)
                    else auto_add + cls._parse_as_str(element, bedrock=bedrock)
                )

        return parsed_motd

    @staticmethod
    def _parse_color(color: str) -> ParsedMotdComponent:
        """Parse a color string."""
        try:
            return MinecraftColor[color.upper()]
        except KeyError:
            if color == "reset":
                # Minecraft servers actually can't return {"reset": True}, instead, they treat
                # reset as a color and set {"color": "reset"}. However logically, reset is
                # a formatting, and it resets both color and other formatting, so we use
                # `Formatting.RESET` here.
                #
                # see `color` field in
                # https://minecraft.wiki/w/Java_Edition_protocol/Chat?oldid=2763811#Shared_between_all_components
                return Formatting.RESET

            # Last attempt: try parsing as HTML (hex rgb) color. Some servers use these to
            # achieve gradients.
            try:
                return WebColor.from_hex(color)
            except ValueError as e:
                raise ValueError(f"Unable to parse color: {color!r}, report this!") from e

    def simplify(self) -> Self:
        """Create new MOTD without unused elements.

        After parsing, the MOTD may contain some unused elements, like empty strings, or formatting/colors
        that don't apply to anything. This method is responsible for creating a new motd with all such elements
        removed, providing a much cleaner representation.

        :returns: New simplified MOTD, with any unused elements removed.
        """
        parsed = self.parsed.copy()
        old_parsed: list[ParsedMotdComponent] | None = None

        while parsed != old_parsed:
            old_parsed = parsed.copy()
            unused_elements = get_unused_elements(parsed)
            parsed = [el for index, el in enumerate(parsed) if index not in unused_elements]

        parsed = squash_nearby_strings(parsed)
        return self.__class__(parsed, self.raw, bedrock=self.bedrock)

    def to_plain(self) -> str:
        """Get plain text from a MOTD, without any colors/formatting.

        Example:
            ``&0Hello &oWorld`` turns into ``Hello World``.
        """
        return PlainTransformer().transform(self.parsed)

    def to_minecraft(self) -> str:
        """Transform MOTD to the Minecraft representation.

        .. note:: This will always use ``§``, even if in original MOTD used ``&``.

        Example:
            .. code-block:: python

                >>> Motd.parse("&0Hello &oWorld")
                "§0Hello §oWorld"
        """
        return MinecraftTransformer().transform(self.parsed)

    def to_html(self) -> str:
        """Transform MOTD to the HTML format.

        The result is always wrapped in a ``<p>`` tag, if you need to remove it,
        just do ``result.removeprefix("<p>").removesuffix("</p>")``.

        .. note::
            You should implement the "obfuscated" CSS class yourself using this snippet:

            .. code-block:: javascript

                const obfuscatedCharacters =
                  "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`~!@#$%^&*()-_=+[]\\"';:<>,./?";
                const obfuscatedElems = document.querySelectorAll(".obfuscated");

                if (obfuscatedElems !== undefined) {
                  const render = () => {
                    obfuscatedElems.forEach((elem) => {
                      let value = "";

                      for (let i = 0, l = elem.innerText.length; i < l; i++) {
                        value += obfuscatedCharacters.charAt(
                          Math.floor(Math.random() * obfuscatedCharacters.length),
                        );
                      }

                      elem.innerText = value;
                    });
                    setTimeout(render, 50);
                  };
                  render();
                }

            Also do note that this formatting does not make sense with
            non-monospace fonts.

        Example:
            ``&6Hello&o from &rAnother &kWorld`` turns into

            .. code-block:: html

                <!-- there are no new lines in the actual output, those are added for readability -->
                <p>
                 <span style='color:rgb(255, 170, 0);text-shadow:0 0 1px rgb(42, 42, 0)'>
                  Hello<i> from </span></i>
                  Another <span class=obfuscated>World</span>
                </p>
        """  # noqa: D301 # Use `r"""` if any backslashes in a docstring
        return HtmlTransformer(bedrock=self.bedrock).transform(self.parsed)

    def to_ansi(self) -> str:
        """Transform MOTD to the ANSI 24-bit format.

        ANSI is mostly used for printing colored text in the terminal.

        "Obfuscated" formatting (``&k``) is shown as a blinking one.

        .. seealso:: https://en.wikipedia.org/wiki/ANSI_escape_code.
        """
        return AnsiTransformer(bedrock=self.bedrock).transform(self.parsed)


================================================
FILE: mcstatus/motd/_simplifies.py
================================================
from __future__ import annotations

import typing as t

from mcstatus.motd.components import Formatting, MinecraftColor, ParsedMotdComponent, WebColor

if t.TYPE_CHECKING:
    from collections.abc import Sequence

__all__ = [
    "get_double_colors",
    "get_double_items",
    "get_empty_text",
    "get_end_non_text",
    "get_formatting_before_color",
    "get_meaningless_resets_and_colors",
    "get_unused_elements",
    "squash_nearby_strings",
]

_PARSED_MOTD_COMPONENTS_TYPEVAR = t.TypeVar("_PARSED_MOTD_COMPONENTS_TYPEVAR", bound="list[ParsedMotdComponent]")


def get_unused_elements(parsed: Sequence[ParsedMotdComponent]) -> set[int]:
    """Get indices of all items which are unused and can be safely removed from the MOTD.

    This is a wrapper method around several unused item collection methods.
    """
    to_remove: set[int] = set()

    for simplifier in [
        get_double_items,
        get_double_colors,
        get_formatting_before_color,
        get_meaningless_resets_and_colors,
        get_empty_text,
        get_end_non_text,
    ]:
        to_remove.update(simplifier(parsed))

    return to_remove


def squash_nearby_strings(parsed: _PARSED_MOTD_COMPONENTS_TYPEVAR) -> _PARSED_MOTD_COMPONENTS_TYPEVAR:
    """Squash duplicate strings together.

    Note that this function doesn't create a copy of passed array, it modifies it.
    This is what those typevars are for in the function signature.
    """
    # in order to not break indexes, we need to fill values and then remove them after the loop
    fillers: set[int] = set()
    for index, item in enumerate(parsed):
        if not isinstance(item, str):
            continue

        try:
            next_item = parsed[index + 1]
        except IndexError:  # Last item (without any next item)
            break

        if isinstance(next_item, str):
            parsed[index + 1] = item + next_item
            fillers.add(index)

    for already_removed, index_to_remove in enumerate(fillers):
        parsed.pop(index_to_remove - already_removed)

    return parsed


def get_double_items(parsed: Sequence[ParsedMotdComponent]) -> set[int]:
    """Get indices of all doubled items that can be removed.

    Removes any items that are followed by an item of the same kind (compared using ``__eq__``).
    """
    to_remove: set[int] = set()

    for index, item in enumerate(parsed):
        try:
            next_item = parsed[index + 1]
        except IndexError:  # Last item (without any next item)
            break

        if isinstance(item, (Formatting, MinecraftColor, WebColor)) and item == next_item:
            to_remove.add(index)

    return to_remove


def get_double_colors(parsed: Sequence[ParsedMotdComponent]) -> set[int]:
    """Get indices of all doubled color items.

    As colors (obviously) override each other, we only ever care about the last one, ignore
    the previous ones. (for example: specifying red color, then orange, then yellow, then some text
    will just result in yellow text)
    """
    to_remove: set[int] = set()

    prev_color: int | None = None
    for index, item in enumerate(parsed):
        if isinstance(item, (MinecraftColor, WebColor)):
            # If we found a color after another, remove the previous color
            if prev_color is not None:
                to_remove.add(prev_color)
            prev_color = index

        # If we find a string, that's what our color we found previously applies to,
        # set prev_color to None, marking this color as used
        if isinstance(item, str):
            prev_color = None

    return to_remove


def get_formatting_before_color(parsed: Sequence[ParsedMotdComponent]) -> set[int]:
    """Obtain indices of all unused formatting items before colors.

    Colors override any formatting before them, meaning we only ever care about the color, and can
    ignore all formatting before it. (For example: specifying bold formatting, then italic, then yellow,
    will just result in yellow text.)
    """
    to_remove: set[int] = set()

    collected_formattings = []
    for index, item in enumerate(parsed):
        # Collect the indices of formatting items
        if isinstance(item, Formatting):
            collected_formattings.append(index)

        # Only run checks if we have some collected formatting items
        if len(collected_formattings) == 0:
            continue

        # If there's a string after some formattings, the formattings apply to it.
        # This means they're not unused, remove them.
        if isinstance(item, str) and not item.isspace():
            collected_formattings = []
            continue

        # If there's a color after some formattings, these formattings will be overridden
        # as colors reset everything. This makes these formattings pointless, mark them
        # for removal.
        if isinstance(item, (MinecraftColor, WebColor)):
            to_remove.update(collected_formattings)
            collected_formattings = []
    return to_remove


def get_empty_text(parsed: Sequence[ParsedMotdComponent]) -> set[int]:
    """Get indices of all empty text items.

    Empty strings in motd serve no purpose and can be marked for removal.
    """
    to_remove: set[int] = set()

    for index, item in enumerate(parsed):
        if isinstance(item, str) and len(item) == 0:
            to_remove.add(index)

    return to_remove


def get_end_non_text(parsed: Sequence[ParsedMotdComponent]) -> set[int]:
    """Get indices of all trailing items, found after the last text component.

    Any color/formatting items only make sense when they apply to some text.
    If there are some at the end, after the last text, they're pointless and
    can be removed.
    """
    to_remove: set[int] = set()

    for rev_index, item in enumerate(reversed(parsed)):
        # The moment we find our last string, stop the loop
        if isinstance(item, str):
            break

        # Remove any color/formatting that doesn't apply to text
        if isinstance(item, (MinecraftColor, WebColor, Formatting)):
            index = len(parsed) - 1 - rev_index
            to_remove.add(index)

    return to_remove


def get_meaningless_resets_and_colors(parsed: Sequence[ParsedMotdComponent]) -> set[int]:
    to_remove: set[int] = set()

    active_color: MinecraftColor | WebColor | None = None
    active_formatting: Formatting | None = None
    for index, item in enumerate(parsed):
        if isinstance(item, (MinecraftColor, WebColor)):
            if active_color == item:
                to_remove.add(index)
            active_color = item
            continue
        if isinstance(item, Formatting):
            if item == Formatting.RESET:
                if active_color is None and active_formatting is None:
                    to_remove.add(index)
                    continue
                active_color, active_formatting = None, None
                continue
            if active_formatting == item:
                to_remove.add(index)
            active_formatting = item

    return to_remove


================================================
FILE: mcstatus/motd/_transformers.py
================================================
from __future__ import annotations

import abc
import typing as t
from collections.abc import Callable, Sequence

from mcstatus.motd.components import Formatting, MinecraftColor, ParsedMotdComponent, TranslationTag, WebColor

__all__ = [
    "AnsiTransformer",
    "HtmlTransformer",
    "MinecraftTransformer",
    "PlainTransformer",
]

if t.TYPE_CHECKING:
    from collections.abc import Callable, Sequence

_HOOK_RETURN_TYPE = t.TypeVar("_HOOK_RETURN_TYPE")
_END_RESULT_TYPE = t.TypeVar("_END_RESULT_TYPE")

# MinecraftColor: (foreground, background) # noqa: ERA001 # commented-out code
_SHARED_MINECRAFT_COLOR_TO_RGB = {
    MinecraftColor.BLACK: ((0, 0, 0), (0, 0, 0)),
    MinecraftColor.DARK_BLUE: ((0, 0, 170), (0, 0, 42)),
    MinecraftColor.DARK_GREEN: ((0, 170, 0), (0, 42, 0)),
    MinecraftColor.DARK_AQUA: ((0, 170, 170), (0, 42, 42)),
    MinecraftColor.DARK_RED: ((170, 0, 0), (42, 0, 0)),
    MinecraftColor.DARK_PURPLE: ((170, 0, 170), (42, 0, 42)),
    MinecraftColor.GOLD: ((255, 170, 0), (64, 42, 0)),
    MinecraftColor.GRAY: ((170, 170, 170), (42, 42, 42)),
    MinecraftColor.DARK_GRAY: ((85, 85, 85), (21, 21, 21)),
    MinecraftColor.BLUE: ((85, 85, 255), (21, 21, 63)),
    MinecraftColor.GREEN: ((85, 255, 85), (21, 63, 21)),
    MinecraftColor.AQUA: ((85, 255, 255), (21, 63, 63)),
    MinecraftColor.RED: ((255, 85, 85), (63, 21, 21)),
    MinecraftColor.LIGHT_PURPLE: ((255, 85, 255), (63, 21, 63)),
    MinecraftColor.YELLOW: ((255, 255, 85), (63, 63, 21)),
    MinecraftColor.WHITE: ((255, 255, 255), (63, 63, 63)),
}

_MINECRAFT_COLOR_TO_RGB_JAVA = _SHARED_MINECRAFT_COLOR_TO_RGB.copy()
_MINECRAFT_COLOR_TO_RGB_JAVA[MinecraftColor.GRAY] = ((170, 170, 170), (42, 42, 42))

_MINECRAFT_COLOR_TO_RGB_BEDROCK = _SHARED_MINECRAFT_COLOR_TO_RGB.copy()
_MINECRAFT_COLOR_TO_RGB_BEDROCK.update(
    {
        MinecraftColor.GRAY: ((198, 198, 198), (49, 49, 49)),
        MinecraftColor.MINECOIN_GOLD: ((221, 214, 5), (55, 53, 1)),
        MinecraftColor.MATERIAL_QUARTZ: ((227, 212, 209), (56, 53, 52)),
        MinecraftColor.MATERIAL_IRON: ((206, 202, 202), (51, 50, 50)),
        MinecraftColor.MATERIAL_NETHERITE: ((68, 58, 59), (17, 14, 14)),
        MinecraftColor.MATERIAL_REDSTONE: ((151, 22, 7), (37, 5, 1)),
        MinecraftColor.MATERIAL_COPPER: ((180, 104, 77), (45, 26, 19)),
        MinecraftColor.MATERIAL_GOLD: ((222, 177, 45), (55, 44, 11)),
        MinecraftColor.MATERIAL_EMERALD: ((17, 159, 54), (4, 40, 13)),
        MinecraftColor.MATERIAL_DIAMOND: ((44, 186, 168), (11, 46, 42)),
        MinecraftColor.MATERIAL_LAPIS: ((33, 73, 123), (8, 18, 30)),
        MinecraftColor.MATERIAL_AMETHYST: ((154, 92, 198), (38, 23, 49)),
        MinecraftColor.MATERIAL_RESIN: ((235, 114, 20), (59, 29, 5)),
    }
)


class _BaseTransformer(abc.ABC, t.Generic[_HOOK_RETURN_TYPE, _END_RESULT_TYPE]):
    """Base MOTD transformer class.

    Transformers are responsible for providing a way to generate an alternative
    representation of MOTD, for example, as HTML.

    The methods ``_handle_*`` handle each
    :type:`~mcstatus.motd.components.ParsedMotdComponent` individually.
    """

    def transform(self, motd_components: Sequence[ParsedMotdComponent]) -> _END_RESULT_TYPE:
        return self._format_output([handled for component in motd_components for handled in self._handle_component(component)])

    @abc.abstractmethod
    def _format_output(self, results: list[_HOOK_RETURN_TYPE]) -> _END_RESULT_TYPE: ...

    def _handle_component(
        self, component: ParsedMotdComponent
    ) -> tuple[_HOOK_RETURN_TYPE, _HOOK_RETURN_TYPE] | tuple[_HOOK_RETURN_TYPE]:
        handler: Callable[[ParsedMotdComponent], _HOOK_RETURN_TYPE] = {
            MinecraftColor: self._handle_minecraft_color,
            WebColor: self._handle_web_color,
            Formatting: self._handle_formatting,
            TranslationTag: self._handle_translation_tag,
            str: self._handle_str,
        }[type(component)]

        additional = None
        if isinstance(component, MinecraftColor):
            additional = self._handle_formatting(Formatting.RESET)

        return (additional, handler(component)) if additional is not None else (handler(component),)

    @abc.abstractmethod
    def _handle_str(self, element: str, /) -> _HOOK_RETURN_TYPE: ...

    @abc.abstractmethod
    def _handle_translation_tag(self, _: TranslationTag, /) -> _HOOK_RETURN_TYPE: ...

    @abc.abstractmethod
    def _handle_web_color(self, element: WebColor, /) -> _HOOK_RETURN_TYPE: ...

    @abc.abstractmethod
    def _handle_formatting(self, element: Formatting, /) -> _HOOK_RETURN_TYPE: ...

    @abc.abstractmethod
    def _handle_minecraft_color(self, element: MinecraftColor, /) -> _HOOK_RETURN_TYPE: ...


class _NothingTransformer(_BaseTransformer[str, str]):
    """Transformer that transforms all elements into empty strings.

    This transformer acts as a base for other transformers with string result type.
    """

    def _format_output(self, results: list[str]) -> str:
        return "".join(results)

    def _handle_str(self, _element: str, /) -> str:
        return ""

    def _handle_minecraft_color(self, _element: MinecraftColor, /) -> str:
        return ""

    def _handle_web_color(self, _element: WebColor, /) -> str:
        return ""

    def _handle_formatting(self, _element: Formatting, /) -> str:
        return ""

    def _handle_translation_tag(self, _element: TranslationTag, /) -> str:
        return ""


class PlainTransformer(_NothingTransformer):
    def _handle_str(self, element: str, /) -> str:
        return element


class MinecraftTransformer(PlainTransformer):
    def _handle_component(self, component: ParsedMotdComponent) -> tuple[str, str] | tuple[str]:
        result = super()._handle_component(component)
        if len(result) == 2:
            return (result[1],)
        return result

    def _handle_minecraft_color(self, element: MinecraftColor, /) -> str:
        return "§" + element.value

    def _handle_formatting(self, element: Formatting, /) -> str:
        return "§" + element.value


class HtmlTransformer(PlainTransformer):
    _FORMATTING_TO_HTML_TAGS: t.ClassVar = {
        Formatting.BOLD: "b",
        Formatting.STRIKETHROUGH: "s",
        Formatting.ITALIC: "i",
        Formatting.UNDERLINED: "u",
    }

    # TODO: When dropping v13 support, make sure to drop the default value for the bedrock arg
    def __init__(self, *, bedrock: bool = False) -> None:
        self.bedrock = bedrock
        self.on_reset: list[str] = []

    def transform(self, motd_components: Sequence[ParsedMotdComponent]) -> str:
        self.on_reset = []
        return super().transform(motd_components)

    def _format_output(self, results: list[str]) -> str:
        return "<p>" + super()._format_output(results) + "".join(self.on_reset) + "</p>"

    def _handle_str(self, element: str, /) -> str:
        return element.replace("\n", "<br>")

    def _handle_minecraft_color(self, element: MinecraftColor, /) -> str:
        color_map = _MINECRAFT_COLOR_TO_RGB_BEDROCK if self.bedrock else _MINECRAFT_COLOR_TO_RGB_JAVA
        fg_color, bg_color = color_map[element]

        self.on_reset.append("</span>")
        return f"<span style='color:rgb{fg_color};text-shadow:0 0 1px rgb{bg_color}'>"

    def _handle_web_color(self, element: WebColor, /) -> str:
        self.on_reset.append("</span>")
        return f"<span style='color:rgb{element.rgb}'>"

    def _handle_formatting(self, element: Formatting, /) -> str:
        if element is Formatting.RESET:
            to_return = "".join(self.on_reset)
            self.on_reset = []
            return to_return

        if element is Formatting.OBFUSCATED:
            self.on_reset.append("</span>")
            return "<span class=obfuscated>"

        tag_name = self._FORMATTING_TO_HTML_TAGS[element]
        self.on_reset.append(f"</{tag_name}>")
        return f"<{tag_name}>"


class AnsiTransformer(PlainTransformer):
    _FORMATTING_TO_ANSI_TAGS: t.ClassVar = {
        Formatting.BOLD: "1",
        Formatting.STRIKETHROUGH: "9",
        Formatting.ITALIC: "3",
        Formatting.UNDERLINED: "4",
        Formatting.OBFUSCATED: "5",
    }
    _MINECRAFT_COLOR_TO_RGB_JAVA: t.ClassVar = {
        key: foreground for key, (foreground, _background) in _MINECRAFT_COLOR_TO_RGB_JAVA.items()
    }
    _MINECRAFT_COLOR_TO_RGB_BEDROCK: t.ClassVar = {
        key: foreground for key, (foreground, _background) in _MINECRAFT_COLOR_TO_RGB_BEDROCK.items()
    }

    # TODO: When dropping v13 support, make sure to drop the default value for the bedrock arg
    def __init__(self, *, bedrock: bool = True) -> None:
        self.bedrock = bedrock

    def ansi_color(self, color: tuple[int, int, int] | MinecraftColor) -> str:
        """Transform RGB color to ANSI color code."""
        if isinstance(color, MinecraftColor):
            color_to_rgb = self._MINECRAFT_COLOR_TO_RGB_BEDROCK if self.bedrock else self._MINECRAFT_COLOR_TO_RGB_JAVA
            color = color_to_rgb[color]

        return "\033[38;2;{};{};{}m".format(*color)

    def _format_output(self, results: list[str]) -> str:
        return "\033[0m" + super()._format_output(results) + "\033[0m"

    def _handle_minecraft_color(self, element: MinecraftColor, /) -> str:
        return self.ansi_color(element)

    def _handle_web_color(self, element: WebColor, /) -> str:
        return self.ansi_color(element.rgb)

    def _handle_formatting(self, element: Formatting, /) -> str:
        if element is Formatting.RESET:
            return "\033[0m"
        return "\033[" + self._FORMATTING_TO_ANSI_TAGS[element] + "m"


================================================
FILE: mcstatus/motd/components.py
================================================
from __future__ import annotations

import typing as t
from dataclasses import dataclass
from enum import Enum

if t.TYPE_CHECKING:
    from typing_extensions import Self

__all__ = [
    "Formatting",
    "MinecraftColor",
    "ParsedMotdComponent",
    "TranslationTag",
    "WebColor",
]


# NOTE: keep in sync with the definition in docs (`docs/api/motd_parsing.rst`)
# the autodocs plugin does not support type aliases yet, so those have to be
# defined manually in docs
ParsedMotdComponent: t.TypeAlias = "Formatting | MinecraftColor | WebColor | TranslationTag | str"


class Formatting(Enum):
    """Enum for Formatting codes.

    See `Minecraft wiki <https://minecraft.wiki/w/Formatting_codes#Formatting_codes>`__
    for more info.

    .. note::
        :attr:`.STRIKETHROUGH` and :attr:`.UNDERLINED` don't work on Bedrock, which our parser
        doesn't keep it in mind. See `MCPE-41729 <https://bugs.mojang.com/browse/MCPE-41729>`_.
    """

    BOLD = "l"
    ITALIC = "o"
    UNDERLINED = "n"
    STRIKETHROUGH = "m"
    OBFUSCATED = "k"
    RESET = "r"


class MinecraftColor(Enum):
    """Enum for Color codes.

    See `Minecraft wiki <https://minecraft.wiki/w/Formatting_codes#Color_codes>`_
    for more info.
    """

    BLACK = "0"
    DARK_BLUE = "1"
    DARK_GREEN = "2"
    DARK_AQUA = "3"
    DARK_RED = "4"
    DARK_PURPLE = "5"
    GOLD = "6"
    GRAY = "7"
    DARK_GRAY = "8"
    BLUE = "9"
    GREEN = "a"
    AQUA = "b"
    RED = "c"
    LIGHT_PURPLE = "d"
    YELLOW = "e"
    WHITE = "f"

    # Only for bedrock
    MINECOIN_GOLD = "g"
    MATERIAL_QUARTZ = "h"
    MATERIAL_IRON = "i"
    MATERIAL_NETHERITE = "j"
    MATERIAL_REDSTONE = "m"
    MATERIAL_COPPER = "n"
    MATERIAL_GOLD = "p"
    MATERIAL_EMERALD = "q"
    MATERIAL_DIAMOND = "s"
    MATERIAL_LAPIS = "t"
    MATERIAL_AMETHYST = "u"
    MATERIAL_RESIN = "v"


@dataclass(frozen=True)
class WebColor:
    """Raw HTML color from MOTD.

    Can be found in MOTD when someone uses gradient.

    .. note:: Actually supported in Minecraft 1.16+ only.
    """

    hex: str
    rgb: tuple[int, int, int]

    @classmethod
    def from_hex(cls, hex: str) -> Self:  # noqa: A002 # shadowing a hex builtin
        """Construct web color using hex color string.

        :raises ValueError: Invalid hex color string.
        :returns: New :class:`WebColor` instance.
        """
        hex = hex.lstrip("#")  # noqa: A001 # shadowing a hex builtin

        if len(hex) not in (3, 6):
            raise ValueError(f"Got too long/short hex color: {'#' + hex!r}")
        if len(hex) == 3:
            hex = "{0}{0}{1}{1}{2}{2}".format(*hex)  # noqa: A001 # shadowing a hex builtin

        try:
            rgb = t.cast("tuple[int, int, int]", tuple(int(hex[i : i + 2], 16) for i in (0, 2, 4)))
        except ValueError as e:
            raise ValueError(f"Failed to parse given hex color: {'#' + hex!r}") from e

        return cls.from_rgb(rgb)

    @classmethod
    def from_rgb(cls, rgb: tuple[int, int, int]) -> Self:
        """Construct web color using rgb color tuple.

        :raises ValueError: When RGB color is out of its 8-bit range.
        :returns: New :class:`WebColor` instance.
        """
        cls._check_rgb(rgb)

        hex = "#{:02x}{:02x}{:02x}".format(*rgb)  # noqa: A001 # shadowing a hex builtin
        return cls(hex, rgb)

    @staticmethod
    def _check_rgb(rgb: tuple[int, int, int]) -> None:
        index_to_color_name = {0: "red", 1: "green", 2: "blue"}

        for index, value in enumerate(rgb):
            if not 255 >= value >= 0:
                color_name = index_to_color_name[index]
                raise ValueError(f"RGB color byte out of its 8-bit range (0-255) for {color_name} ({value=})")


@dataclass(frozen=True)
class TranslationTag:
    """Represents a ``translate`` field in server's answer.

    This just exists, but is completely ignored by our transformers.
    You can find translation tags in :attr:`Motd.parsed <mcstatus.motd.Motd.parsed>` attribute.

    .. seealso:: `Minecraft's wiki. <https://minecraft.wiki/w/Raw_JSON_text_format#Translated_Text>`__
    """

    id: str


================================================
FILE: mcstatus/py.typed
================================================


================================================
FILE: mcstatus/responses/__init__.py
================================================
from mcstatus.responses.base import BaseStatusPlayers, BaseStatusResponse, BaseStatusVersion
from mcstatus.responses.bedrock import BedrockStatusPlayers, BedrockStatusResponse, BedrockStatusVersion
from mcstatus.responses.forge import ForgeData, ForgeDataChannel, ForgeDataMod
from mcstatus.responses.java import JavaStatusPlayer, JavaStatusPlayers, JavaStatusResponse, JavaStatusVersion
from mcstatus.responses.legacy import LegacyStatusPlayers, LegacyStatusResponse, LegacyStatusVersion
from mcstatus.responses.query import QueryPlayers, QueryResponse, QuerySoftware

__all__ = [
    "BaseStatusPlayers",
    "BaseStatusResponse",
    "BaseStatusVersion",
    "BedrockStatusPlayers",
    "BedrockStatusResponse",
    "BedrockStatusVersion",
    "ForgeData",
    "ForgeDataChannel",
    "ForgeDataMod",
    "JavaStatusPlayer",
    "JavaStatusPlayers",
    "JavaStatusResponse",
    "JavaStatusVersion",
    "LegacyStatusPlayers",
    "LegacyStatusResponse",
    "LegacyStatusVersion",
    "QueryPlayers",
    "QueryResponse",
    "QuerySoftware",
]


================================================
FILE: mcstatus/responses/_raw.py
================================================
from __future__ import annotations

from typing import Literal, TYPE_CHECKING, TypeAlias, TypedDict

if TYPE_CHECKING:
    from typing_extensions import NotRequired

__all__ = [
    "RawForgeData",
    "RawForgeDataChannel",
    "RawForgeDataMod",
    "RawJavaResponse",
    "RawJavaResponseMotd",
    "RawJavaResponseMotdWhenDict",
    "RawJavaResponsePlayer",
    "RawJavaResponsePlayers",
    "RawJavaResponseVersion",
    "RawQueryResponse",
]

RawJavaResponseMotd: TypeAlias = "RawJavaResponseMotdWhenDict | list[RawJavaResponseMotdWhenDict | str] | str"


class RawForgeDataChannel(TypedDict):
    res: str
    """Channel name and ID (for example ``fml:handshake``)."""
    version: str
    """Channel version (for example ``1.2.3.4``)."""
    required: bool
    """Is this channel required for client to join?"""


class RawForgeDataMod(TypedDict, total=False):
    modid: str
    modId: str
    modmarker: str
    """Mod version."""
    version: str


class RawForgeData(TypedDict, total=False):
    fmlNetworkVersion: int
    channels: list[RawForgeDataChannel]
    mods: list[RawForgeDataMod]
    modList: list[RawForgeDataMod]
    d: str
    truncated: bool


class RawJavaResponsePlayer(TypedDict):
    name: str
    id: str


class RawJavaResponsePlayers(TypedDict):
    online: int
    max: int
    sample: NotRequired[list[RawJavaResponsePlayer] | None]


class RawJavaResponseVersion(TypedDict):
    name: str
    protocol: int


class RawJavaResponseMotdWhenDict(TypedDict, total=False):
    text: str  # only present if `translate` is set
    translate: str  # same to the above field
    extra: list[RawJavaResponseMotdWhenDict | str]

    color: str
    bold: bool
    strikethrough: bool
    italic: bool
    underlined: bool
    obfuscated: bool


class RawJavaResponse(TypedDict):
    description: RawJavaResponseMotd
    players: RawJavaResponsePlayers
    version: RawJavaResponseVersion
    favicon: NotRequired[str]
    forgeData: NotRequired[RawForgeData | None]
    modinfo: NotRequired[RawForgeData | None]
    enforcesSecureChat: NotRequired[bool]


class RawQueryResponse(TypedDict):
    hostname: str
    gametype: Literal["SMP"]
    game_id: Literal["MINECRAFT"]
    version: str
    plugins: str
    map: str
    numplayers: str  # can be transformed into `int`
    maxplayers: str  # can be transformed into `int`
    hostport: str  # can be transformed into `int`
    hostip: str


================================================
FILE: mcstatus/responses/base.py
================================================
from __future__ import annotations

from abc import ABC, abstractmethod
from dataclasses import asdict, dataclass
from typing import Any, TYPE_CHECKING

if TYPE_CHECKING:
    from typing_extensions import Self

    from mcstatus.motd import Motd

__all__ = [
    "BaseStatusPlayers",
    "BaseStatusResponse",
    "BaseStatusVersion",
]


@dataclass(frozen=True)
class BaseStatusResponse(ABC):
    """Class for storing shared data from a status response."""

    players: BaseStatusPlayers
    """The players information."""
    version: BaseStatusVersion
    """The version information."""
    motd: Motd
    """Message Of The Day. Also known as description.

    .. seealso:: :doc:`/api/motd_parsing`.
    """
    latency: float
    """Latency between a server and the client (you). In milliseconds."""

    @property
    def description(self) -> str:
        """Alias to the :meth:`mcstatus.motd.Motd.to_minecraft` method."""
        return self.motd.to_minecraft()

    @classmethod
    @abstractmethod
    def build(cls, *args: Any, **kwargs: Any) -> Self:
        """Build BaseStatusResponse and check is it valid.

        :param args: Arguments in specific realisation.
        :param kwargs: Keyword arguments in specific realisation.
        :return: :class:`BaseStatusResponse` object.
        """
        raise NotImplementedError("You can't use abstract methods.")

    def as_dict(self) -> dict[str, Any]:
        """Return the dataclass as JSON-serializable :class:`dict`.

        Do note that this method doesn't return :class:`string <str>` but
        :class:`dict`, so you can do some processing on returned value.

        Difference from
        :attr:`~mcstatus.responses.JavaStatusResponse.raw` is in that,
        :attr:`~mcstatus.responses.JavaStatusResponse.raw` returns raw response
        in the same format as we got it. This method returns the response
        in a more user-friendly JSON serializable format (for example,
        :attr:`~mcstatus.responses.BaseStatusResponse.motd` is returned as a
        :func:`Minecraft string <mcstatus.motd.Motd.to_minecraft>` and not
        :class:`dict`).
        """
        as_dict = asdict(self)
        as_dict["motd"] = self.motd.simplify().to_minecraft()
        return as_dict


@dataclass(frozen=True)
class BaseStatusPlayers(ABC):
    """Class for storing information about players on the server."""

    online: int
    """Current number of online players."""
    max: int
    """The maximum allowed number of players (aka server slots)."""


@dataclass(frozen=True)
class BaseStatusVersion(ABC):
    """A class for storing version information."""

    name: str
    """The version name, like ``1.19.3``.

    See `Minecraft wiki <https://minecraft.wiki/w/Java_Edition_version_history#Full_release>`__
    for complete list.
    """
    protocol: int
    """The protocol version, like ``761``.

    See `Minecraft wiki <https://minecraft.wiki/w/Protocol_version#Java_Edition_2>`__.
    """


================================================
FILE: mcstatus/responses/bedrock.py
================================================
from __future__ import annotations

from dataclasses import dataclass
from typing import Any, TYPE_CHECKING

from mcstatus._utils import deprecated
from mcstatus.motd import Motd
from mcstatus.responses.base import BaseStatusPlayers, BaseStatusResponse, BaseStatusVersion

if TYPE_CHECKING:
    from typing_extensions import Self

__all__ = [
    "BedrockStatusPlayers",
    "BedrockStatusResponse",
    "BedrockStatusVersion",
]


@dataclass(frozen=True)
class BedrockStatusResponse(BaseStatusResponse):
    """The response object for :meth:`BedrockServer.status() <mcstatus.server.BedrockServer.status>`."""

    players: BedrockStatusPlayers
    version: BedrockStatusVersion
    map_name: str | None
    """The name of the map."""
    gamemode: str | None
    """The name of the gamemode on the server."""

    @classmethod
    def build(cls, decoded_data: list[Any], latency: float) -> Self:
        """Build BaseStatusResponse and check is it valid.

        :param decoded_data: Raw decoded response object.
        :param latency: Latency of the request.
        :return: :class:`BedrockStatusResponse` object.
        """
        try:
            map_name = decoded_data[7]
        except IndexError:
            map_name = None
        try:
            gamemode = decoded_data[8]
        except IndexError:
            gamemode = None

        return cls(
            players=BedrockStatusPlayers(
                online=int(decoded_data[4]),
                max=int(decoded_data[5]),
            ),
            version=BedrockStatusVersion(
                name=decoded_data[3],
                protocol=int(decoded_data[2]),
                brand=decoded_data[0],
            ),
            motd=Motd.parse(decoded_data[1], bedrock=True),
            latency=latency,
            map_name=map_name,
            gamemode=gamemode,
        )


@dataclass(frozen=True)
class BedrockStatusPlayers(BaseStatusPlayers):
    """Class for storing information about players on the server."""


@dataclass(frozen=True)
class BedrockStatusVersion(BaseStatusVersion):
    """A class for storing version information."""

    name: str
    """The version name, like ``1.19.60``.

    See `Minecraft wiki <https://minecraft.wiki/w/Bedrock_Edition_version_history#Bedrock_Edition>`__
    for complete list.
    """
    brand: str
    """``MCPE`` or ``MCEE`` for Education Edition."""

    @property
    @deprecated(replacement="name", removal_version="13.0.0")
    def version(self) -> str:
        """
        .. deprecated:: 12.0.0
            Will be removed in 13.0.0, use :attr:`.name` instead.
        """  # noqa: D205, D212 # no summary line
        return self.name


================================================
FILE: mcstatus/responses/forge.py
================================================
"""Decoder for data from Forge, that is included into a response object.

After 1.18.1, Forge started to compress its mod data into a
UTF-16 string that represents binary data containing data like
the forge mod loader network version, a big list of channels
that all the forge mods use, and a list of mods the server has.

Before 1.18.1, the mod data was in `forgeData` attribute inside
a response object. We support this implementation too.

For more information see this file from forge itself:
https://github.com/MinecraftForge/MinecraftForge/blob/54b08d2711a15418130694342a3fe9a5dfe005d2/src/main/java/net/minecraftforge/network/ServerStatusPing.java#L27-L73
"""

from __future__ import annotations

from dataclasses import dataclass
from io import StringIO
from typing import Final, TYPE_CHECKING

from mcstatus._protocol.connection import BaseConnection, BaseReadSync, Connection
from mcstatus._utils import or_none

if TYPE_CHECKING:
    from typing_extensions import Self

    from mcstatus.responses._raw import RawForgeData, RawForgeDataChannel, RawForgeDataMod

__all__ = [
    "ForgeData",
    "ForgeDataChannel",
    "ForgeDataMod",
]

_VERSION_FLAG_IGNORE_SERVER_ONLY: Final = 0b1
_IGNORE_SERVER_ONLY: Final = "<not required for client>"


@dataclass(frozen=True)
class ForgeDataChannel:
    """A single Forge data channel."""

    name: str
    """Channel name and ID (for example ``fml:handshake``)."""
    version: str
    """Channel version (for example ``1.2.3.4``)."""
    required: bool
    """Is this channel required for client to join?"""

    @classmethod
    def build(cls, raw: RawForgeDataChannel) -> Self:
        """Build an object about Forge channel from raw response.

        :param raw: ``channel`` element in raw forge response :class:`dict`.
        :return: :class:`ForgeDataChannel` object.
        """
        return cls(name=raw["res"], version=raw["version"], required=raw["required"])

    @classmethod
    def decode(cls, buffer: Connection, mod_id: str | None = None) -> Self:
        """Decode an object about Forge channel from decoded optimized buffer.

        :param buffer: :class:`Connection` object from UTF-16 encoded binary data.
        :param mod_id: Optional mod id prefix :class:`str`.
        :return: :class:`ForgeDataChannel` object.
        """
        channel_identifier = buffer.read_utf()
        if mod_id is not None:
            channel_identifier = f"{mod_id}:{channel_identifier}"
        version = buffer.read_utf()
        client_required = buffer.read_bool()

        return cls(
            name=channel_identifier,
            version=version,
            required=client_required,
        )


@dataclass(frozen=True)
class ForgeDataMod:
    """A single Forge mod."""

    name: str
    """A mod name."""
    marker: str
    """A mod marker. Usually a version."""

    @classmethod
    def build(cls, raw: RawForgeDataMod) -> Self:
        """Build an object about Forge mod from raw response.

        :param raw: ``mod`` element in raw forge response :class:`dict`.
        :return: :class:`ForgeDataMod` object.
        """
        # In FML v1, modmarker was version instead.
        mod_version = or_none(raw.get("modmarker"), raw.get("version"))
        if mod_version is None:
            raise KeyError(f"Mod version in Forge mod data must be provided. Mod info: {raw}")

        # In FML v2, modid was modId instead. At least one of the two should exist.
        mod_id = or_none(raw.get("modid"), raw.get("modId"))
        if mod_id is None:
            raise KeyError(f"Mod ID in Forge mod data must be provided. Mod info: {raw}.")

        return cls(name=mod_id, marker=mod_version)

    @classmethod
    def decode(cls, buffer: Connection) -> tuple[Self, list[ForgeDataChannel]]:
        """Decode data about a Forge mod from decoded optimized buffer.

        :param buffer: :class:`Connection` object from UTF-16 encoded binary data.
        :return: :class:`tuple` object of :class:`ForgeDataMod` object and :class:`list` of :class:`ForgeDataChannel` objects.
        """
        channel_version_flags = buffer.read_varint()

        channel_count = channel_version_flags >> 1
        is_server = channel_version_flags & _VERSION_FLAG_IGNORE_SERVER_ONLY != 0
        mod_id = buffer.read_utf()

        mod_version = _IGNORE_SERVER_ONLY
        if not is_server:
            mod_version = buffer.read_utf()

        channels = [ForgeDataChannel.decode(buffer, mod_id) for _ in range(channel_count)]

        return cls(name=mod_id, marker=mod_version), channels


class _StringBuffer(BaseReadSync, BaseConnection):
    """String Buffer for reading utf-16 encoded binary data."""

    __slots__ = ("received", "stringio")

    def __init__(self, stringio: StringIO) -> None:
        self.stringio = stringio
        self.received = bytearray()

    d
Download .txt
gitextract_h1f5vhjq/

├── .coveragerc
├── .github/
│   ├── CODEOWNERS
│   ├── renovate.json5
│   └── workflows/
│       ├── main.yml
│       ├── publish.yml
│       ├── status_embed.yml
│       ├── unit-tests.yml
│       └── validation.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .readthedocs.yaml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── docs/
│   ├── Makefile
│   ├── api/
│   │   ├── basic.rst
│   │   ├── internal.rst
│   │   └── motd_parsing.rst
│   ├── conf.py
│   ├── examples/
│   │   ├── code/
│   │   │   ├── ping_as_java_and_bedrock_in_one_time.py
│   │   │   ├── ping_many_servers_at_once.py
│   │   │   └── player_list_from_query_with_fallback_on_status.py
│   │   ├── examples.rst
│   │   ├── ping_as_java_and_bedrock_in_one_time.rst
│   │   ├── ping_many_servers_at_once.rst
│   │   └── player_list_from_query_with_fallback_on_status.rst
│   ├── index.rst
│   ├── pages/
│   │   ├── contributing.rst
│   │   ├── faq.rst
│   │   └── versioning.rst
│   └── pyproject.toml
├── mcstatus/
│   ├── __init__.py
│   ├── __main__.py
│   ├── _compat/
│   │   ├── README.md
│   │   ├── __init__.py
│   │   ├── forge_data.py
│   │   ├── motd_transformers.py
│   │   └── status_response.py
│   ├── _net/
│   │   ├── __init__.py
│   │   ├── address.py
│   │   └── dns.py
│   ├── _protocol/
│   │   ├── __init__.py
│   │   ├── bedrock_client.py
│   │   ├── connection.py
│   │   ├── java_client.py
│   │   ├── legacy_client.py
│   │   └── query_client.py
│   ├── _utils/
│   │   ├── __init__.py
│   │   ├── deprecation.py
│   │   ├── general.py
│   │   └── retry.py
│   ├── motd/
│   │   ├── __init__.py
│   │   ├── _simplifies.py
│   │   ├── _transformers.py
│   │   └── components.py
│   ├── py.typed
│   ├── responses/
│   │   ├── __init__.py
│   │   ├── _raw.py
│   │   ├── base.py
│   │   ├── bedrock.py
│   │   ├── forge.py
│   │   ├── java.py
│   │   ├── legacy.py
│   │   └── query.py
│   └── server.py
├── pyproject.toml
└── tests/
    ├── __init__.py
    ├── helpers.py
    ├── motd/
    │   ├── __init__.py
    │   ├── conftest.py
    │   ├── test_components.py
    │   ├── test_motd.py
    │   ├── test_simplifies.py
    │   └── test_transformers.py
    ├── net/
    │   ├── __init__.py
    │   └── test_address.py
    ├── protocol/
    │   ├── __init__.py
    │   ├── test_async_support.py
    │   ├── test_bedrock_client.py
    │   ├── test_connection.py
    │   ├── test_java_client.py
    │   ├── test_java_client_async.py
    │   ├── test_legacy_client.py
    │   ├── test_query_client.py
    │   ├── test_query_client_async.py
    │   └── test_timeout.py
    ├── responses/
    │   ├── __init__.py
    │   ├── conftest.py
    │   ├── test_base.py
    │   ├── test_bedrock.py
    │   ├── test_forge_data.py
    │   ├── test_java.py
    │   ├── test_legacy.py
    │   └── test_query.py
    ├── test_cli.py
    ├── test_compat.py
    ├── test_server.py
    └── utils/
        ├── __init__.py
        ├── test_deprecation.py
        ├── test_general.py
        └── test_retry.py
Download .txt
SYMBOL INDEX (734 symbols across 56 files)

FILE: docs/conf.py
  function _get_version (line 34) | def _get_version() -> Version:
  function mock_autodoc (line 135) | def mock_autodoc() -> None:
  function delete_doc_for_address_base (line 152) | def delete_doc_for_address_base() -> None:

FILE: docs/examples/code/ping_as_java_and_bedrock_in_one_time.py
  function status (line 10) | async def status(host: str) -> JavaStatusResponse | BedrockStatusResponse:
  function handle_exceptions (line 33) | async def handle_exceptions(done: set[asyncio.Task], pending: set[asynci...
  function handle_java (line 55) | async def handle_java(host: str) -> JavaStatusResponse:
  function handle_bedrock (line 60) | async def handle_bedrock(host: str) -> BedrockStatusResponse:

FILE: docs/examples/code/ping_many_servers_at_once.py
  function ping_server (line 6) | async def ping_server(ip: str) -> None:
  function ping_ips (line 15) | async def ping_ips(ips: list[str]) -> None:
  function main (line 27) | def main() -> None:

FILE: mcstatus/__main__.py
  function _motd (line 35) | def _motd(motd: Motd) -> str:
  function _kind (line 41) | def _kind(serv: SupportedServers) -> str:
  function _ping_with_fallback (line 51) | def _ping_with_fallback(server: SupportedServers) -> float:
  function ping_cmd (line 74) | def ping_cmd(server: SupportedServers) -> int:
  function status_cmd (line 79) | def status_cmd(server: SupportedServers) -> int:
  function json_cmd (line 96) | def json_cmd(server: SupportedServers) -> int:
  function query_cmd (line 127) | def query_cmd(server: SupportedServers) -> int:
  function main (line 146) | def main(argv: list[str] = sys.argv[1:]) -> int:

FILE: mcstatus/_net/address.py
  function _valid_urlparse (line 26) | def _valid_urlparse(address: str) -> tuple[str, int | None]:
  class _AddressBase (line 41) | class _AddressBase(NamedTuple):
  class Address (line 53) | class Address(_AddressBase):
    method __init__ (line 65) | def __init__(self, host: str, port: int) -> None:  # noqa: ARG002 # un...
    method _ensure_validity (line 76) | def _ensure_validity(host: object, port: object) -> None:
    method from_tuple (line 85) | def from_tuple(cls, tup: tuple[str, int]) -> Self:
    method from_path (line 90) | def from_path(cls, path: Path, *, default_port: int | None = None) -> ...
    method parse_address (line 100) | def parse_address(cls, address: str, *, default_port: int | None = Non...
    method resolve_ip (line 119) | def resolve_ip(self, lifetime: float | None = None) -> ipaddress.IPv4A...
    method async_resolve_ip (line 157) | async def async_resolve_ip(self, lifetime: float | None = None) -> ipa...
  function minecraft_srv_address_lookup (line 189) | def minecraft_srv_address_lookup(
  function async_minecraft_srv_address_lookup (line 237) | async def async_minecraft_srv_address_lookup(

FILE: mcstatus/_net/dns.py
  function resolve_a_record (line 23) | def resolve_a_record(hostname: str, lifetime: float | None = None) -> str:
  function async_resolve_a_record (line 41) | async def async_resolve_a_record(hostname: str, lifetime: float | None =...
  function resolve_srv_record (line 54) | def resolve_srv_record(query_name: str, lifetime: float | None = None) -...
  function async_resolve_srv_record (line 73) | async def async_resolve_srv_record(query_name: str, lifetime: float | No...
  function resolve_mc_srv (line 87) | def resolve_mc_srv(hostname: str, lifetime: float | None = None) -> tupl...
  function async_resolve_mc_srv (line 100) | async def async_resolve_mc_srv(hostname: str, lifetime: float | None = N...

FILE: mcstatus/_protocol/bedrock_client.py
  class BedrockClient (line 19) | class BedrockClient:
    method __init__ (line 25) | def __init__(self, address: Address, timeout: float = 3) -> None:
    method parse_response (line 30) | def parse_response(data: bytes, latency: float) -> BedrockStatusResponse:
    method read_status (line 37) | def read_status(self) -> BedrockStatusResponse:
    method _read_status (line 43) | def _read_status(self) -> bytes:
    method read_status_async (line 52) | async def read_status_async(self) -> BedrockStatusResponse:
    method _read_status_async (line 59) | async def _read_status_async(self) -> bytes:

FILE: mcstatus/_protocol/connection.py
  function _ip_type (line 41) | def _ip_type(address: int | str) -> int | None:
  class BaseWriteSync (line 55) | class BaseWriteSync(ABC):
    method write (line 61) | def write(self, data: Connection | str | bytearray | bytes) -> None:
    method __repr__ (line 64) | def __repr__(self) -> str:
    method _pack (line 68) | def _pack(format_: str, data: int) -> bytes:
    method write_varint (line 72) | def write_varint(self, value: int) -> None:
    method write_varlong (line 89) | def write_varlong(self, value: int) -> None:
    method write_utf (line 106) | def write_utf(self, value: str) -> None:
    method write_ascii (line 111) | def write_ascii(self, value: str) -> None:
    method write_short (line 116) | def write_short(self, value: int) -> None:
    method write_ushort (line 120) | def write_ushort(self, value: int) -> None:
    method write_int (line 124) | def write_int(self, value: int) -> None:
    method write_uint (line 128) | def write_uint(self, value: int) -> None:
    method write_long (line 132) | def write_long(self, value: int) -> None:
    method write_ulong (line 136) | def write_ulong(self, value: int) -> None:
    method write_bool (line 140) | def write_bool(self, value: bool) -> None:  # noqa: FBT001 # Boolean p...
    method write_buffer (line 144) | def write_buffer(self, buffer: Connection) -> None:
  class BaseWriteAsync (line 151) | class BaseWriteAsync(ABC):
    method write (line 157) | async def write(self, data: Connection | str | bytearray | bytes) -> N...
    method __repr__ (line 160) | def __repr__(self) -> str:
    method _pack (line 164) | def _pack(format_: str, data: int) -> bytes:
    method write_varint (line 168) | async def write_varint(self, value: int) -> None:
    method write_varlong (line 185) | async def write_varlong(self, value: int) -> None:
    method write_utf (line 202) | async def write_utf(self, value: str) -> None:
    method write_ascii (line 207) | async def write_ascii(self, value: str) -> None:
    method write_short (line 212) | async def write_short(self, value: int) -> None:
    method write_ushort (line 216) | async def write_ushort(self, value: int) -> None:
    method write_int (line 220) | async def write_int(self, value: int) -> None:
    method write_uint (line 224) | async def write_uint(self, value: int) -> None:
    method write_long (line 228) | async def write_long(self, value: int) -> None:
    method write_ulong (line 232) | async def write_ulong(self, value: int) -> None:
    method write_bool (line 236) | async def write_bool(self, value: bool) -> None:  # noqa: FBT001 # Boo...
    method write_buffer (line 240) | async def write_buffer(self, buffer: Connection) -> None:
  class BaseReadSync (line 247) | class BaseReadSync(ABC):
    method read (line 253) | def read(self, length: int, /) -> bytearray:
    method __repr__ (line 256) | def __repr__(self) -> str:
    method _unpack (line 260) | def _unpack(format_: str, data: bytes) -> int:
    method read_varint (line 264) | def read_varint(self) -> int:
    method read_varlong (line 278) | def read_varlong(self) -> int:
    method read_utf (line 292) | def read_utf(self) -> str:
    method read_ascii (line 297) | def read_ascii(self) -> str:
    method read_short (line 304) | def read_short(self) -> int:
    method read_ushort (line 308) | def read_ushort(self) -> int:
    method read_int (line 312) | def read_int(self) -> int:
    method read_uint (line 316) | def read_uint(self) -> int:
    method read_long (line 320) | def read_long(self) -> int:
    method read_ulong (line 324) | def read_ulong(self) -> int:
    method read_bool (line 328) | def read_bool(self) -> bool:
    method read_buffer (line 332) | def read_buffer(self) -> Connection:
  class BaseReadAsync (line 340) | class BaseReadAsync(ABC):
    method read (line 346) | async def read(self, length: int, /) -> bytearray:
    method __repr__ (line 349) | def __repr__(self) -> str:
    method _unpack (line 353) | def _unpack(format_: str, data: bytes) -> int:
    method read_varint (line 357) | async def read_varint(self) -> int:
    method read_varlong (line 371) | async def read_varlong(self) -> int:
    method read_utf (line 385) | async def read_utf(self) -> str:
    method read_ascii (line 390) | async def read_ascii(self) -> str:
    method read_short (line 397) | async def read_short(self) -> int:
    method read_ushort (line 401) | async def read_ushort(self) -> int:
    method read_int (line 405) | async def read_int(self) -> int:
    method read_uint (line 409) | async def read_uint(self) -> int:
    method read_long (line 413) | async def read_long(self) -> int:
    method read_ulong (line 417) | async def read_ulong(self) -> int:
    method read_bool (line 421) | async def read_bool(self) -> bool:
    method read_buffer (line 425) | async def read_buffer(self) -> Connection:
  class BaseConnection (line 433) | class BaseConnection:
    method __repr__ (line 438) | def __repr__(self) -> str:
    method flush (line 441) | def flush(self) -> bytearray:
    method receive (line 445) | def receive(self, _data: BytesConvertable | bytearray) -> None:
    method remaining (line 449) | def remaining(self) -> int:
  class BaseSyncConnection (line 454) | class BaseSyncConnection(BaseConnection, BaseReadSync, BaseWriteSync):
  class BaseAsyncReadSyncWriteConnection (line 460) | class BaseAsyncReadSyncWriteConnection(BaseConnection, BaseReadAsync, Ba...
  class BaseAsyncConnection (line 466) | class BaseAsyncConnection(BaseConnection, BaseReadAsync, BaseWriteAsync):
  class Connection (line 472) | class Connection(BaseSyncConnection):
    method __init__ (line 477) | def __init__(self) -> None:
    method read (line 481) | def read(self, length: int, /) -> bytearray:
    method write (line 490) | def write(self, data: Connection | str | bytearray | bytes) -> None:
    method receive (line 498) | def receive(self, data: BytesConvertable | bytearray) -> None:
    method remaining (line 504) | def remaining(self) -> int:
    method flush (line 508) | def flush(self) -> bytearray:
    method copy (line 513) | def copy(self) -> Connection:
  class SocketConnection (line 521) | class SocketConnection(BaseSyncConnection):
    method __init__ (line 526) | def __init__(self) -> None:
    method close (line 530) | def close(self) -> None:
    method __enter__ (line 541) | def __enter__(self) -> Self:
    method __exit__ (line 544) | def __exit__(self, *_: object) -> None:
  class TCPSocketConnection (line 548) | class TCPSocketConnection(SocketConnection):
    method __init__ (line 553) | def __init__(self, addr: tuple[str | None, int], timeout: float = 3) -...
    method read (line 558) | def read(self, length: int, /) -> bytearray:
    method write (line 568) | def write(self, data: Connection | str | bytes | bytearray) -> None:
  class UDPSocketConnection (line 577) | class UDPSocketConnection(SocketConnection):
    method __init__ (line 582) | def __init__(self, addr: Address, timeout: float = 3) -> None:
    method remaining (line 591) | def remaining(self) -> int:
    method read (line 595) | def read(self, _length: int, /) -> bytearray:
    method write (line 602) | def write(self, data: Connection | str | bytes | bytearray) -> None:
  class TCPAsyncSocketConnection (line 611) | class TCPAsyncSocketConnection(BaseAsyncReadSyncWriteConnection):
    method __init__ (line 616) | def __init__(self, addr: Address, timeout: float = 3) -> None:
    method connect (line 623) | async def connect(self) -> None:
    method read (line 631) | async def read(self, length: int, /) -> bytearray:
    method write (line 641) | def write(self, data: Connection | str | bytes | bytearray) -> None:
    method close (line 649) | def close(self) -> None:
    method __aenter__ (line 654) | async def __aenter__(self) -> Self:
    method __aexit__ (line 658) | async def __aexit__(self, *_: object) -> None:
  class UDPAsyncSocketConnection (line 662) | class UDPAsyncSocketConnection(BaseAsyncConnection):
    method __init__ (line 667) | def __init__(self, addr: Address, timeout: float = 3) -> None:
    method connect (line 673) | async def connect(self) -> None:
    method remaining (line 678) | def remaining(self) -> int:
    method read (line 682) | async def read(self, _length: int, /) -> bytearray:
    method write (line 687) | async def write(self, data: Connection | str | bytes | bytearray) -> N...
    method close (line 695) | def close(self) -> None:
    method __aenter__ (line 700) | async def __aenter__(self) -> Self:
    method __aexit__ (line 704) | async def __aexit__(self, *_: object) -> None:

FILE: mcstatus/_protocol/java_client.py
  class _BaseJavaClient (line 23) | class _BaseJavaClient(ABC):
    method __post_init__ (line 31) | def __post_init__(self) -> None:
    method handshake (line 35) | def handshake(self) -> None:
    method read_status (line 47) | def read_status(self) -> JavaStatusResponse | Awaitable[JavaStatusResp...
    method test_ping (line 52) | def test_ping(self) -> float | Awaitable[float]:
    method _handle_status_response (line 56) | def _handle_status_response(self, response: Connection, start: float, ...
    method _handle_ping_response (line 71) | def _handle_ping_response(self, response: Connection, start: float, en...
  class JavaClient (line 83) | class JavaClient(_BaseJavaClient):
    method read_status (line 86) | def read_status(self) -> JavaStatusResponse:
    method test_ping (line 97) | def test_ping(self) -> float:
  class AsyncJavaClient (line 112) | class AsyncJavaClient(_BaseJavaClient):
    method read_status (line 115) | async def read_status(self) -> JavaStatusResponse:
    method test_ping (line 126) | async def test_ping(self) -> float:

FILE: mcstatus/_protocol/legacy_client.py
  class _BaseLegacyClient (line 9) | class _BaseLegacyClient:
    method parse_response (line 16) | def parse_response(data: bytes, latency: float) -> LegacyStatusResponse:
  class LegacyClient (line 27) | class LegacyClient(_BaseLegacyClient):
    method __init__ (line 28) | def __init__(self, connection: BaseSyncConnection) -> None:
    method read_status (line 31) | def read_status(self) -> LegacyStatusResponse:
  class AsyncLegacyClient (line 44) | class AsyncLegacyClient(_BaseLegacyClient):
    method __init__ (line 45) | def __init__(self, connection: BaseAsyncReadSyncWriteConnection) -> None:
    method read_status (line 48) | async def read_status(self) -> LegacyStatusResponse:

FILE: mcstatus/_protocol/query_client.py
  class _BaseQueryClient (line 21) | class _BaseQueryClient:
    method _generate_session_id (line 31) | def _generate_session_id() -> int:
    method _create_packet (line 35) | def _create_packet(self) -> Connection:
    method _create_handshake_packet (line 44) | def _create_handshake_packet(self) -> Connection:
    method _read_packet (line 52) | def _read_packet(self) -> Connection | Awaitable[Connection]:
    method handshake (line 56) | def handshake(self) -> None | Awaitable[None]:
    method read_query (line 60) | def read_query(self) -> QueryResponse | Awaitable[QueryResponse]:
    method _parse_response (line 63) | def _parse_response(self, response: Connection) -> tuple[RawQueryRespo...
  class QueryClient (line 105) | class QueryClient(_BaseQueryClient):
    method _read_packet (line 108) | def _read_packet(self) -> Connection:
    method handshake (line 114) | def handshake(self) -> None:
    method read_query (line 120) | def read_query(self) -> QueryResponse:
  class AsyncQueryClient (line 130) | class AsyncQueryClient(_BaseQueryClient):
    method _read_packet (line 133) | async def _read_packet(self) -> Connection:
    method handshake (line 139) | async def handshake(self) -> None:
    method read_query (line 145) | async def read_query(self) -> QueryResponse:

FILE: mcstatus/_utils/deprecation.py
  function _get_project_version (line 63) | def _get_project_version() -> tuple[int, int, int]:
  function deprecation_warn (line 121) | def deprecation_warn(
  class DecoratorFunction (line 176) | class DecoratorFunction(Protocol):
    method __call__ (line 177) | def __call__(self, /, func: Callable[P, R]) -> Callable[P, R]: ...
  function deprecated (line 180) | def deprecated(

FILE: mcstatus/_utils/general.py
  function or_none (line 9) | def or_none(*args: T) -> T | None:

FILE: mcstatus/_utils/retry.py
  function retry (line 18) | def retry(tries: int, exceptions: tuple[type[BaseException]] = (Exceptio...

FILE: mcstatus/motd/__init__.py
  class Motd (line 22) | class Motd:
    method parse (line 36) | def parse(
    method _parse_as_str (line 62) | def _parse_as_str(raw: str, *, bedrock: bool = False) -> list[ParsedMo...
    method _parse_as_dict (line 98) | def _parse_as_dict(
    method _parse_color (line 149) | def _parse_color(color: str) -> ParsedMotdComponent:
    method simplify (line 171) | def simplify(self) -> Self:
    method to_plain (line 191) | def to_plain(self) -> str:
    method to_minecraft (line 199) | def to_minecraft(self) -> str:
    method to_html (line 212) | def to_html(self) -> str:
    method to_ansi (line 262) | def to_ansi(self) -> str:

FILE: mcstatus/motd/_simplifies.py
  function get_unused_elements (line 24) | def get_unused_elements(parsed: Sequence[ParsedMotdComponent]) -> set[int]:
  function squash_nearby_strings (line 44) | def squash_nearby_strings(parsed: _PARSED_MOTD_COMPONENTS_TYPEVAR) -> _P...
  function get_double_items (line 71) | def get_double_items(parsed: Sequence[ParsedMotdComponent]) -> set[int]:
  function get_double_colors (line 90) | def get_double_colors(parsed: Sequence[ParsedMotdComponent]) -> set[int]:
  function get_formatting_before_color (line 115) | def get_formatting_before_color(parsed: Sequence[ParsedMotdComponent]) -...
  function get_empty_text (line 149) | def get_empty_text(parsed: Sequence[ParsedMotdComponent]) -> set[int]:
  function get_end_non_text (line 163) | def get_end_non_text(parsed: Sequence[ParsedMotdComponent]) -> set[int]:
  function get_meaningless_resets_and_colors (line 185) | def get_meaningless_resets_and_colors(parsed: Sequence[ParsedMotdCompone...

FILE: mcstatus/motd/_transformers.py
  class _BaseTransformer (line 65) | class _BaseTransformer(abc.ABC, t.Generic[_HOOK_RETURN_TYPE, _END_RESULT...
    method transform (line 75) | def transform(self, motd_components: Sequence[ParsedMotdComponent]) ->...
    method _format_output (line 79) | def _format_output(self, results: list[_HOOK_RETURN_TYPE]) -> _END_RES...
    method _handle_component (line 81) | def _handle_component(
    method _handle_str (line 99) | def _handle_str(self, element: str, /) -> _HOOK_RETURN_TYPE: ...
    method _handle_translation_tag (line 102) | def _handle_translation_tag(self, _: TranslationTag, /) -> _HOOK_RETUR...
    method _handle_web_color (line 105) | def _handle_web_color(self, element: WebColor, /) -> _HOOK_RETURN_TYPE...
    method _handle_formatting (line 108) | def _handle_formatting(self, element: Formatting, /) -> _HOOK_RETURN_T...
    method _handle_minecraft_color (line 111) | def _handle_minecraft_color(self, element: MinecraftColor, /) -> _HOOK...
  class _NothingTransformer (line 114) | class _NothingTransformer(_BaseTransformer[str, str]):
    method _format_output (line 120) | def _format_output(self, results: list[str]) -> str:
    method _handle_str (line 123) | def _handle_str(self, _element: str, /) -> str:
    method _handle_minecraft_color (line 126) | def _handle_minecraft_color(self, _element: MinecraftColor, /) -> str:
    method _handle_web_color (line 129) | def _handle_web_color(self, _element: WebColor, /) -> str:
    method _handle_formatting (line 132) | def _handle_formatting(self, _element: Formatting, /) -> str:
    method _handle_translation_tag (line 135) | def _handle_translation_tag(self, _element: TranslationTag, /) -> str:
  class PlainTransformer (line 139) | class PlainTransformer(_NothingTransformer):
    method _handle_str (line 140) | def _handle_str(self, element: str, /) -> str:
  class MinecraftTransformer (line 144) | class MinecraftTransformer(PlainTransformer):
    method _handle_component (line 145) | def _handle_component(self, component: ParsedMotdComponent) -> tuple[s...
    method _handle_minecraft_color (line 151) | def _handle_minecraft_color(self, element: MinecraftColor, /) -> str:
    method _handle_formatting (line 154) | def _handle_formatting(self, element: Formatting, /) -> str:
  class HtmlTransformer (line 158) | class HtmlTransformer(PlainTransformer):
    method __init__ (line 167) | def __init__(self, *, bedrock: bool = False) -> None:
    method transform (line 171) | def transform(self, motd_components: Sequence[ParsedMotdComponent]) ->...
    method _format_output (line 175) | def _format_output(self, results: list[str]) -> str:
    method _handle_str (line 178) | def _handle_str(self, element: str, /) -> str:
    method _handle_minecraft_color (line 181) | def _handle_minecraft_color(self, element: MinecraftColor, /) -> str:
    method _handle_web_color (line 188) | def _handle_web_color(self, element: WebColor, /) -> str:
    method _handle_formatting (line 192) | def _handle_formatting(self, element: Formatting, /) -> str:
  class AnsiTransformer (line 207) | class AnsiTransformer(PlainTransformer):
    method __init__ (line 223) | def __init__(self, *, bedrock: bool = True) -> None:
    method ansi_color (line 226) | def ansi_color(self, color: tuple[int, int, int] | MinecraftColor) -> ...
    method _format_output (line 234) | def _format_output(self, results: list[str]) -> str:
    method _handle_minecraft_color (line 237) | def _handle_minecraft_color(self, element: MinecraftColor, /) -> str:
    method _handle_web_color (line 240) | def _handle_web_color(self, element: WebColor, /) -> str:
    method _handle_formatting (line 243) | def _handle_formatting(self, element: Formatting, /) -> str:

FILE: mcstatus/motd/components.py
  class Formatting (line 25) | class Formatting(Enum):
  class MinecraftColor (line 44) | class MinecraftColor(Enum):
  class WebColor (line 84) | class WebColor:
    method from_hex (line 96) | def from_hex(cls, hex: str) -> Self:  # noqa: A002 # shadowing a hex b...
    method from_rgb (line 117) | def from_rgb(cls, rgb: tuple[int, int, int]) -> Self:
    method _check_rgb (line 129) | def _check_rgb(rgb: tuple[int, int, int]) -> None:
  class TranslationTag (line 139) | class TranslationTag:

FILE: mcstatus/responses/_raw.py
  class RawForgeDataChannel (line 24) | class RawForgeDataChannel(TypedDict):
  class RawForgeDataMod (line 33) | class RawForgeDataMod(TypedDict, total=False):
  class RawForgeData (line 41) | class RawForgeData(TypedDict, total=False):
  class RawJavaResponsePlayer (line 50) | class RawJavaResponsePlayer(TypedDict):
  class RawJavaResponsePlayers (line 55) | class RawJavaResponsePlayers(TypedDict):
  class RawJavaResponseVersion (line 61) | class RawJavaResponseVersion(TypedDict):
  class RawJavaResponseMotdWhenDict (line 66) | class RawJavaResponseMotdWhenDict(TypedDict, total=False):
  class RawJavaResponse (line 79) | class RawJavaResponse(TypedDict):
  class RawQueryResponse (line 89) | class RawQueryResponse(TypedDict):

FILE: mcstatus/responses/base.py
  class BaseStatusResponse (line 20) | class BaseStatusResponse(ABC):
    method description (line 36) | def description(self) -> str:
    method build (line 42) | def build(cls, *args: Any, **kwargs: Any) -> Self:
    method as_dict (line 51) | def as_dict(self) -> dict[str, Any]:
  class BaseStatusPlayers (line 72) | class BaseStatusPlayers(ABC):
  class BaseStatusVersion (line 82) | class BaseStatusVersion(ABC):

FILE: mcstatus/responses/bedrock.py
  class BedrockStatusResponse (line 21) | class BedrockStatusResponse(BaseStatusResponse):
    method build (line 32) | def build(cls, decoded_data: list[Any], latency: float) -> Self:
  class BedrockStatusPlayers (line 66) | class BedrockStatusPlayers(BaseStatusPlayers):
  class BedrockStatusVersion (line 71) | class BedrockStatusVersion(BaseStatusVersion):
    method version (line 85) | def version(self) -> str:

FILE: mcstatus/responses/forge.py
  class ForgeDataChannel (line 40) | class ForgeDataChannel:
    method build (line 51) | def build(cls, raw: RawForgeDataChannel) -> Self:
    method decode (line 60) | def decode(cls, buffer: Connection, mod_id: str | None = None) -> Self:
  class ForgeDataMod (line 81) | class ForgeDataMod:
    method build (line 90) | def build(cls, raw: RawForgeDataMod) -> Self:
    method decode (line 109) | def decode(cls, buffer: Connection) -> tuple[Self, list[ForgeDataChann...
  class _StringBuffer (line 130) | class _StringBuffer(BaseReadSync, BaseConnection):
    method __init__ (line 135) | def __init__(self, stringio: StringIO) -> None:
    method read (line 139) | def read(self, length: int) -> bytearray:
    method remaining (line 153) | def remaining(self) -> int:
    method read_optimized_size (line 157) | def read_optimized_size(self) -> int:
    method read_optimized_buffer (line 161) | def read_optimized_buffer(self) -> Connection:
  class ForgeData (line 180) | class ForgeData:
    method _decode_optimized (line 193) | def _decode_optimized(string: str) -> Connection:
    method build (line 200) | def build(cls, raw: RawForgeData) -> Self:

FILE: mcstatus/responses/java.py
  class JavaStatusResponse (line 24) | class JavaStatusResponse(BaseStatusResponse):
    method build (line 52) | def build(cls, raw: RawJavaResponse, latency: float = 0) -> Self:
  class JavaStatusPlayers (line 80) | class JavaStatusPlayers(BaseStatusPlayers):
    method build (line 96) | def build(cls, raw: RawJavaResponsePlayers) -> Self:
  class JavaStatusPlayer (line 117) | class JavaStatusPlayer:
    method uuid (line 126) | def uuid(self) -> str:
    method build (line 131) | def build(cls, raw: RawJavaResponsePlayer) -> Self:
  class JavaStatusVersion (line 144) | class JavaStatusVersion(BaseStatusVersion):
    method build (line 148) | def build(cls, raw: RawJavaResponseVersion) -> Self:

FILE: mcstatus/responses/legacy.py
  class LegacyStatusResponse (line 20) | class LegacyStatusResponse(BaseStatusResponse):
    method build (line 28) | def build(cls, decoded_data: list[str], latency: float) -> Self:
  class LegacyStatusPlayers (line 50) | class LegacyStatusPlayers(BaseStatusPlayers):
  class LegacyStatusVersion (line 55) | class LegacyStatusVersion(BaseStatusVersion):

FILE: mcstatus/responses/query.py
  class QueryResponse (line 22) | class QueryResponse:
    method build (line 51) | def build(cls, raw: RawQueryResponse, players_list: list[str]) -> Self:
    method as_dict (line 64) | def as_dict(self) -> dict[str, Any]:
    method map (line 87) | def map(self) -> str | None:
  class QueryPlayers (line 96) | class QueryPlayers:
    method build (line 107) | def build(cls, raw: RawQueryResponse, players_list: list[str]) -> Self:
    method names (line 116) | def names(self) -> list[str]:
  class QuerySoftware (line 125) | class QuerySoftware:
    method build (line 136) | def build(cls, version: str, plugins: str) -> Self:
    method _parse_plugins (line 145) | def _parse_plugins(plugins: str) -> tuple[str, list[str]]:

FILE: mcstatus/server.py
  class MCServer (line 28) | class MCServer(ABC):
    method __init__ (line 37) | def __init__(self, host: str, port: int | None = None, timeout: float ...
    method lookup (line 49) | def lookup(cls, address: str, timeout: float = 3) -> Self:
  class BaseJavaServer (line 59) | class BaseJavaServer(MCServer):
    method lookup (line 68) | def lookup(cls, address: str, timeout: float = 3) -> Self:
    method async_lookup (line 83) | async def async_lookup(cls, address: str, timeout: float = 3) -> Self:
  class JavaServer (line 92) | class JavaServer(BaseJavaServer):
    method __init__ (line 95) | def __init__(self, host: str, port: int | None = None, timeout: float ...
    method ping (line 108) | def ping(self, *, tries: int = 3, version: int = 47, ping_token: int |...
    method _retry_ping (line 125) | def _retry_ping(
    method async_ping (line 142) | async def async_ping(self, *, tries: int = 3, version: int = 47, ping_...
    method _retry_async_ping (line 159) | async def _retry_async_ping(
    method status (line 177) | def status(self, *, tries: int = 3, version: int = 47, ping_token: int...
    method _retry_status (line 189) | def _retry_status(
    method async_status (line 207) | async def async_status(self, *, tries: int = 3, version: int = 47, pin...
    method _retry_async_status (line 219) | async def _retry_async_status(
    method query (line 237) | def query(self, *, tries: int = 3) -> QueryResponse:
    method _retry_query (line 247) | def _retry_query(self, addr: Address, tries: int = 3) -> QueryResponse...
    method async_query (line 253) | async def async_query(self, *, tries: int = 3) -> QueryResponse:
    method _retry_async_query (line 263) | async def _retry_async_query(self, address: Address, tries: int = 3) -...
  class LegacyServer (line 270) | class LegacyServer(BaseJavaServer):
    method status (line 277) | def status(self, *, tries: int = 3) -> LegacyStatusResponse:  # noqa: ...
    method async_status (line 287) | async def async_status(self, *, tries: int = 3) -> LegacyStatusRespons...
  class BedrockServer (line 297) | class BedrockServer(MCServer):
    method status (line 303) | def status(self, *, tries: int = 3) -> BedrockStatusResponse:  # noqa:...
    method async_status (line 312) | async def async_status(self, *, tries: int = 3) -> BedrockStatusRespon...

FILE: tests/helpers.py
  function patch_project_version (line 11) | def patch_project_version(version: str | None) -> c.Iterator[None]:

FILE: tests/motd/conftest.py
  function source_java (line 5) | def source_java() -> dict:
  function source_bedrock (line 40) | def source_bedrock() -> dict:

FILE: tests/motd/test_components.py
  class TestWebColor (line 8) | class TestWebColor:
    method test_hex_to_rgb_correct (line 17) | def test_hex_to_rgb_correct(self, hex_, rgb):
    method test_rgb_to_hex_correct (line 28) | def test_rgb_to_hex_correct(self, hex_, rgb):
    method test_hex_in_output_has_number_sign (line 31) | def test_hex_in_output_has_number_sign(self):
    method test_fail_on_incorrect_hex (line 35) | def test_fail_on_incorrect_hex(self):
    method test_fail_on_too_long_or_too_short_hex (line 40) | def test_fail_on_too_long_or_too_short_hex(self, length: int):
    method test_fail_on_incorrect_rgb (line 45) | def test_fail_on_incorrect_rgb(self):
    method test_3_symbols_hex (line 49) | def test_3_symbols_hex(self):

FILE: tests/motd/test_motd.py
  class TestMotdParse (line 10) | class TestMotdParse:
    method test_correct_result (line 11) | def test_correct_result(self, source_bedrock):
    method test_bedrock_parameter_nothing_changes (line 53) | def test_bedrock_parameter_nothing_changes(self, bedrock: bool):
    method test_parse_as_str_ignore_minecoin_gold_on_java (line 62) | def test_parse_as_str_ignore_minecoin_gold_on_java(self, bedrock: bool...
    method test_parse_incorrect_color_passes (line 65) | def test_parse_incorrect_color_passes(self):
    method test_parse_uppercase_passes (line 69) | def test_parse_uppercase_passes(self):
    method test_empty_input_also_empty_raw (line 75) | def test_empty_input_also_empty_raw(self, input_, expected):
    method test_top_level_formatting_applies_to_all_in_extra (line 78) | def test_top_level_formatting_applies_to_all_in_extra(self) -> None:
    method test_top_level_formatting_can_be_overwrote (line 90) | def test_top_level_formatting_can_be_overwrote(self) -> None:
    method test_top_level_formatting_applies_to_string_inside_extra (line 103) | def test_top_level_formatting_applies_to_string_inside_extra(self) -> ...
    method test_formatting_key_set_to_false_here_without_it_being_set_to_true_before (line 116) | def test_formatting_key_set_to_false_here_without_it_being_set_to_true...
    method test_translate_string (line 127) | def test_translate_string(self):
    method test_short_text_is_not_considered_as_color (line 133) | def test_short_text_is_not_considered_as_color(self):
    method test_text_field_contains_formatting (line 137) | def test_text_field_contains_formatting(self):
    method test_invalid_raw_input (line 141) | def test_invalid_raw_input(self):
    method test_invalid_color (line 149) | def test_invalid_color(self):
    method test_multiple_times_nested_extras (line 153) | def test_multiple_times_nested_extras(self):
    method test_raw_attribute (line 204) | def test_raw_attribute(self, source_bedrock):

FILE: tests/motd/test_simplifies.py
  class TestMotdSimplifies (line 21) | class TestMotdSimplifies:
    method test_get_unused_elements_call_every_simplifier (line 22) | def test_get_unused_elements_call_every_simplifier(self):
    method test_simplify_returns_new_instance (line 40) | def test_simplify_returns_new_instance(self):
    method test_simplifies_work (line 46) | def test_simplifies_work(self):
    method test_simplify_runs_few_times (line 49) | def test_simplify_runs_few_times(self):
    method test_get_double_colors (line 56) | def test_get_double_colors(self, first, second):
    method test_get_double_colors_with_three_items (line 62) | def test_get_double_colors_with_three_items(self, first, second, third):
    method test_get_double_colors_with_no_double_colors (line 67) | def test_get_double_colors_with_no_double_colors(self, first, second):
    method test_get_double_items (line 71) | def test_get_double_items(self, item):
    method test_get_double_items_with_three_items (line 75) | def test_get_double_items_with_three_items(self, item):
    method test_get_double_items_with_no_double_items (line 79) | def test_get_double_items_with_no_double_items(self, item):
    method test_get_formatting_before_color (line 83) | def test_get_formatting_before_color(self, last_item):
    method test_get_formatting_before_color_without_formatting_before_color (line 87) | def test_get_formatting_before_color_without_formatting_before_color(s...
    method test_skip_get_formatting_before_color (line 90) | def test_skip_get_formatting_before_color(self):
    method test_get_formatting_before_color_if_space_between (line 94) | def test_get_formatting_before_color_if_space_between(self, last_item):
    method test_get_empty_text_removes_empty_string (line 97) | def test_get_empty_text_removes_empty_string(self):
    method test_two_formattings_before_minecraft_color (line 100) | def test_two_formattings_before_minecraft_color(self):
    method test_two_formattings_one_by_one (line 104) | def test_two_formattings_one_by_one(self):
    method test_dont_remove_empty_text (line 109) | def test_dont_remove_empty_text(self, item):
    method test_non_text_in_the_end (line 113) | def test_non_text_in_the_end(self, last_item):
    method test_translation_tag_in_the_end (line 116) | def test_translation_tag_in_the_end(self):
    method test_meaningless_resets_and_colors_active (line 120) | def test_meaningless_resets_and_colors_active(self, item):
    method test_meaningless_resets_and_colors_reset_nothing (line 123) | def test_meaningless_resets_and_colors_reset_nothing(self):
    method test_meaningless_resets_and_colors_resets (line 127) | def test_meaningless_resets_and_colors_resets(self, item):
    method test_no_conflict_on_poping_items (line 130) | def test_no_conflict_on_poping_items(self):
    method test_simplify_function_provides_the_same_raw (line 153) | def test_simplify_function_provides_the_same_raw(self):
    method test_simplify_do_not_remove_string_contains_only_spaces (line 157) | def test_simplify_do_not_remove_string_contains_only_spaces(self):
    method test_simplify_meaningless_resets_and_colors (line 161) | def test_simplify_meaningless_resets_and_colors(self):
    method test_remove_formatting_reset_if_there_was_no_color_or_formatting (line 164) | def test_remove_formatting_reset_if_there_was_no_color_or_formatting(s...
    method test_squash_nearby_strings (line 169) | def test_squash_nearby_strings(self):

FILE: tests/motd/test_transformers.py
  function test_nothing_transformer (line 17) | def test_nothing_transformer():
  class TestMotdPlain (line 21) | class TestMotdPlain:
    method result (line 23) | def result(self) -> Callable[[str | RawJavaResponseMotd], str]:
    method test_plain_text (line 26) | def test_plain_text(self, result):
    method test_removes_colors (line 29) | def test_removes_colors(self, result):
    method test_skip_web_colors (line 32) | def test_skip_web_colors(self, result):
    method test_skip_minecraft_colors (line 35) | def test_skip_minecraft_colors(self, result):
  class TestMotdMinecraft (line 39) | class TestMotdMinecraft:
    method result (line 41) | def result(self) -> Callable[[str | RawJavaResponseMotd], str]:
    method test_return_the_same (line 45) | def test_return_the_same(self, motd: str, result):
    method test_skip_web_colors (line 48) | def test_skip_web_colors(self, result):
  class TestMotdHTML (line 52) | class TestMotdHTML:
    method result (line 54) | def result(self) -> Callable[[str, bool], str]:
    method test_correct_output_java (line 57) | def test_correct_output_java(self, result: Callable[[str | dict, bool]...
    method test_correct_output_bedrock (line 82) | def test_correct_output_bedrock(self, result: Callable[[str | dict, bo...
    method test_new_line_is_br_tag (line 119) | def test_new_line_is_br_tag(self):
  class TestMotdAnsi (line 124) | class TestMotdAnsi:
    method result (line 126) | def result(self) -> Callable[[str, bool], str]:
    method test_correct_output_java (line 129) | def test_correct_output_java(self, result: Callable[[str | dict, bool]...
    method test_correct_output_bedrock (line 155) | def test_correct_output_bedrock(self, result: Callable[[str | dict, bo...

FILE: tests/net/test_address.py
  class TestSRVLookup (line 16) | class TestSRVLookup:
    method test_address_no_srv (line 18) | def test_address_no_srv(self, exception):
    method test_address_no_srv_no_default_port (line 28) | def test_address_no_srv_no_default_port(self, exception):
    method test_address_with_srv (line 35) | def test_address_with_srv(self):
    method test_async_address_no_srv (line 49) | async def test_async_address_no_srv(self, exception):
    method test_async_address_no_srv_no_default_port (line 60) | async def test_async_address_no_srv_no_default_port(self, exception):
    method test_async_address_with_srv (line 68) | async def test_async_address_with_srv(self):
  class TestAddressValidity (line 81) | class TestAddressValidity:
    method test_address_validation_valid (line 91) | def test_address_validation_valid(self, address, port):
    method test_address_validation_range (line 101) | def test_address_validation_range(self, address, port):
    method test_address_validation_port_invalid_type (line 105) | def test_address_validation_port_invalid_type(self):
    method test_address_validation_host_invalid_type (line 113) | def test_address_validation_host_invalid_type(self, address, port):
    method test_address_host_invalid_format (line 117) | def test_address_host_invalid_format(self):
  class TestAddressConstructing (line 122) | class TestAddressConstructing:
    method test_init_constructor (line 123) | def test_init_constructor(self):
    method test_tuple_behavior (line 128) | def test_tuple_behavior(self):
    method test_from_tuple_constructor (line 135) | def test_from_tuple_constructor(self):
    method test_from_path_constructor (line 140) | def test_from_path_constructor(self):
    method test_address_with_port_no_default (line 145) | def test_address_with_port_no_default(self):
    method test_address_with_port_default (line 150) | def test_address_with_port_default(self):
    method test_address_without_port_default (line 155) | def test_address_without_port_default(self):
    method test_address_without_port (line 160) | def test_address_without_port(self):
    method test_address_with_invalid_port (line 167) | def test_address_with_invalid_port(self):
    method test_address_with_multiple_ports (line 171) | def test_address_with_multiple_ports(self):
  class TestAddressIPResolving (line 176) | class TestAddressIPResolving:
    method setup_method (line 177) | def setup_method(self):
    method test_ip_resolver_with_hostname (line 182) | def test_ip_resolver_with_hostname(self):
    method test_async_ip_resolver_with_hostname (line 195) | async def test_async_ip_resolver_with_hostname(self):
    method test_ip_resolver_cache (line 208) | def test_ip_resolver_cache(self, ip_version: str):
    method test_async_ip_resolver_cache (line 215) | async def test_async_ip_resolver_cache(self, ip_version: str):
    method test_ip_resolver_with_ipv4 (line 222) | def test_ip_resolver_with_ipv4(self):
    method test_async_ip_resolver_with_ipv4 (line 231) | async def test_async_ip_resolver_with_ipv4(self):
    method test_ip_resolver_with_ipv6 (line 239) | def test_ip_resolver_with_ipv6(self):
    method test_async_ip_resolver_with_ipv6 (line 248) | async def test_async_ip_resolver_with_ipv6(self):
    method test_resolve_localhost (line 256) | def test_resolve_localhost(self):
    method test_async_resolve_localhost (line 264) | async def test_async_resolve_localhost(self):

FILE: tests/protocol/test_async_support.py
  function test_is_completely_asynchronous (line 6) | def test_is_completely_asynchronous():
  function test_query_is_completely_asynchronous (line 16) | def test_query_is_completely_asynchronous():

FILE: tests/protocol/test_bedrock_client.py
  function test_bedrock_response_is_expected_type (line 12) | def test_bedrock_response_is_expected_type():
  function test_latency_is_real_number (line 24) | def test_latency_is_real_number():
  function test_async_latency_is_real_number (line 46) | async def test_async_latency_is_real_number():

FILE: tests/protocol/test_connection.py
  class TestConnection (line 9) | class TestConnection:
    method setup_method (line 12) | def setup_method(self):
    method test_flush (line 15) | def test_flush(self):
    method test_receive (line 21) | def test_receive(self):
    method test_remaining (line 27) | def test_remaining(self):
    method test_send (line 33) | def test_send(self):
    method test_read (line 39) | def test_read(self):
    method _assert_varint_read_write (line 45) | def _assert_varint_read_write(self, hexstr, value) -> None:
    method test_varint_cases (line 52) | def test_varint_cases(self):
    method test_read_invalid_varint (line 61) | def test_read_invalid_varint(self):
    method test_write_invalid_varint (line 67) | def test_write_invalid_varint(self):
    method test_read_utf (line 73) | def test_read_utf(self):
    method test_write_utf (line 78) | def test_write_utf(self):
    method test_read_empty_utf (line 83) | def test_read_empty_utf(self):
    method test_read_ascii (line 88) | def test_read_ascii(self):
    method test_write_ascii (line 93) | def test_write_ascii(self):
    method test_read_empty_ascii (line 98) | def test_read_empty_ascii(self):
    method test_read_short_negative (line 103) | def test_read_short_negative(self):
    method test_write_short_negative (line 108) | def test_write_short_negative(self):
    method test_read_short_positive (line 113) | def test_read_short_positive(self):
    method test_write_short_positive (line 118) | def test_write_short_positive(self):
    method test_read_ushort_positive (line 123) | def test_read_ushort_positive(self):
    method test_write_ushort_positive (line 128) | def test_write_ushort_positive(self):
    method test_read_int_negative (line 133) | def test_read_int_negative(self):
    method test_write_int_negative (line 138) | def test_write_int_negative(self):
    method test_read_int_positive (line 143) | def test_read_int_positive(self):
    method test_write_int_positive (line 148) | def test_write_int_positive(self):
    method test_read_uint_positive (line 153) | def test_read_uint_positive(self):
    method test_write_uint_positive (line 158) | def test_write_uint_positive(self):
    method test_read_long_negative (line 163) | def test_read_long_negative(self):
    method test_write_long_negative (line 168) | def test_write_long_negative(self):
    method test_read_long_positive (line 173) | def test_read_long_positive(self):
    method test_write_long_positive (line 178) | def test_write_long_positive(self):
    method test_read_ulong_positive (line 183) | def test_read_ulong_positive(self):
    method test_write_ulong_positive (line 188) | def test_write_ulong_positive(self):
    method test_read_bool (line 194) | def test_read_bool(self, as_bytes: str, as_bool: bool) -> None:
    method test_write_bool (line 200) | def test_write_bool(self, as_bytes: str, as_bool: bool) -> None:
    method test_read_buffer (line 205) | def test_read_buffer(self):
    method test_write_buffer (line 212) | def test_write_buffer(self):
    method test_read_empty (line 219) | def test_read_empty(self):
    method test_read_not_enough (line 225) | def test_read_not_enough(self):
  class TestTCPSocketConnection (line 232) | class TestTCPSocketConnection:
    method connection (line 234) | def connection(self):
    method test_flush (line 245) | def test_flush(self, connection):
    method test_receive (line 249) | def test_receive(self, connection):
    method test_remaining (line 253) | def test_remaining(self, connection):
    method test_read (line 257) | def test_read(self, connection):
    method test_read_empty (line 262) | def test_read_empty(self, connection):
    method test_read_not_enough (line 268) | def test_read_not_enough(self, connection):
    method test_write (line 274) | def test_write(self, connection):
  class TestUDPSocketConnection (line 280) | class TestUDPSocketConnection:
    method connection (line 282) | def connection(self):
    method test_flush (line 293) | def test_flush(self, connection):
    method test_receive (line 297) | def test_receive(self, connection):
    method test_remaining (line 301) | def test_remaining(self, connection):
    method test_read (line 304) | def test_read(self, connection):
    method test_read_empty (line 309) | def test_read_empty(self, connection):
    method test_write (line 315) | def test_write(self, connection):

FILE: tests/protocol/test_java_client.py
  class TestJavaClient (line 12) | class TestJavaClient:
    method setup_method (line 13) | def setup_method(self):
    method test_handshake (line 20) | def test_handshake(self):
    method test_read_status (line 25) | def test_read_status(self):
    method test_read_status_invalid_json (line 42) | def test_read_status_invalid_json(self):
    method test_read_status_invalid_reply (line 47) | def test_read_status_invalid_reply(self):
    method test_read_status_invalid_status (line 58) | def test_read_status_invalid_status(self):
    method test_test_ping (line 64) | def test_test_ping(self):
    method test_test_ping_invalid (line 71) | def test_test_ping_invalid(self):
    method test_test_ping_wrong_token (line 78) | def test_test_ping_wrong_token(self):
    method test_latency_is_real_number (line 86) | def test_latency_is_real_number(self):
    method test_test_ping_is_in_milliseconds (line 120) | def test_test_ping_is_in_milliseconds(self):

FILE: tests/protocol/test_java_client_async.py
  function async_decorator (line 13) | def async_decorator(f):
  class FakeAsyncConnection (line 20) | class FakeAsyncConnection(Connection):
    method read_buffer (line 21) | async def read_buffer(self):  # pyright: ignore[reportIncompatibleMeth...
  class TestAsyncJavaClient (line 25) | class TestAsyncJavaClient:
    method setup_method (line 26) | def setup_method(self):
    method test_handshake (line 33) | def test_handshake(self):
    method test_read_status (line 38) | def test_read_status(self):
    method test_read_status_invalid_json (line 55) | def test_read_status_invalid_json(self):
    method test_read_status_invalid_reply (line 60) | def test_read_status_invalid_reply(self):
    method test_read_status_invalid_status (line 70) | def test_read_status_invalid_status(self):
    method test_test_ping (line 76) | def test_test_ping(self):
    method test_test_ping_invalid (line 83) | def test_test_ping_invalid(self):
    method test_test_ping_wrong_token (line 90) | def test_test_ping_wrong_token(self):
    method test_latency_is_real_number (line 99) | async def test_latency_is_real_number(self):
    method test_test_ping_is_in_milliseconds (line 137) | async def test_test_ping_is_in_milliseconds(self):

FILE: tests/protocol/test_legacy_client.py
  function test_invalid_kick_reason (line 9) | def test_invalid_kick_reason():
  function test_parse_response (line 38) | def test_parse_response(response: bytes, expected: LegacyStatusResponse):
  function test_invalid_packet_id (line 42) | def test_invalid_packet_id():

FILE: tests/protocol/test_query_client.py
  class TestQueryClient (line 8) | class TestQueryClient:
    method setup_method (line 9) | def setup_method(self):
    method test_handshake (line 12) | def test_handshake(self):
    method test_query (line 20) | def test_query(self):
    method test_query_handles_unorderd_map_response (line 48) | def test_query_handles_unorderd_map_response(self):
    method test_query_handles_unicode_motd_with_nulls (line 63) | def test_query_handles_unicode_motd_with_nulls(self):
    method test_query_handles_unicode_motd_with_2a00_at_the_start (line 78) | def test_query_handles_unicode_motd_with_2a00_at_the_start(self):
    method test_session_id (line 95) | def test_session_id(self):

FILE: tests/protocol/test_query_client_async.py
  class FakeUDPAsyncConnection (line 6) | class FakeUDPAsyncConnection(Connection):
    method read (line 7) | async def read(self, length):  # pyright: ignore[reportIncompatibleMet...
    method write (line 10) | async def write(self, data):  # pyright: ignore[reportIncompatibleMeth...
  class TestAsyncQueryClient (line 14) | class TestAsyncQueryClient:
    method setup_method (line 15) | def setup_method(self):
    method test_handshake (line 18) | def test_handshake(self):
    method test_query (line 25) | def test_query(self):

FILE: tests/protocol/test_timeout.py
  class FakeAsyncStream (line 12) | class FakeAsyncStream(asyncio.StreamReader):
    method read (line 13) | async def read(self, *args, **kwargs) -> typing.NoReturn:
  function fake_asyncio_asyncio_open_connection (line 18) | async def fake_asyncio_asyncio_open_connection(hostname: str, port: int):
  class TestAsyncSocketConnection (line 22) | class TestAsyncSocketConnection:
    method test_tcp_socket_read (line 24) | async def test_tcp_socket_read(self):

FILE: tests/responses/__init__.py
  class BaseResponseTest (line 15) | class BaseResponseTest(abc.ABC):
    method _validate (line 24) | def _validate(self) -> None:
    method build (line 48) | def build(self) -> BaseStatusResponse: ...
    method test_values_of_attributes (line 52) | def test_values_of_attributes(self, build: BaseStatusResponse, field: ...
    method test_types_of_attributes (line 55) | def test_types_of_attributes(self, build: BaseStatusResponse, field: s...
    method test_attribute_in (line 58) | def test_attribute_in(self, build: BaseStatusResponse, field: str) -> ...
    method test_optional_field_turns_into_none (line 61) | def test_optional_field_turns_into_none(self, build: BaseStatusRespons...
    method _dependency_table (line 66) | def _dependency_table(self) -> dict[str, bool]:
    method _marks_table (line 77) | def _marks_table(self) -> dict[str, tuple[str, tuple[Any, ...]]]:
    method construct (line 95) | def construct(class_: _T) -> _T:

FILE: tests/responses/conftest.py
  function pytest_generate_tests (line 13) | def pytest_generate_tests(metafunc: Metafunc) -> None:
  function pytest_collection_modifyitems (line 25) | def pytest_collection_modifyitems(items: list[Function]) -> None:

FILE: tests/responses/test_base.py
  class TestMCStatusResponse (line 6) | class TestMCStatusResponse:
    method test_raises_not_implemented_error_on_build (line 7) | def test_raises_not_implemented_error_on_build(self):

FILE: tests/responses/test_bedrock.py
  function build (line 12) | def build():
  class TestBedrockStatusResponse (line 34) | class TestBedrockStatusResponse(BaseResponseTest):
    method build (line 47) | def build(self, build):
    method test_optional_parameters_is_none (line 51) | def test_optional_parameters_is_none(self, field, pop_index):
    method test_as_dict (line 71) | def test_as_dict(self, build: BedrockStatusResponse):
    method test_description_alias (line 81) | def test_description_alias(self, build: BedrockStatusResponse):
  class TestBedrockStatusPlayers (line 86) | class TestBedrockStatusPlayers(BaseResponseTest):
    method build (line 90) | def build(self, build):
  class TestBedrockStatusVersion (line 95) | class TestBedrockStatusVersion(BaseResponseTest):
    method build (line 99) | def build(self, build):
    method test_deprecated_version_alias (line 102) | def test_deprecated_version_alias(self, build: BedrockStatusVersion):

FILE: tests/responses/test_forge_data.py
  class TestForgeDataV1 (line 20) | class TestForgeDataV1(BaseResponseTest):
    method build (line 50) | def build(self) -> ForgeData:
  class TestForgeDataV2 (line 55) | class TestForgeDataV2(BaseResponseTest):
    method build (line 75) | def build(self) -> ForgeData:
  class TestForgeDataV3 (line 80) | class TestForgeDataV3(BaseResponseTest):
    method build (line 116) | def build(self) -> ForgeData:
  class TestForgeDataMod (line 120) | class TestForgeDataMod:
    method test_build_with_empty_input (line 121) | def test_build_with_empty_input(self):
    method test_build_without_mod_id (line 125) | def test_build_without_mod_id(self):
  class TestForgeData (line 133) | class TestForgeData(BaseResponseTest):
    method build (line 548) | def build(self) -> ForgeData:
    method test_build_with_empty_input (line 685) | def test_build_with_empty_input(self):
  function test_java_status_response_forge_data_is_none (line 691) | def test_java_status_response_forge_data_is_none(key):
  function test_java_status_response_forge_data (line 705) | def test_java_status_response_forge_data(key: str, raw: bytes) -> None:

FILE: tests/responses/test_java.py
  class TestJavaStatusResponse (line 11) | class TestJavaStatusResponse(BaseResponseTest):
    method build (line 42) | def build(self) -> JavaStatusResponse:
    method test_as_dict (line 45) | def test_as_dict(self, build: JavaStatusResponse):
    method test_description_alias (line 63) | def test_description_alias(self, build: JavaStatusResponse):
  class TestJavaStatusPlayers (line 68) | class TestJavaStatusPlayers(BaseResponseTest):
    method build (line 95) | def build(self) -> JavaStatusPlayers:
    method test_empty_sample_turns_into_empty_list (line 108) | def test_empty_sample_turns_into_empty_list(self) -> None:
    method test_java_status_players_sample_is_none (line 111) | def test_java_status_players_sample_is_none(self) -> None:
  class TestJavaStatusPlayer (line 126) | class TestJavaStatusPlayer(BaseResponseTest):
    method build (line 130) | def build(self) -> JavaStatusPlayer:
    method test_id_field_the_same_as_uuid (line 133) | def test_id_field_the_same_as_uuid(self) -> None:
  class TestJavaStatusVersion (line 141) | class TestJavaStatusVersion(BaseResponseTest):
    method build (line 145) | def build(self) -> JavaStatusVersion:

FILE: tests/responses/test_legacy.py
  function build (line 11) | def build():
  class TestLegacyStatusResponse (line 25) | class TestLegacyStatusResponse(BaseResponseTest):
    method build (line 36) | def build(self, build):
    method test_as_dict (line 39) | def test_as_dict(self, build: LegacyStatusResponse):
  class TestLegacyStatusPlayers (line 49) | class TestLegacyStatusPlayers(BaseResponseTest):
    method build (line 53) | def build(self, build):
  class TestLegacyStatusVersion (line 58) | class TestLegacyStatusVersion(BaseResponseTest):
    method build (line 62) | def build(self, build):

FILE: tests/responses/test_query.py
  class TestQueryResponse (line 13) | class TestQueryResponse(BaseResponseTest):
    method build (line 41) | def build(self):
    method test_as_dict (line 44) | def test_as_dict(self, build: QueryResponse):
    method test_deprecated_map_alias (line 80) | def test_deprecated_map_alias(self, build: QueryResponse):
  class TestQueryPlayers (line 91) | class TestQueryPlayers(BaseResponseTest):
    method build (line 99) | def build(self):
    method test_deprecated_names_alias (line 116) | def test_deprecated_names_alias(self, build: QueryPlayers):
  class TestQuerySoftware (line 129) | class TestQuerySoftware:
    method test_vanilla (line 130) | def test_vanilla(self):
    method test_modded (line 136) | def test_modded(self):
    method test_modded_no_plugins (line 141) | def test_modded_no_plugins(self):

FILE: tests/test_cli.py
  function patch_stdout_stderr (line 91) | def patch_stdout_stderr():
  function mock_network_requests (line 99) | def mock_network_requests():
  function normalise_help_output (line 118) | def normalise_help_output(s: str) -> str:
  function test_no_args (line 137) | def test_no_args():
  function test_help (line 146) | def test_help():
  function test_help_matches_recorded_output (line 156) | def test_help_matches_recorded_output():
  function test_one_argument_is_status (line 164) | def test_one_argument_is_status(mock_network_requests):
  function test_status (line 177) | def test_status(mock_network_requests):
  function test_status_with_sample (line 190) | def test_status_with_sample(mock_network_requests):
  function test_status_sample_empty_list (line 217) | def test_status_sample_empty_list(mock_network_requests):
  function test_status_bedrock (line 237) | def test_status_bedrock(mock_network_requests):
  function test_status_legacy (line 252) | def test_status_legacy(mock_network_requests):
  function test_status_offline (line 262) | def test_status_offline(mock_network_requests):
  function test_query (line 270) | def test_query(mock_network_requests):
  function test_query_offline (line 284) | def test_query_offline(mock_network_requests):
  function test_query_on_bedrock (line 292) | def test_query_on_bedrock(mock_network_requests):
  function test_json (line 300) | def test_json(mock_network_requests):
  function test_ping (line 362) | def test_ping(mock_network_requests):
  function test_ping_bedrock (line 370) | def test_ping_bedrock(mock_network_requests):
  function test_ping_legacy (line 378) | def test_ping_legacy(mock_network_requests):
  function test_ping_server_doesnt_support (line 386) | def test_ping_server_doesnt_support(mock_network_requests):

FILE: tests/test_compat.py
  function _chdir (line 21) | def _chdir(path: Path) -> Iterator[None]:
  function _extractall_compat (line 31) | def _extractall_compat(tar: tarfile.TarFile, destination: Path) -> None:
  function test_deprecated_import_path (line 61) | def test_deprecated_import_path(raises: bool, module: str, msg_pattern: ...
  function sdist_path (line 80) | def sdist_path(tmp_path_factory: pytest.TempPathFactory) -> Path:
  function sdist_member_names (line 114) | def sdist_member_names(sdist_path: Path) -> set[str]:
  function wheel_member_names (line 122) | def wheel_member_names(sdist_path: Path, tmp_path_factory: pytest.TempPa...
  function test_includes_compat_shims (line 158) | def test_includes_compat_shims(

FILE: tests/test_server.py
  class AsyncConnection (line 20) | class AsyncConnection(BaseAsyncReadSyncWriteConnection):
    method __init__ (line 21) | def __init__(self) -> None:
    method read (line 25) | async def read(self, length: int) -> bytearray:
    method write (line 34) | def write(self, data: Connection | str | bytearray | bytes) -> None:
    method receive (line 42) | def receive(self, data: BytesConvertable | bytearray) -> None:
    method remaining (line 48) | def remaining(self) -> int:
    method flush (line 52) | def flush(self) -> bytearray:
  class MockProtocolFactory (line 58) | class MockProtocolFactory(asyncio.Protocol):
    method __init__ (line 61) | def __init__(self, data_expected_to_receive, data_to_respond_with):
    method connection_made (line 65) | def connection_made(self, transport: asyncio.Transport):  # pyright: i...
    method connection_lost (line 69) | def connection_lost(self, exc):
    method data_received (line 73) | def data_received(self, data):
    method eof_received (line 77) | def eof_received(self):
    method pause_writing (line 80) | def pause_writing(self):
    method resume_writing (line 83) | def resume_writing(self):
  function create_mock_packet_server (line 88) | async def create_mock_packet_server():
  class TestBedrockServer (line 108) | class TestBedrockServer:
    method setup_method (line 109) | def setup_method(self):
    method test_default_port (line 112) | def test_default_port(self):
    method test_lookup_constructor (line 115) | def test_lookup_constructor(self):
  class TestAsyncJavaServer (line 121) | class TestAsyncJavaServer:
    method test_async_ping (line 123) | async def test_async_ping(self, unused_tcp_port, create_mock_packet_se...
    method test_async_lookup_constructor (line 135) | async def test_async_lookup_constructor(self):
  function test_java_server_with_query_port (line 141) | def test_java_server_with_query_port():
  function test_java_server_with_query_port_async (line 150) | async def test_java_server_with_query_port_async():
  class TestJavaServer (line 158) | class TestJavaServer:
    method setup_method (line 159) | def setup_method(self):
    method test_default_port (line 163) | def test_default_port(self):
    method test_ping (line 166) | def test_ping(self):
    method test_ping_retry (line 177) | def test_ping_retry(self):
    method test_status (line 185) | def test_status(self):
    method test_status_retry (line 207) | def test_status_retry(self):
    method test_query (line 215) | def test_query(self):
    method test_query_retry (line 253) | def test_query_retry(self):
    method test_lookup_constructor (line 262) | def test_lookup_constructor(self):
  class TestLegacyServer (line 268) | class TestLegacyServer:
    method setup_method (line 269) | def setup_method(self):
    method test_default_port (line 273) | def test_default_port(self):
    method test_lookup_constructor (line 276) | def test_lookup_constructor(self):
    method test_status (line 281) | def test_status(self):
  class TestAsyncLegacyServer (line 307) | class TestAsyncLegacyServer:
    method setup_method (line 308) | def setup_method(self):
    method test_async_lookup_constructor (line 313) | async def test_async_lookup_constructor(self):
    method test_async_status (line 319) | async def test_async_status(self):

FILE: tests/utils/test_deprecation.py
  function test_invalid_lib_version (line 14) | def test_invalid_lib_version():
  function test_epoch_in_lib_version (line 22) | def test_epoch_in_lib_version():
  function test_deprecation_warn_produces_error (line 33) | def test_deprecation_warn_produces_error(removal_version: str | tuple[in...
  function test_deprecation_warn_produces_warning (line 46) | def test_deprecation_warn_produces_warning(removal_version: str | tuple[...
  function test_deprecation_invalid_removal_version (line 57) | def test_deprecation_invalid_removal_version():
  function test_deprecation_warn_unknown_version (line 70) | def test_deprecation_warn_unknown_version():
  function test_deprecation_decorator_warn (line 84) | def test_deprecation_decorator_warn():
  function test_deprecation_decorator_inferred_name (line 100) | def test_deprecation_decorator_inferred_name():
  function test_deprecation_decorator_missing_docstring_directive (line 117) | def test_deprecation_decorator_missing_docstring_directive():
  function test_deprecation_decorator_no_docstring_check_opt_out (line 132) | def test_deprecation_decorator_no_docstring_check_opt_out():
  function test_project_version_non_normalized_parsing (line 160) | def test_project_version_non_normalized_parsing(version: str, expected: ...
  function test_project_version_normalizes_release_components (line 184) | def test_project_version_normalizes_release_components(

FILE: tests/utils/test_general.py
  function test_or_none (line 15) | def test_or_none(a, b, result):
  function test_or_none_many_arguments (line 19) | def test_or_none_many_arguments():

FILE: tests/utils/test_retry.py
  function test_sync_success (line 7) | def test_sync_success():
  function test_sync_fail (line 21) | def test_sync_fail():
  function test_async_success (line 38) | def test_async_success():
  function test_async_fail (line 52) | def test_async_fail():
Condensed preview — 101 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (443K chars).
[
  {
    "path": ".coveragerc",
    "chars": 228,
    "preview": "[report]\nexclude_lines =\n    pragma: no cover\n    ((t|typing)\\.)?TYPE_CHECKING\n    ^\\s\\.\\.\\.\\s$\n    def __repr__\n    cla"
  },
  {
    "path": ".github/CODEOWNERS",
    "chars": 138,
    "preview": "# Devops & CI workflows\n.github/dependabot.yml      @ItsDrike\n.github/workflows/**        @ItsDrike\n.pre-commit-config.y"
  },
  {
    "path": ".github/renovate.json5",
    "chars": 766,
    "preview": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\n    \":automergeMinor\",\n    \":automerg"
  },
  {
    "path": ".github/workflows/main.yml",
    "chars": 1203,
    "preview": "name: CI\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n  workflow_dispatch:\n\n# Cancel already running workfl"
  },
  {
    "path": ".github/workflows/publish.yml",
    "chars": 3964,
    "preview": "---\nname: Publish to PyPI\n\non:\n  push:\n    tags:\n      # This pattern is not a typical regular expression, see:\n      # "
  },
  {
    "path": ".github/workflows/status_embed.yml",
    "chars": 2878,
    "preview": "name: Status Embed\n\non:\n  workflow_run:\n    workflows:\n      - CI\n    types:\n      - completed\n\nconcurrency:\n  group: ${"
  },
  {
    "path": ".github/workflows/unit-tests.yml",
    "chars": 1536,
    "preview": "name: Unit-Tests\n\non: workflow_call\n\njobs:\n  unit-tests:\n    runs-on: ${{ matrix.platform }}\n\n    strategy:\n      fail-f"
  },
  {
    "path": ".github/workflows/validation.yml",
    "chars": 2022,
    "preview": "name: Validation\n\non: workflow_call\n\nenv:\n  PYTHON_VERSION: \"3.14\"\n  PRE_COMMIT_HOME: \"/home/runner/.cache/pre-commit\"\n\n"
  },
  {
    "path": ".gitignore",
    "chars": 1881,
    "preview": ".python-version\n\n# Created by http://www.gitignore.io\n\n### Python ###\n# Byte-compiled / optimized / DLL files\n__pycache_"
  },
  {
    "path": ".pre-commit-config.yaml",
    "chars": 1711,
    "preview": "ci:\n  autofix_commit_msg: \"[pre-commit.ci] auto fixes from pre-commit.com hooks\"\n  autofix_prs: true\n  autoupdate_commit"
  },
  {
    "path": ".readthedocs.yaml",
    "chars": 350,
    "preview": "version: 2\n\nbuild:\n  os: ubuntu-22.04\n  tools:\n    python: \"3.13\"\n\n  jobs:\n    post_create_environment:\n      - python -"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 170,
    "preview": "## Contributing\n\nSee [the documentation page](https://mcstatus.readthedocs.io/en/stable/pages/contributing/).\nThe docume"
  },
  {
    "path": "LICENSE",
    "chars": 11358,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "README.md",
    "chars": 4112,
    "preview": "# <img src=\"https://i.imgur.com/nPCcxts.png\" height=\"25\" style=\"height: 25px\"> MCStatus\n\n[![discord chat](https://img.sh"
  },
  {
    "path": "docs/Makefile",
    "chars": 634,
    "preview": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line, and also\n# from the "
  },
  {
    "path": "docs/api/basic.rst",
    "chars": 3938,
    "preview": "Basic Usage\n===========\n\nWe are small package, so our API is not so big. There are only few classes, which are suggested"
  },
  {
    "path": "docs/api/internal.rst",
    "chars": 1895,
    "preview": "Internal Data\n=============\n\nThis page contains some internal objects, classes, functions, etc. These **are not a part o"
  },
  {
    "path": "docs/api/motd_parsing.rst",
    "chars": 587,
    "preview": "MOTD Parsing\n============\n\nWe provide a really powerful system to parse servers MOTDs.\n\n\nThe main class\n--------------\n\n"
  },
  {
    "path": "docs/conf.py",
    "chars": 4848,
    "preview": "\"\"\"Configuration file for the Sphinx documentation builder.\n\nThis file does only contain a selection of the most common "
  },
  {
    "path": "docs/examples/code/ping_as_java_and_bedrock_in_one_time.py",
    "chars": 2263,
    "preview": "from __future__ import annotations\n\nimport asyncio\n\nfrom mcstatus import BedrockServer, JavaServer\nfrom mcstatus.respons"
  },
  {
    "path": "docs/examples/code/ping_many_servers_at_once.py",
    "chars": 846,
    "preview": "import asyncio\n\nfrom mcstatus import JavaServer\n\n\nasync def ping_server(ip: str) -> None:\n    try:\n        status = awai"
  },
  {
    "path": "docs/examples/code/player_list_from_query_with_fallback_on_status.py",
    "chars": 446,
    "preview": "from mcstatus import JavaServer\n\nserver = JavaServer.lookup(\"play.hypixel.net\")\nquery = server.query()\n\nif query.players"
  },
  {
    "path": "docs/examples/examples.rst",
    "chars": 315,
    "preview": "Examples\n========\n\nWe have these examples at the moment:\n\n\n.. toctree::\n\t:maxdepth: 1\n\t:caption: Examples\n\n\tping_as_java"
  },
  {
    "path": "docs/examples/ping_as_java_and_bedrock_in_one_time.rst",
    "chars": 843,
    "preview": "Ping as Java and as Bedrock in one time\n=======================================\n\nYou can easily ping a server as a Java "
  },
  {
    "path": "docs/examples/ping_many_servers_at_once.rst",
    "chars": 184,
    "preview": "Ping many servers at once\n=========================\n\nYou can ping many servers at once with mcstatus async methods, just"
  },
  {
    "path": "docs/examples/player_list_from_query_with_fallback_on_status.rst",
    "chars": 190,
    "preview": "Get player list from query, while falling back on status\n========================================================\n\n.. li"
  },
  {
    "path": "docs/index.rst",
    "chars": 381,
    "preview": ".. mdinclude:: ../README.md\n\nContent\n-------\n\n.. toctree::\n\t:maxdepth: 1\n\t:caption: Pages\n\n\tpages/faq.rst\n\tpages/contrib"
  },
  {
    "path": "docs/pages/contributing.rst",
    "chars": 2455,
    "preview": "Contributing\n============\n\nSetup\n-----\n\n.. code-block:: sh\n\n   pipx install uv\n   uv sync\n\n   # The following command wi"
  },
  {
    "path": "docs/pages/faq.rst",
    "chars": 3405,
    "preview": "Frequently Asked Questions\n==========================\n\n\nWhy doesn't :class:`~mcstatus.server.BedrockServer` have an asyn"
  },
  {
    "path": "docs/pages/versioning.rst",
    "chars": 11347,
    "preview": "Versioning Practices & Guarantees\n=================================\n\nThis page explains what you can expect when upgradi"
  },
  {
    "path": "docs/pyproject.toml",
    "chars": 395,
    "preview": "[project]\nname = \"docs\"\nversion = \"0.0.0\"\nlicense = \"Apache-2.0\"\nrequires-python = \">=3.12\"\ndependencies = [\n  \"sphinx~="
  },
  {
    "path": "mcstatus/__init__.py",
    "chars": 168,
    "preview": "from mcstatus.server import BedrockServer, JavaServer, LegacyServer, MCServer\n\n__all__ = [\n    \"BedrockServer\",\n    \"Jav"
  },
  {
    "path": "mcstatus/__main__.py",
    "chars": 6616,
    "preview": "# ruff: noqa: T201 # usage of `print`\nfrom __future__ import annotations\n\nimport argparse\nimport json\nimport socket\nimpo"
  },
  {
    "path": "mcstatus/_compat/README.md",
    "chars": 1239,
    "preview": "# Compatibility Shims\n\nThis directory holds compatibility shims for deprecated public modules.\n\nThese modules are not pa"
  },
  {
    "path": "mcstatus/_compat/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "mcstatus/_compat/forge_data.py",
    "chars": 331,
    "preview": "from mcstatus._utils import deprecation_warn\nfrom mcstatus.responses.forge import ForgeData, ForgeDataChannel, ForgeData"
  },
  {
    "path": "mcstatus/_compat/motd_transformers.py",
    "chars": 602,
    "preview": "from mcstatus._utils import deprecation_warn\nfrom mcstatus.motd._transformers import (\n    AnsiTransformer,\n    HtmlTran"
  },
  {
    "path": "mcstatus/_compat/status_response.py",
    "chars": 727,
    "preview": "from mcstatus._utils import deprecation_warn\nfrom mcstatus.responses import (\n    BaseStatusPlayers,\n    BaseStatusRespo"
  },
  {
    "path": "mcstatus/_net/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "mcstatus/_net/address.py",
    "chars": 10542,
    "preview": "from __future__ import annotations\n\nimport ipaddress\nimport sys\nimport warnings\nfrom typing import NamedTuple, TYPE_CHEC"
  },
  {
    "path": "mcstatus/_net/dns.py",
    "chars": 4364,
    "preview": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING, cast\n\nimport dns.asyncresolver\nimport dns.resolver"
  },
  {
    "path": "mcstatus/_protocol/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "mcstatus/_protocol/bedrock_client.py",
    "chars": 2177,
    "preview": "from __future__ import annotations\n\nimport asyncio\nimport socket\nimport struct\nfrom time import perf_counter\nfrom typing"
  },
  {
    "path": "mcstatus/_protocol/connection.py",
    "chars": 26070,
    "preview": "from __future__ import annotations\n\nimport asyncio\nimport errno\nimport socket\nimport struct\nfrom abc import ABC, abstrac"
  },
  {
    "path": "mcstatus/_protocol/java_client.py",
    "chars": 5052,
    "preview": "from __future__ import annotations\n\nimport json\nimport random\nfrom abc import ABC, abstractmethod\nfrom dataclasses impor"
  },
  {
    "path": "mcstatus/_protocol/legacy_client.py",
    "chars": 2358,
    "preview": "from time import perf_counter\n\nfrom mcstatus._protocol.connection import BaseAsyncReadSyncWriteConnection, BaseSyncConne"
  },
  {
    "path": "mcstatus/_protocol/query_client.py",
    "chars": 5138,
    "preview": "from __future__ import annotations\n\nimport random\nimport re\nimport struct\nfrom abc import abstractmethod\nfrom dataclasse"
  },
  {
    "path": "mcstatus/_utils/__init__.py",
    "chars": 219,
    "preview": "from mcstatus._utils.deprecation import deprecated, deprecation_warn\nfrom mcstatus._utils.general import or_none\nfrom mc"
  },
  {
    "path": "mcstatus/_utils/deprecation.py",
    "chars": 9393,
    "preview": "from __future__ import annotations\n\nimport functools\nimport importlib.metadata\nimport re\nimport warnings\nfrom functools "
  },
  {
    "path": "mcstatus/_utils/general.py",
    "chars": 1069,
    "preview": "from typing import TypeVar\n\n__all__ = [\"or_none\"]\n\n\nT = TypeVar(\"T\")\n\n\ndef or_none(*args: T) -> T | None:\n    \"\"\"Return "
  },
  {
    "path": "mcstatus/_utils/retry.py",
    "chars": 2785,
    "preview": "from __future__ import annotations\n\nimport inspect\nfrom functools import wraps\nfrom typing import ParamSpec, TYPE_CHECKI"
  },
  {
    "path": "mcstatus/motd/__init__.py",
    "chars": 10830,
    "preview": "from __future__ import annotations\n\nimport re\nimport typing as t\nfrom dataclasses import dataclass\n\nfrom mcstatus.motd._"
  },
  {
    "path": "mcstatus/motd/_simplifies.py",
    "chars": 7088,
    "preview": "from __future__ import annotations\n\nimport typing as t\n\nfrom mcstatus.motd.components import Formatting, MinecraftColor,"
  },
  {
    "path": "mcstatus/motd/_transformers.py",
    "chars": 9651,
    "preview": "from __future__ import annotations\n\nimport abc\nimport typing as t\nfrom collections.abc import Callable, Sequence\n\nfrom m"
  },
  {
    "path": "mcstatus/motd/components.py",
    "chars": 4139,
    "preview": "from __future__ import annotations\n\nimport typing as t\nfrom dataclasses import dataclass\nfrom enum import Enum\n\nif t.TYP"
  },
  {
    "path": "mcstatus/py.typed",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "mcstatus/responses/__init__.py",
    "chars": 1050,
    "preview": "from mcstatus.responses.base import BaseStatusPlayers, BaseStatusResponse, BaseStatusVersion\nfrom mcstatus.responses.bed"
  },
  {
    "path": "mcstatus/responses/_raw.py",
    "chars": 2417,
    "preview": "from __future__ import annotations\n\nfrom typing import Literal, TYPE_CHECKING, TypeAlias, TypedDict\n\nif TYPE_CHECKING:\n "
  },
  {
    "path": "mcstatus/responses/base.py",
    "chars": 2976,
    "preview": "from __future__ import annotations\n\nfrom abc import ABC, abstractmethod\nfrom dataclasses import asdict, dataclass\nfrom t"
  },
  {
    "path": "mcstatus/responses/bedrock.py",
    "chars": 2670,
    "preview": "from __future__ import annotations\n\nfrom dataclasses import dataclass\nfrom typing import Any, TYPE_CHECKING\n\nfrom mcstat"
  },
  {
    "path": "mcstatus/responses/forge.py",
    "chars": 8845,
    "preview": "\"\"\"Decoder for data from Forge, that is included into a response object.\n\nAfter 1.18.1, Forge started to compress its mo"
  },
  {
    "path": "mcstatus/responses/java.py",
    "chars": 6094,
    "preview": "from __future__ import annotations\n\nfrom dataclasses import dataclass\nfrom typing import TYPE_CHECKING\n\nfrom mcstatus.mo"
  },
  {
    "path": "mcstatus/responses/legacy.py",
    "chars": 2210,
    "preview": "from __future__ import annotations\n\nfrom dataclasses import dataclass\nfrom typing import TYPE_CHECKING\n\nfrom mcstatus.mo"
  },
  {
    "path": "mcstatus/responses/query.py",
    "chars": 5262,
    "preview": "from __future__ import annotations\n\nfrom dataclasses import asdict, dataclass\nfrom typing import Any, TYPE_CHECKING\n\nfro"
  },
  {
    "path": "mcstatus/server.py",
    "chars": 14270,
    "preview": "from __future__ import annotations\n\nfrom abc import ABC\nfrom typing import TYPE_CHECKING\n\nfrom mcstatus._net.address imp"
  },
  {
    "path": "pyproject.toml",
    "chars": 7039,
    "preview": "[project]\nname = \"mcstatus\"\ndynamic = [\"version\"]\nlicense = \"Apache-2.0\"\ndescription = \"A library to query Minecraft Ser"
  },
  {
    "path": "tests/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/helpers.py",
    "chars": 1168,
    "preview": "import collections.abc as c\nimport importlib.metadata\nfrom contextlib import contextmanager\nfrom functools import wraps\n"
  },
  {
    "path": "tests/motd/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/motd/conftest.py",
    "chars": 3536,
    "preview": "import pytest\n\n\n@pytest.fixture(scope=\"session\")\ndef source_java() -> dict:\n    \"\"\"Return ultimate dict with almost all "
  },
  {
    "path": "tests/motd/test_components.py",
    "chars": 1709,
    "preview": "from __future__ import annotations\n\nimport pytest\n\nfrom mcstatus.motd.components import WebColor\n\n\nclass TestWebColor:\n "
  },
  {
    "path": "tests/motd/test_motd.py",
    "chars": 9101,
    "preview": "from __future__ import annotations\n\nimport pytest\n\nfrom mcstatus.motd import Motd\nfrom mcstatus.motd.components import F"
  },
  {
    "path": "tests/motd/test_simplifies.py",
    "chars": 8141,
    "preview": "from __future__ import annotations\n\nfrom contextlib import ExitStack\nfrom unittest import mock\n\nimport pytest\n\nfrom mcst"
  },
  {
    "path": "tests/motd/test_transformers.py",
    "chars": 10315,
    "preview": "# ruff: noqa: FBT003 # boolean positional value in `result` fixture\nfrom __future__ import annotations\n\nimport typing\n\ni"
  },
  {
    "path": "tests/net/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/net/test_address.py",
    "chars": 11918,
    "preview": "from __future__ import annotations\n\nimport ipaddress\nimport sys\nfrom pathlib import Path\nfrom typing import cast\nfrom un"
  },
  {
    "path": "tests/protocol/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/protocol/test_async_support.py",
    "chars": 806,
    "preview": "from inspect import iscoroutinefunction\n\nfrom mcstatus._protocol.connection import TCPAsyncSocketConnection, UDPAsyncSoc"
  },
  {
    "path": "tests/protocol/test_bedrock_client.py",
    "chars": 2307,
    "preview": "import sys\nimport time\nfrom unittest import mock\n\nimport pytest\n\nfrom mcstatus._net.address import Address\nfrom mcstatus"
  },
  {
    "path": "tests/protocol/test_connection.py",
    "chars": 11270,
    "preview": "from unittest.mock import Mock, patch\n\nimport pytest\n\nfrom mcstatus._net.address import Address\nfrom mcstatus._protocol."
  },
  {
    "path": "tests/protocol/test_java_client.py",
    "chars": 6055,
    "preview": "import sys\nimport time\nfrom unittest import mock\n\nimport pytest\n\nfrom mcstatus._net.address import Address\nfrom mcstatus"
  },
  {
    "path": "tests/protocol/test_java_client_async.py",
    "chars": 6303,
    "preview": "import asyncio\nimport sys\nimport time\nfrom unittest import mock\n\nimport pytest\n\nfrom mcstatus._net.address import Addres"
  },
  {
    "path": "tests/protocol/test_legacy_client.py",
    "chars": 1656,
    "preview": "import pytest\n\nfrom mcstatus._protocol.connection import Connection\nfrom mcstatus._protocol.legacy_client import LegacyC"
  },
  {
    "path": "tests/protocol/test_query_client.py",
    "chars": 5121,
    "preview": "from unittest.mock import Mock\n\nfrom mcstatus._protocol.connection import Connection\nfrom mcstatus._protocol.query_clien"
  },
  {
    "path": "tests/protocol/test_query_client_async.py",
    "chars": 2345,
    "preview": "from mcstatus._protocol.connection import Connection\nfrom mcstatus._protocol.query_client import AsyncQueryClient\nfrom t"
  },
  {
    "path": "tests/protocol/test_timeout.py",
    "chars": 1008,
    "preview": "import asyncio\nimport typing\nfrom asyncio.exceptions import TimeoutError as AsyncioTimeoutError\nfrom unittest.mock impor"
  },
  {
    "path": "tests/responses/__init__.py",
    "chars": 4844,
    "preview": "from __future__ import annotations\n\nimport abc\nfrom typing import Any, ClassVar, TYPE_CHECKING, TypeVar, cast\n\nimport py"
  },
  {
    "path": "tests/responses/conftest.py",
    "chars": 1193,
    "preview": "from __future__ import annotations\n\nimport typing\n\nimport pytest\n\nfrom tests.responses import BaseResponseTest\n\nif typin"
  },
  {
    "path": "tests/responses/test_base.py",
    "chars": 295,
    "preview": "import pytest\n\nfrom mcstatus.responses import BaseStatusResponse\n\n\nclass TestMCStatusResponse:\n    def test_raises_not_i"
  },
  {
    "path": "tests/responses/test_bedrock.py",
    "chars": 3455,
    "preview": "import typing as t\n\nimport pytest\n\nfrom mcstatus.motd import Motd\nfrom mcstatus.responses import BedrockStatusPlayers, B"
  },
  {
    "path": "tests/responses/test_forge_data.py",
    "chars": 41148,
    "preview": "import typing as t\n\nimport pytest\n\nfrom mcstatus.responses import JavaStatusResponse\nfrom mcstatus.responses._raw import"
  },
  {
    "path": "tests/responses/test_java.py",
    "chars": 5220,
    "preview": "import typing as t\n\nimport pytest\n\nfrom mcstatus.motd import Motd\nfrom mcstatus.responses import JavaStatusPlayer, JavaS"
  },
  {
    "path": "tests/responses/test_legacy.py",
    "chars": 1615,
    "preview": "import typing as t\n\nimport pytest\n\nfrom mcstatus.motd import Motd\nfrom mcstatus.responses import LegacyStatusPlayers, Le"
  },
  {
    "path": "tests/responses/test_query.py",
    "chars": 4818,
    "preview": "import typing as t\n\nimport pytest\n\nfrom mcstatus.motd import Motd\nfrom mcstatus.responses import QueryPlayers, QueryResp"
  },
  {
    "path": "tests/test_cli.py",
    "chars": 13396,
    "preview": "import contextlib\nimport io\nimport json\nimport os\nimport socket\nfrom unittest import mock\nfrom unittest.mock import patc"
  },
  {
    "path": "tests/test_compat.py",
    "chars": 5971,
    "preview": "\"\"\"Tests for compatibility shims and build-time packaging behavior.\"\"\"\n\nimport importlib\nimport os\nimport shutil\nimport "
  },
  {
    "path": "tests/test_server.py",
    "chars": 13239,
    "preview": "from __future__ import annotations\n\nimport asyncio\nfrom typing import SupportsIndex, TYPE_CHECKING, TypeAlias\nfrom unitt"
  },
  {
    "path": "tests/utils/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/utils/test_deprecation.py",
    "chars": 6585,
    "preview": "from __future__ import annotations\n\nimport re\nimport warnings\n\nimport pytest\n\nfrom mcstatus._utils.deprecation import _g"
  },
  {
    "path": "tests/utils/test_general.py",
    "chars": 392,
    "preview": "import pytest\n\nfrom mcstatus._utils.general import or_none\n\n\n@pytest.mark.parametrize(\n    (\"a\", \"b\", \"result\"),\n    [\n "
  },
  {
    "path": "tests/utils/test_retry.py",
    "chars": 1283,
    "preview": "import pytest\n\nfrom mcstatus._utils.retry import retry\nfrom tests.protocol.test_java_client_async import async_decorator"
  }
]

About this extraction

This page contains the full source code of the py-mine/mcstatus GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 101 files (407.1 KB), approximately 108.9k tokens, and a symbol index with 734 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!