├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── python-package.yml │ ├── python-publish.yml │ └── sphinx-gh-pages.yml ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── docs ├── Makefile ├── api │ └── .gitignore ├── conf.py ├── favicon.ico ├── index.rst ├── install.rst ├── make.bat ├── requirements.txt ├── tests │ ├── protocols │ │ ├── index.rst │ │ ├── test_ase │ │ │ ├── index.rst │ │ │ └── test_get_status.rst │ │ ├── test_battlefield │ │ │ ├── index.rst │ │ │ ├── test_get_info.rst │ │ │ ├── test_get_players.rst │ │ │ └── test_get_version.rst │ │ ├── test_doom3 │ │ │ ├── index.rst │ │ │ └── test_get_status.rst │ │ ├── test_eos │ │ │ ├── index.rst │ │ │ ├── test_get_info.rst │ │ │ ├── test_get_info_palworld.rst │ │ │ └── test_get_matchmaking.rst │ │ ├── test_fivem │ │ │ ├── index.rst │ │ │ ├── test_get_dynamic.rst │ │ │ ├── test_get_info.rst │ │ │ └── test_get_players.rst │ │ ├── test_flatout2 │ │ │ ├── index.rst │ │ │ ├── test_flatout2_get_status.rst │ │ │ └── test_get_status.rst │ │ ├── test_gamespy1 │ │ │ ├── index.rst │ │ │ ├── test_get_basic.rst │ │ │ ├── test_get_info.rst │ │ │ ├── test_get_players.rst │ │ │ ├── test_get_rules.rst │ │ │ ├── test_get_status.rst │ │ │ └── test_get_teams.rst │ │ ├── test_gamespy2 │ │ │ ├── index.rst │ │ │ └── test_get_status.rst │ │ ├── test_gamespy3 │ │ │ ├── index.rst │ │ │ └── test_get_status.rst │ │ ├── test_gamespy4 │ │ │ ├── index.rst │ │ │ └── test_get_status.rst │ │ ├── test_kaillera │ │ │ ├── index.rst │ │ │ ├── test_get_status.rst │ │ │ └── test_query_master_servers.rst │ │ ├── test_killingfloor │ │ │ ├── index.rst │ │ │ ├── test_get_details.rst │ │ │ ├── test_get_players.rst │ │ │ └── test_get_rules.rst │ │ ├── test_minecraft │ │ │ ├── index.rst │ │ │ ├── test_get_status.rst │ │ │ └── test_get_status_pre17.rst │ │ ├── test_nadeo │ │ │ ├── index.rst │ │ │ └── test_get_status.rst │ │ ├── test_palworld │ │ │ ├── index.rst │ │ │ └── test_get_status.rst │ │ ├── test_quake1 │ │ │ ├── index.rst │ │ │ └── test_get_status.rst │ │ ├── test_quake2 │ │ │ ├── index.rst │ │ │ └── test_get_status.rst │ │ ├── test_quake3 │ │ │ ├── index.rst │ │ │ ├── test_get_info.rst │ │ │ └── test_get_status.rst │ │ ├── test_raknet │ │ │ ├── index.rst │ │ │ └── test_get_status.rst │ │ ├── test_renegadex │ │ │ ├── index.rst │ │ │ └── test_renegadex_status.rst │ │ ├── test_samp │ │ │ ├── index.rst │ │ │ ├── test_get_players.rst │ │ │ ├── test_get_rules.rst │ │ │ └── test_get_status.rst │ │ ├── test_satisfactory │ │ │ ├── index.rst │ │ │ └── test_get_status.rst │ │ ├── test_scum │ │ │ ├── index.rst │ │ │ ├── test_get_status.rst │ │ │ └── test_query_master_servers.rst │ │ ├── test_source │ │ │ ├── index.rst │ │ │ ├── test_get_info.rst │ │ │ ├── test_get_players.rst │ │ │ └── test_get_rules.rst │ │ ├── test_teamspeak3 │ │ │ ├── index.rst │ │ │ ├── test_get_channels.rst │ │ │ ├── test_get_clients.rst │ │ │ └── test_get_info.rst │ │ ├── test_toxikk │ │ │ ├── index.rst │ │ │ └── test_toxikk_status.rst │ │ ├── test_unreal2 │ │ │ ├── index.rst │ │ │ ├── test_get_details.rst │ │ │ ├── test_get_players.rst │ │ │ └── test_get_rules.rst │ │ ├── test_ut3 │ │ │ ├── index.rst │ │ │ └── test_ut3_status.rst │ │ ├── test_vcmp │ │ │ ├── index.rst │ │ │ ├── test_get_players.rst │ │ │ └── test_get_status.rst │ │ ├── test_warcraft3 │ │ │ ├── index.rst │ │ │ └── test_warcraft3_status.rst │ │ └── test_won │ │ │ ├── index.rst │ │ │ ├── test_get_info.rst │ │ │ ├── test_get_players.rst │ │ │ └── test_get_rules.rst │ └── rcon_protocols │ │ ├── index.rst │ │ └── test_source_rcon │ │ ├── index.rst │ │ └── test_authenticate.rst └── usage.rst ├── opengsq ├── __init__.py ├── binary_reader.py ├── cli.py ├── exceptions │ ├── __init__.py │ ├── authentication_exception.py │ ├── invalid_packet_exception.py │ └── server_not_found_exception.py ├── protocol_base.py ├── protocol_socket.py ├── protocols │ ├── __init__.py │ ├── ase.py │ ├── battlefield.py │ ├── doom3.py │ ├── eos.py │ ├── fivem.py │ ├── flatout2.py │ ├── gamespy1.py │ ├── gamespy2.py │ ├── gamespy3.py │ ├── gamespy4.py │ ├── kaillera.py │ ├── killingfloor.py │ ├── minecraft.py │ ├── nadeo.py │ ├── palworld.py │ ├── quake1.py │ ├── quake2.py │ ├── quake3.py │ ├── raknet.py │ ├── renegadex.py │ ├── samp.py │ ├── satisfactory.py │ ├── scum.py │ ├── source.py │ ├── teamspeak3.py │ ├── toxikk.py │ ├── udk.py │ ├── unreal2.py │ ├── ut3.py │ ├── vcmp.py │ ├── warcraft3.py │ ├── warfork.py │ └── won.py ├── rcon_protocols │ ├── __init__.py │ └── source_rcon.py ├── responses │ ├── __init__.py │ ├── ase │ │ ├── __init__.py │ │ ├── player.py │ │ └── status.py │ ├── battlefield │ │ ├── __init__.py │ │ ├── info.py │ │ └── version_info.py │ ├── doom3 │ │ ├── __init__.py │ │ └── status.py │ ├── eos │ │ ├── __init__.py │ │ └── matchmaking.py │ ├── flatout2.py │ ├── flatout2 │ │ ├── __init__.py │ │ └── status.py │ ├── gamespy1 │ │ ├── __init__.py │ │ └── status.py │ ├── gamespy2 │ │ ├── __init__.py │ │ └── status.py │ ├── kaillera │ │ ├── __init__.py │ │ └── status.py │ ├── killingfloor │ │ ├── __init__.py │ │ └── status.py │ ├── minecraft │ │ ├── __init__.py │ │ └── status_pre_17.py │ ├── nadeo │ │ ├── __init__.py │ │ └── status.py │ ├── palworld │ │ ├── __init__.py │ │ ├── player.py │ │ └── status.py │ ├── quake1 │ │ ├── __init__.py │ │ ├── player.py │ │ └── status.py │ ├── quake2 │ │ ├── __init__.py │ │ ├── player.py │ │ └── status.py │ ├── raknet │ │ ├── __init__.py │ │ └── status.py │ ├── renegadex │ │ ├── __init__.py │ │ └── status.py │ ├── samp │ │ ├── __init__.py │ │ ├── player.py │ │ └── status.py │ ├── satisfactory │ │ ├── __init__.py │ │ └── status.py │ ├── scum │ │ ├── __init__.py │ │ └── status.py │ ├── source │ │ ├── __init__.py │ │ ├── environment.py │ │ ├── extra_data_flag.py │ │ ├── gold_source_info.py │ │ ├── partial_info.py │ │ ├── player.py │ │ ├── server_type.py │ │ ├── source_info.py │ │ ├── vac.py │ │ └── visibility.py │ ├── toxikk │ │ ├── __init__.py │ │ └── status.py │ ├── udk │ │ ├── __init__.py │ │ └── status.py │ ├── unreal2 │ │ ├── __init__.py │ │ ├── player.py │ │ └── status.py │ ├── ut3 │ │ ├── __init__.py │ │ └── status.py │ ├── vcmp │ │ ├── __init__.py │ │ ├── player.py │ │ └── status.py │ ├── warcraft3 │ │ ├── __init__.py │ │ └── status.py │ └── warfork │ │ ├── __init__.py │ │ └── player.py └── version.py ├── pyproject.toml ├── requirements.txt ├── setup.cfg ├── setup.py └── tests ├── __init__.py ├── protocols ├── __init__.py ├── test_ase.py ├── test_battlefield.py ├── test_doom3.py ├── test_eos.py ├── test_fivem.py ├── test_flatout2.py ├── test_gamespy1.py ├── test_gamespy2.py ├── test_gamespy3.py ├── test_gamespy4.py ├── test_kaillera.py ├── test_killingfloor.py ├── test_minecraft.py ├── test_nadeo.py ├── test_palworld.py ├── test_quake1.py ├── test_quake2.py ├── test_quake3.py ├── test_raknet.py ├── test_renegadex.py ├── test_samp.py ├── test_satisfactory.py ├── test_scum.py ├── test_source.py ├── test_teamspeak3.py ├── test_toxikk.py ├── test_unreal2.py ├── test_ut3.py ├── test_vcmp.py ├── test_warcraft3.py └── test_won.py ├── rcon_protocols ├── __init__.py └── test_source_rcon.py └── result_handler.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: BattlefieldDuck 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | 13 | - package-ecosystem: "pip" 14 | directory: "/" 15 | schedule: 16 | interval: "daily" 17 | -------------------------------------------------------------------------------- /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python Package 5 | 6 | on: 7 | push: 8 | branches: [main] 9 | pull_request: 10 | branches: [main] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | python-version: [3.9, "3.10", 3.11, 3.12, 3.13] 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Set up Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@v5 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | python -m pip install flake8 pytest 30 | python -m pip install . 31 | if [ -f requirements.txt ]; then pip install --no-cache-dir -r requirements.txt; fi 32 | - name: Lint with flake8 33 | run: | 34 | # stop the build if there are Python syntax errors or undefined names 35 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 36 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 37 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=221 --statistics --exclude tests 38 | - name: Test with pytest 39 | run: | 40 | pytest 41 | env: 42 | STEAM_API_KEY: ${{ secrets.STEAM_API_KEY }} 43 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload Python Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | deploy: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Set up Python 18 | uses: actions/setup-python@v5 19 | with: 20 | python-version: '3.x' 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install build 25 | - name: Build package 26 | run: python -m build 27 | - name: Publish package 28 | uses: pypa/gh-action-pypi-publish@release/v1 29 | with: 30 | user: __token__ 31 | password: ${{ secrets.PYPI_API_TOKEN }} 32 | -------------------------------------------------------------------------------- /.github/workflows/sphinx-gh-pages.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying sphinx to GitHub Pages 2 | name: Deploy sphinx content to Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["main"] 8 | 9 | # The workflow will only run when there are changes in the 'docs' or 'OpenGSQ' folders 10 | paths: 11 | - 'docs/**' 12 | - 'opengsq/**' 13 | 14 | # Allows you to run this workflow manually from the Actions tab 15 | workflow_dispatch: 16 | 17 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 18 | permissions: 19 | contents: read 20 | pages: write 21 | id-token: write 22 | 23 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 24 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 25 | concurrency: 26 | group: "pages" 27 | cancel-in-progress: false 28 | 29 | jobs: 30 | # Single deploy job since we're just deploying 31 | deploy: 32 | environment: 33 | name: github-pages 34 | url: ${{ steps.deployment.outputs.page_url }} 35 | runs-on: ubuntu-latest 36 | steps: 37 | - name: Checkout 38 | uses: actions/checkout@v4 39 | 40 | - name: Setup Pages 41 | uses: actions/configure-pages@v5 42 | 43 | - name: Set up Python 44 | uses: actions/setup-python@v5 45 | with: 46 | python-version: 3.11 47 | 48 | - name: Install sphinx and dependencies 49 | run: | 50 | python -m pip install --upgrade pip 51 | python -m pip install . 52 | python -m pip install -U sphinx 53 | python -m pip install -r docs/requirements.txt 54 | 55 | - name: Sphinx API Documentation Generation 56 | run: sphinx-apidoc -o docs/api opengsq 57 | 58 | - name: Sphinx HTML Documentation Build 59 | run: sphinx-build -b html docs docs/_build 60 | 61 | - name: Upload artifact 62 | uses: actions/upload-pages-artifact@v3 63 | with: 64 | # Upload entire repository 65 | path: "docs/_build" 66 | 67 | - name: Deploy to GitHub Pages 68 | id: deployment 69 | uses: actions/deploy-pages@v4 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-python.autopep8", 4 | "ms-python.vscode-pylance", 5 | "ms-python.python", 6 | "donjayamanne.python-environment-manager", 7 | "njpwerner.autodocstring", 8 | "ms-python.black-formatter" 9 | ] 10 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.testing.pytestArgs": [], 3 | "python.testing.unittestEnabled": false, 4 | "python.testing.pytestEnabled": true 5 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 OpenGSQ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenGSQ Python Library 2 | 3 | [![Python Package](https://github.com/opengsq/opengsq-python/actions/workflows/python-package.yml/badge.svg)](https://github.com/opengsq/opengsq-python/actions/workflows/python-package.yml) 4 | [![GitHub license](https://img.shields.io/github/license/opengsq/opengsq-python)](https://github.com/opengsq/opengsq-python/blob/main/LICENSE) 5 | [![PyPI version](https://img.shields.io/pypi/v/opengsq.svg)](https://pypi.org/project/opengsq/) 6 | [![Python versions](https://img.shields.io/pypi/pyversions/opengsq.svg)](https://pypi.org/project/opengsq/) 7 | [![Downloads](https://pepy.tech/badge/opengsq)](https://pepy.tech/project/opengsq) 8 | 9 | The OpenGSQ Python library provides a convenient way to query servers 10 | from applications written in the Python language. 11 | 12 | ## Supported Protocols 13 | 14 | The library supports a wide range of protocols. Here are some examples: 15 | 16 | ```py 17 | from opengsq.protocols import ( 18 | ASE, 19 | Battlefield, 20 | Doom3, 21 | EOS, 22 | FiveM, 23 | GameSpy1, 24 | GameSpy2, 25 | GameSpy3, 26 | GameSpy4, 27 | Kaillera, 28 | KillingFloor, 29 | Minecraft, 30 | Nadeo, 31 | Palworld, 32 | Quake1, 33 | Quake2, 34 | Quake3, 35 | RakNet, 36 | RenegadeX, 37 | Samp, 38 | Satisfactory, 39 | Scum, 40 | Source, 41 | TeamSpeak3, 42 | Toxikk, 43 | UDK, 44 | Unreal2, 45 | UT3, 46 | Vcmp, 47 | WON, 48 | ) 49 | ``` 50 | 51 | ## Requirements 52 | 53 | - Python 3.7 or higher 54 | 55 | ## Installation 56 | 57 | The recommended installation method is using [pip](http://pip-installer.org/): 58 | 59 | ```sh 60 | pip install --upgrade opengsq 61 | ``` 62 | 63 | ## Usage 64 | 65 | Here’s an example of how to query a server using the Source protocol: 66 | 67 | ```py 68 | import asyncio 69 | from opengsq.protocols import Source 70 | 71 | async def main(): 72 | source = Source(host='45.147.5.5', port=27015) 73 | info = await source.get_info() 74 | print(info) 75 | 76 | asyncio.run(main()) 77 | ``` 78 | 79 | You can also use the Source Remote Console: 80 | 81 | ```py 82 | import asyncio 83 | from opengsq.exceptions import AuthenticationException 84 | from opengsq.rcon_protocols.source_rcon import SourceRcon 85 | 86 | async def main(): 87 | with SourceRcon("123.123.123.123", 27015) as source_rcon: 88 | try: 89 | await source_rcon.authenticate("serverRconPassword") 90 | except AuthenticationException: 91 | print('Failed to authenticate') 92 | 93 | response = await source_rcon.send_command("cvarlist") 94 | print(response) 95 | 96 | asyncio.run(main()) 97 | ``` 98 | 99 | ### Command-line interface 100 | 101 | This library additionally provides an `opengsq` command-line utility 102 | which makes it easy to query game servers from your terminal. Run 103 | `opengsq -h` for usage. 104 | 105 | ```sh 106 | # query server using source protocol 107 | opengsq source --host 123.123.123.123 --port 27015 --function get_info 108 | ``` 109 | 110 | ## Tests and Results 111 | 112 | You can find information about tests and results at [https://python.opengsq.com/tests/protocols](https://python.opengsq.com/tests/protocols) 113 | 114 | ## Contributing 115 | Contributions are welcome! Please feel free to submit pull requests or open issues. 116 | 117 | ![https://github.com/opengsq/opengsq-python/graphs/contributors](https://contrib.rocks/image?repo=opengsq/opengsq-python) 118 | 119 | ## Stargazers over time 120 | 121 | [![Stargazers over time](https://starchart.cc/opengsq/opengsq-python.svg?variant=adaptive)](https://starchart.cc/opengsq/opengsq-python) 122 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/api/.gitignore: -------------------------------------------------------------------------------- 1 | *.rst 2 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | import os 7 | 8 | # -- Project information ----------------------------------------------------- 9 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 10 | 11 | project = "OpenGSQ Python" 12 | copyright = "2024, OpenGSQ, BattlefieldDuck" 13 | author = "OpenGSQ, BattlefieldDuck" 14 | release = "2.3.1" 15 | 16 | # -- General configuration --------------------------------------------------- 17 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 18 | 19 | extensions = ["sphinx_rtd_theme", "sphinx.ext.autodoc", "sphinx_docsearch"] 20 | 21 | templates_path = ["_templates"] 22 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 23 | autodoc_member_order = "bysource" 24 | 25 | docsearch_app_id = "Z4FH0B65P0" 26 | docsearch_api_key = "703d26db3f2af38cbcb3b92d79a048bc" 27 | docsearch_index_name = "python-opengsq" 28 | 29 | 30 | # -- Options for HTML output ------------------------------------------------- 31 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 32 | 33 | html_theme = "sphinx_rtd_theme" 34 | html_favicon = "favicon.ico" 35 | html_static_path = ["_static"] 36 | 37 | # Enabling the extension only when building on GitHub Actions 38 | if os.getenv("GITHUB_ACTIONS"): 39 | extensions.append("sphinxcontrib.googleanalytics") 40 | googleanalytics_id = "G-GLNNDPSR1B" 41 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opengsq/opengsq-python/b34071f3822bd7e22e47dc9b696fe2a4f6828ff9/docs/favicon.ico -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. OpenGSQ Python documentation master file, created by 2 | sphinx-quickstart on Mon Jan 22 05:23:15 2024. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to OpenGSQ Python's documentation! 7 | ========================================== 8 | 9 | .. image:: https://github.com/opengsq/opengsq-python/actions/workflows/python-package.yml/badge.svg 10 | :target: https://github.com/opengsq/opengsq-python/actions/workflows/python-package.yml 11 | .. image:: https://img.shields.io/github/license/opengsq/opengsq-python 12 | :target: https://github.com/opengsq/opengsq-python/blob/main/LICENSE 13 | .. image:: https://img.shields.io/pypi/v/opengsq.svg 14 | :target: https://pypi.org/project/opengsq/ 15 | .. image:: https://img.shields.io/pypi/pyversions/opengsq.svg 16 | :target: https://pypi.org/project/opengsq/ 17 | .. image:: https://pepy.tech/badge/opengsq 18 | :target: https://pepy.tech/project/opengsq 19 | 20 | The OpenGSQ Python library provides a convenient way to query servers 21 | from applications written in the Python language. 22 | 23 | .. toctree:: 24 | :maxdepth: 4 25 | :caption: Get Started 26 | 27 | Home 28 | install 29 | usage 30 | 31 | .. toctree:: 32 | :maxdepth: 4 33 | :caption: API 34 | 35 | api/modules 36 | 37 | .. toctree:: 38 | :maxdepth: 4 39 | :caption: Tests 40 | 41 | tests/protocols/index 42 | tests/rcon_protocols/index 43 | 44 | 45 | Indices and tables 46 | ================== 47 | 48 | * :ref:`genindex` 49 | * :ref:`modindex` 50 | * :ref:`search` 51 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | Requirements 5 | ------------ 6 | 7 | - Python 3.7 or higher 8 | 9 | You can install the OpenGSQ library using pip: 10 | 11 | .. code-block:: console 12 | 13 | (.venv) $ pip install --upgrade opengsq 14 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx-rtd-theme==3.0.2 2 | sphinxcontrib-googleanalytics==0.5 3 | sphinx-docsearch==0.1.0 -------------------------------------------------------------------------------- /docs/tests/protocols/index.rst: -------------------------------------------------------------------------------- 1 | .. _protocols_tests: 2 | 3 | Protocols Tests 4 | =============== 5 | 6 | .. toctree:: 7 | test_gamespy4/index 8 | test_teamspeak3/index 9 | test_won/index 10 | test_toxikk/index 11 | test_gamespy1/index 12 | test_minecraft/index 13 | test_raknet/index 14 | test_eos/index 15 | test_renegadex/index 16 | test_kaillera/index 17 | test_ase/index 18 | test_quake1/index 19 | test_killingfloor/index 20 | test_source/index 21 | test_samp/index 22 | test_scum/index 23 | test_ut3/index 24 | test_unreal2/index 25 | test_quake3/index 26 | test_warcraft3/index 27 | test_nadeo/index 28 | test_battlefield/index 29 | test_fivem/index 30 | test_palworld/index 31 | test_quake2/index 32 | test_gamespy2/index 33 | test_flatout2/index 34 | test_doom3/index 35 | test_vcmp/index 36 | test_satisfactory/index 37 | test_gamespy3/index 38 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_ase/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_ase: 2 | 3 | test_ase 4 | ======== 5 | 6 | .. toctree:: 7 | test_get_status 8 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_ase/test_get_status.rst: -------------------------------------------------------------------------------- 1 | test_get_status 2 | =============== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "game_name": "mta", 10 | "game_port": 22003, 11 | "hostname": "MTA Türkiye ✖ GÜL GAMİNG FREEROAM ✖ [Roleplay/Askeri/Drift/Drop/Turkey/Tr/Gül Gaming]", 12 | "game_type": "BEDAVA - VIP", 13 | "map": "None", 14 | "version": "1.6", 15 | "password": false, 16 | "num_players": 3, 17 | "max_players": 120, 18 | "rules": {}, 19 | "players": { 20 | "name": "Gidikla_Beni", 21 | "team": "", 22 | "skin": "", 23 | "score": 0, 24 | "ping": 65, 25 | "time": 0 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_battlefield/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_battlefield: 2 | 3 | test_battlefield 4 | ================ 5 | 6 | .. toctree:: 7 | test_get_info 8 | test_get_players 9 | test_get_version 10 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_battlefield/test_get_info.rst: -------------------------------------------------------------------------------- 1 | test_get_info 2 | ============= 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "hostname": "# -bZ4- # 60HZ LOCKER NO RULES BANZORE.COM", 10 | "num_players": 57, 11 | "max_players": 64, 12 | "game_type": "ConquestLarge0", 13 | "map": "MP_Prison", 14 | "rounds_played": 0, 15 | "rounds_total": 1, 16 | "teams": [ 17 | 236.388138, 18 | 630.203 19 | ], 20 | "target_score": 0, 21 | "status": "", 22 | "ranked": true, 23 | "punk_buster": true, 24 | "password": false, 25 | "uptime": 7323, 26 | "round_time": 1325, 27 | "mod": null, 28 | "ip_port": "74.91.124.140:25200", 29 | "punk_buster_version": "v1.905 | A1390 C2.351", 30 | "join_queue": true, 31 | "region": "NAm", 32 | "ping_site": "iad", 33 | "country": "US", 34 | "blaze_player_count": 61, 35 | "blaze_game_state": "IN_GAME", 36 | "quick_match": null 37 | } 38 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_battlefield/test_get_version.rst: -------------------------------------------------------------------------------- 1 | test_get_version 2 | ================ 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "mod": "BF4", 10 | "version": "179665" 11 | } 12 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_doom3/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_doom3: 2 | 3 | test_doom3 4 | ========== 5 | 6 | .. toctree:: 7 | test_get_status 8 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_eos/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_eos: 2 | 3 | test_eos 4 | ======== 5 | 6 | .. toctree:: 7 | test_get_info 8 | test_get_info_palworld 9 | test_get_matchmaking 10 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_eos/test_get_info.rst: -------------------------------------------------------------------------------- 1 | test_get_info 2 | ============= 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "deployment": "ad9a8feffb3b4b2ca315546f038c3ae2", 10 | "id": "4ebb1f9272ec45eaab480f98fc8aa41f", 11 | "bucket": "TestGameMode_C::TheIsland_WP", 12 | "settings": { 13 | "maxPublicPlayers": 70, 14 | "allowInvites": true, 15 | "shouldAdvertise": true, 16 | "allowReadById": true, 17 | "allowJoinViaPresence": true, 18 | "allowJoinInProgress": true, 19 | "allowConferenceRoom": false, 20 | "checkSanctions": false, 21 | "allowMigration": false, 22 | "rejoinAfterKick": "", 23 | "platforms": null 24 | }, 25 | "totalPlayers": 50, 26 | "openPublicPlayers": 20, 27 | "publicPlayers": [], 28 | "started": false, 29 | "lastUpdated": null, 30 | "attributes": { 31 | "MINORBUILDID_s": "78", 32 | "MODID_l": 0, 33 | "CUSTOMSERVERNAME_s": "NA-PVE-TheIsland5366", 34 | "ADDRESSDEV_s": "5.62.115.46,127.0.0.1", 35 | "ISPRIVATE_l": 0, 36 | "SERVERPASSWORD_b": false, 37 | "MATCHTIMEOUT_d": 120.0, 38 | "ENABLEDMODSFILEIDS_s": "4979340", 39 | "DAYTIME_s": "489", 40 | "SOTFMATCHSTARTED_b": false, 41 | "STEELSHIELDENABLED_l": 1, 42 | "SERVERUSESBATTLEYE_b": true, 43 | "EOSSERVERPING_l": 211, 44 | "ALLOWDOWNLOADCHARS_l": 1, 45 | "OFFICIALSERVER_s": "1", 46 | "GAMEMODE_s": "TestGameMode_C", 47 | "ADDRESS_s": "5.62.115.46", 48 | "SEARCHKEYWORDS_s": "Custom", 49 | "__EOS_BLISTENING_b": true, 50 | "ALLOWDOWNLOADITEMS_l": 1, 51 | "LEGACY_l": 0, 52 | "ADDRESSBOUND_s": "0.0.0.0:7783", 53 | "SESSIONISPVE_l": 1, 54 | "__EOS_BUSESPRESENCE_b": true, 55 | "ENABLEDMODS_s": "927090", 56 | "SESSIONNAMEUPPER_s": "NA-PVE-THEISLAND5366 - (V33.78)", 57 | "SERVERPLATFORMTYPE_s": "PC+XSX+WINGDK+PS5", 58 | "MAPNAME_s": "TheIsland_WP", 59 | "BUILDID_s": "33", 60 | "SESSIONNAME_s": "NA-PVE-TheIsland5366 - (v33.78)" 61 | }, 62 | "owner": "Client_xyza7891muomRmynIIHaJB9COBKkwj6n", 63 | "ownerPlatformId": null 64 | } 65 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_eos/test_get_info_palworld.rst: -------------------------------------------------------------------------------- 1 | test_get_info_palworld 2 | ====================== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "deployment": "0a18471f93d448e2a1f60e47e03d3413", 10 | "id": "73dccdf58bae4bbd8ca8ab6516e9c0b5", 11 | "bucket": "Pal_1", 12 | "settings": { 13 | "maxPublicPlayers": 32, 14 | "allowInvites": false, 15 | "shouldAdvertise": true, 16 | "allowReadById": true, 17 | "allowJoinViaPresence": true, 18 | "allowJoinInProgress": true, 19 | "allowConferenceRoom": false, 20 | "checkSanctions": false, 21 | "allowMigration": false, 22 | "rejoinAfterKick": "", 23 | "platforms": null 24 | }, 25 | "totalPlayers": 0, 26 | "openPublicPlayers": 32, 27 | "publicPlayers": [], 28 | "started": true, 29 | "lastUpdated": null, 30 | "attributes": { 31 | "OWNINGUSERNAME_s": "DedicatedServer - euid65532", 32 | "GAMESERVER_PORT_l": 30111, 33 | "DAYS_l": 254, 34 | "NUMPRIVATECONNECTIONS_l": 0, 35 | "TYPE_s": "Official", 36 | "CREATE_TIME_l": 1707082563, 37 | "SERVERTIME_l": 46, 38 | "DEDICATEDONLY_b": true, 39 | "ISPASSWORD_b": false, 40 | "VERSION_s": "v0.1.4.0", 41 | "NAMESPACE_s": "production", 42 | "BANTICHEATPROTECTED_b": false, 43 | "BUSESSTATS_b": false, 44 | "WORLDGUID_s": "0F4EAE5CD6D14FBA86E3C95DAF86E61B", 45 | "REGION_s": "NA", 46 | "NAME_s": "Official NA Server 0111", 47 | "ADDRESS_s": "35.226.201.18", 48 | "PRESENCESEARCH_b": true, 49 | "NUMPUBLICCONNECTIONS_l": 32, 50 | "DESCRIPTION_s": "All guild items, buildings and Pals will be REMOVED if no guild members have logged in to the server in the last 168 hours.", 51 | "BUILDUNIQUEID_l": 0, 52 | "BISDEDICATED_b": true, 53 | "PLAYERS_l": 12, 54 | "MAPNAME_s": "MainWorld5" 55 | }, 56 | "owner": "Client_xyza78918C8ZyYGcxMcpvTQjPFdjtykx", 57 | "ownerPlatformId": null 58 | } 59 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_fivem/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_fivem: 2 | 3 | test_fivem 4 | ========== 5 | 6 | .. toctree:: 7 | test_get_dynamic 8 | test_get_info 9 | test_get_players 10 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_fivem/test_get_dynamic.rst: -------------------------------------------------------------------------------- 1 | test_get_dynamic 2 | ================ 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "clients": 0, 10 | "gametype": "Freeroam", 11 | "hostname": "LostCityRP", 12 | "iv": "1464799579", 13 | "mapname": "fivem-map-hipster", 14 | "sv_maxclients": "128" 15 | } 16 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_fivem/test_get_players.rst: -------------------------------------------------------------------------------- 1 | test_get_players 2 | ================ 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | [] 9 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_flatout2/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_flatout2: 2 | 3 | test_flatout2 4 | ============= 5 | 6 | .. toctree:: 7 | test_flatout2_get_status 8 | test_get_status 9 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_flatout2/test_flatout2_get_status.rst: -------------------------------------------------------------------------------- 1 | test_flatout2_get_status 2 | ======================== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "info": { 10 | "hostname": "TestServer", 11 | "timestamp": "-4022673819270859506", 12 | "flags": "679480060", 13 | "status": "3422879744", 14 | "config": "c00000000000081000086124" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_flatout2/test_get_status.rst: -------------------------------------------------------------------------------- 1 | test_get_status 2 | =============== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "info": { 10 | "hostname": "TestServer", 11 | "timestamp": "1234567890", 12 | "flags": "2818572332", 13 | "status": "6094848", 14 | "config": "000000000810000861240400101440" 15 | }, 16 | "players": [] 17 | } -------------------------------------------------------------------------------- /docs/tests/protocols/test_gamespy1/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_gamespy1: 2 | 3 | test_gamespy1 4 | ============= 5 | 6 | .. toctree:: 7 | test_get_basic 8 | test_get_info 9 | test_get_players 10 | test_get_rules 11 | test_get_status 12 | test_get_teams 13 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_gamespy1/test_get_basic.rst: -------------------------------------------------------------------------------- 1 | test_get_basic 2 | ============== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "gamename": "ut", 10 | "gamever": "469", 11 | "minnetver": "432", 12 | "location": "0" 13 | } 14 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_gamespy1/test_get_info.rst: -------------------------------------------------------------------------------- 1 | test_get_info 2 | ============= 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "hostname": "--- ComboGib CTF (Grapple) -- London --- EatSleepUT.com", 10 | "hostport": "7777", 11 | "maptitle": "2on2 Crates spfix", 12 | "mapname": "CTF-2on2-Crates_spfix", 13 | "gametype": "CTFGame", 14 | "numplayers": "8", 15 | "maxplayers": "18", 16 | "gamemode": "openplaying", 17 | "gamever": "469", 18 | "minnetver": "432", 19 | "worldlog": "false", 20 | "wantworldlog": "false" 21 | } 22 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_gamespy1/test_get_players.rst: -------------------------------------------------------------------------------- 1 | test_get_players 2 | ================ 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | [ 9 | { 10 | "player": "|Wacko|", 11 | "frags": "0", 12 | "ping": "44", 13 | "team": "255", 14 | "mesh": "Spectator", 15 | "skin": "None", 16 | "face": "", 17 | "ngsecret": "true" 18 | }, 19 | { 20 | "player": "O..", 21 | "frags": "99", 22 | "ping": "68", 23 | "team": "0", 24 | "mesh": "Female Commando", 25 | "skin": "FCommandoSkins.daco", 26 | "face": "FCommandoSkins.Jayce", 27 | "ngsecret": "true" 28 | }, 29 | { 30 | "player": "KFV", 31 | "frags": "169", 32 | "ping": "226", 33 | "team": "1", 34 | "mesh": "Female Commando", 35 | "skin": "FCommandoSkins.aphe", 36 | "face": "FCommandoSkins.Indina", 37 | "ngsecret": "true" 38 | }, 39 | { 40 | "player": "uranus", 41 | "frags": "274", 42 | "ping": "102", 43 | "team": "1", 44 | "mesh": "Female Commando", 45 | "skin": "FCommandoSkins.goth", 46 | "face": "FCommandoSkins.Visse", 47 | "ngsecret": "true" 48 | }, 49 | { 50 | "player": "ul~Cllectr", 51 | "frags": "283", 52 | "ping": "89", 53 | "team": "0", 54 | "mesh": "", 55 | "skin": "FCommandoSkins.goth", 56 | "face": "FCommandoSkins.Freylis", 57 | "ngsecret": "true" 58 | }, 59 | { 60 | "player": "bustacell", 61 | "frags": "992", 62 | "ping": "38", 63 | "team": "1", 64 | "mesh": "Female Commando", 65 | "skin": "FCommandoSkins.goth", 66 | "face": "FCommandoSkins.Malise", 67 | "ngsecret": "true" 68 | }, 69 | { 70 | "player": "munro.pa", 71 | "frags": "459", 72 | "ping": "46", 73 | "team": "0", 74 | "mesh": "Male Soldier", 75 | "skin": "SoldierSkins.RawS", 76 | "face": "SoldierSkins.Arkon", 77 | "ngsecret": "true" 78 | }, 79 | { 80 | "player": "Jamally", 81 | "frags": "663", 82 | "ping": "38", 83 | "team": "0", 84 | "mesh": "Male Commando", 85 | "skin": "CommandoSkins.cmdo", 86 | "face": "CommandoSkins.Gorn", 87 | "ngsecret": "true" 88 | } 89 | ] 90 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_gamespy1/test_get_rules.rst: -------------------------------------------------------------------------------- 1 | test_get_rules 2 | ============== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "mutators": "NDdj4, NDgrap49c, Revenge 2, Player Commands V01a, NoSelfDamage, MapVoteLAv2 Big21a, Auto Team Balance, SmartCTF 4E ESU2", 10 | "listenserver": "False", 11 | "password": "False", 12 | "timelimit": "20", 13 | "goalteamscore": "7", 14 | "minplayers": "6", 15 | "changelevels": "True", 16 | "maxteams": "2", 17 | "balanceteams": "True", 18 | "playersbalanceteams": "True", 19 | "friendlyfire": "0%", 20 | "tournament": "False", 21 | "gamestyle": "Hardcore", 22 | "botskill": "Skilled", 23 | "adminname": "snowguy", 24 | "adminemail": "negruler@gmail.com" 25 | } 26 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_gamespy1/test_get_status.rst: -------------------------------------------------------------------------------- 1 | test_get_status 2 | =============== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "info": { 10 | "gamename": "ut", 11 | "gamever": "469", 12 | "minnetver": "432", 13 | "location": "0", 14 | "hostname": "--- ComboGib CTF (Grapple) -- London --- EatSleepUT.com", 15 | "hostport": "7777", 16 | "maptitle": "2on2 Crates spfix", 17 | "mapname": "CTF-2on2-Crates_spfix", 18 | "gametype": "CTFGame", 19 | "numplayers": "8", 20 | "maxplayers": "18", 21 | "gamemode": "openplaying", 22 | "worldlog": "false", 23 | "wantworldlog": "false", 24 | "mutators": "NDdj4, NDgrap49c, Revenge 2, Player Commands V01a, NoSelfDamage, MapVoteLAv2 Big21a, Auto Team Balance, SmartCTF 4E ESU2", 25 | "listenserver": "False", 26 | "password": "False", 27 | "timelimit": "20", 28 | "goalteamscore": "7", 29 | "minplayers": "6", 30 | "changelevels": "True", 31 | "maxteams": "2", 32 | "balanceteams": "True", 33 | "playersbalanceteams": "True", 34 | "friendlyfire": "0%", 35 | "tournament": "False", 36 | "gamestyle": "Hardcore", 37 | "botskill": "Skilled", 38 | "adminname": "snowguy", 39 | "adminemail": "negruler@gmail.com" 40 | }, 41 | "players": [ 42 | { 43 | "player": "|Wacko|", 44 | "frags": "0", 45 | "ping": "43", 46 | "team": "255", 47 | "mesh": "Spectator", 48 | "skin": "None", 49 | "face": "", 50 | "ngsecret": "true" 51 | }, 52 | { 53 | "player": "O..", 54 | "frags": "99", 55 | "ping": "67", 56 | "team": "0", 57 | "mesh": "Female Commando", 58 | "skin": "FCommandoSkins.daco", 59 | "face": "FCommandoSkins.Jayce", 60 | "ngsecret": "true" 61 | }, 62 | { 63 | "player": "KFV", 64 | "frags": "169", 65 | "ping": "226", 66 | "team": "1", 67 | "mesh": "Female Commando", 68 | "skin": "FCommandoSkins.aphe", 69 | "face": "FCommandoSkins.Indina", 70 | "ngsecret": "true" 71 | }, 72 | { 73 | "player": "uranus", 74 | "frags": "274", 75 | "ping": "104", 76 | "team": "1", 77 | "mesh": "Female Commando", 78 | "skin": "FCommandoSkins.goth", 79 | "face": "FCommandoSkins.Visse", 80 | "ngsecret": "true" 81 | }, 82 | { 83 | "player": "ul~Cllectr", 84 | "frags": "283", 85 | "ping": "91", 86 | "team": "0", 87 | "mesh": "", 88 | "skin": "FCommandoSkins.goth", 89 | "face": "FCommandoSkins.Freylis", 90 | "ngsecret": "true" 91 | }, 92 | { 93 | "player": "bustacell", 94 | "frags": "992", 95 | "ping": "38", 96 | "team": "1", 97 | "mesh": "Female Commando", 98 | "skin": "FCommandoSkins.goth", 99 | "face": "FCommandoSkins.Malise", 100 | "ngsecret": "true" 101 | }, 102 | { 103 | "player": "munro.pa", 104 | "frags": "459", 105 | "ping": "48", 106 | "team": "0", 107 | "mesh": "Male Soldier", 108 | "skin": "SoldierSkins.RawS", 109 | "face": "SoldierSkins.Arkon", 110 | "ngsecret": "true" 111 | }, 112 | { 113 | "player": "Jamally", 114 | "frags": "663", 115 | "ping": "38", 116 | "team": "0", 117 | "mesh": "Male Commando", 118 | "skin": "CommandoSkins.cmdo", 119 | "face": "CommandoSkins.Gorn", 120 | "ngsecret": "true" 121 | } 122 | ], 123 | "teams": [] 124 | } 125 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_gamespy1/test_get_teams.rst: -------------------------------------------------------------------------------- 1 | test_get_teams 2 | ============== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | [ 9 | { 10 | "team": "Red", 11 | "score": "7.000000", 12 | "size": "4" 13 | }, 14 | { 15 | "team": "Blue", 16 | "score": "6.000000", 17 | "size": "3" 18 | }, 19 | { 20 | "team": "Green", 21 | "score": "0.000000", 22 | "size": "0" 23 | }, 24 | { 25 | "team": "Gold", 26 | "score": "0.000000", 27 | "size": "0" 28 | } 29 | ] 30 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_gamespy2/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_gamespy2: 2 | 3 | test_gamespy2 4 | ============= 5 | 6 | .. toctree:: 7 | test_get_status 8 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_gamespy2/test_get_status.rst: -------------------------------------------------------------------------------- 1 | test_get_status 2 | =============== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "info": { 10 | "hostname": "*Easy Prey/Death*", 11 | "hostport": "15567", 12 | "mapname": "HUE [Choppers] -SSM-", 13 | "gametype": "coop", 14 | "numplayers": "0", 15 | "maxplayers": "50", 16 | "gamemode": "openplaying", 17 | "password": "0", 18 | "gamever": "v1.21", 19 | "dedicated": "2", 20 | "status": "4", 21 | "game_id": "bfvietnam", 22 | "map_id": "BFVietnam", 23 | "sv_punkbuster": "0", 24 | "timelimit": "60", 25 | "number_of_rounds": "1", 26 | "spawn_wave_time": "4s", 27 | "spawn_delay": "10s", 28 | "soldier_friendly_fire": "30%", 29 | "vehicle_friendly_fire": "50%", 30 | "game_start_delay": "75s", 31 | "ticket_ratio": "271%", 32 | "allow_nose_cam": "yes", 33 | "external_view": "on", 34 | "us_team_ratio": "1", 35 | "nva_team_ratio": "1", 36 | "bandwidth_choke_limit": "0", 37 | "free_camera": "off", 38 | "auto_balance_teams": "off", 39 | "name_tag_distance": "100", 40 | "name_tag_distance_scope": "300", 41 | "kickback": "30%", 42 | "kickback_on_splash": "30%", 43 | "soldier_friendly_fire_on_splash": "100%", 44 | "vehicle_friendly_fire_on_splash": "100%", 45 | "cpu": "3599", 46 | "bot_skill": "100%", 47 | "reservedslots": "0", 48 | "spectatorsallowed": "1", 49 | "spectatorvoting": "0", 50 | "spectatorswitchtime": "30s", 51 | "active_mods": "", 52 | "all_active_mods": "bfvietnam", 53 | "game_id_name": "BFVietnam" 54 | }, 55 | "players": [], 56 | "teams": [ 57 | { 58 | "team": "red", 59 | "tickets": "438" 60 | }, 61 | { 62 | "team": "blue", 63 | "tickets": "656" 64 | } 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_gamespy3/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_gamespy3: 2 | 3 | test_gamespy3 4 | ============= 5 | 6 | .. toctree:: 7 | test_get_status 8 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_gamespy4/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_gamespy4: 2 | 3 | test_gamespy4 4 | ============= 5 | 6 | .. toctree:: 7 | test_get_status 8 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_gamespy4/test_get_status.rst: -------------------------------------------------------------------------------- 1 | test_get_status 2 | =============== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "info": { 10 | "hostname": "lbAvenge6Tech", 11 | "gametype": "SMP", 12 | "map": "WaterdogPE", 13 | "numplayers": "6", 14 | "maxplayers": "300", 15 | "hostport": "19132", 16 | "hostip": "0.0.0.0", 17 | "game_id": "MINECRAFTPE", 18 | "version": "", 19 | "plugins": "", 20 | "whitelist": "off" 21 | }, 22 | "players": [ 23 | { 24 | "name": "XxNE0BLAZExX777" 25 | }, 26 | { 27 | "name": "shadowwolf32538" 28 | }, 29 | { 30 | "name": "Blobber420" 31 | }, 32 | { 33 | "name": "IronFlam3s" 34 | }, 35 | { 36 | "name": "AstrayApple6" 37 | }, 38 | { 39 | "name": "Muqadir" 40 | } 41 | ], 42 | "teams": [] 43 | } 44 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_kaillera/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_kaillera: 2 | 3 | test_kaillera 4 | ============= 5 | 6 | .. toctree:: 7 | test_get_status 8 | test_query_master_servers 9 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_kaillera/test_get_status.rst: -------------------------------------------------------------------------------- 1 | test_get_status 2 | =============== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | true 9 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_killingfloor/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_killingfloor: 2 | 3 | test_killingfloor 4 | ================= 5 | 6 | .. toctree:: 7 | test_get_details 8 | test_get_players 9 | test_get_rules 10 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_killingfloor/test_get_details.rst: -------------------------------------------------------------------------------- 1 | test_get_details 2 | ================ 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "server_id": 0, 10 | "server_ip": "", 11 | "game_port": 7707, 12 | "query_port": 0, 13 | "server_name": "WS-GAMING.EU | NORMAL | 60LVL [#1] (!PERK RESET!)", 14 | "map_name": "KF-PoliceStationOnEWIPE", 15 | "game_type": "UZGameType", 16 | "num_players": 23, 17 | "max_players": 25, 18 | "ping": 0, 19 | "flags": 64, 20 | "skill": "0", 21 | "wave_current": 11, 22 | "wave_total": 10 23 | } 24 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_killingfloor/test_get_players.rst: -------------------------------------------------------------------------------- 1 | test_get_players 2 | ================ 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | [ 9 | { 10 | "id": 11018, 11 | "name": "sickboy", 12 | "ping": 56, 13 | "score": 500, 14 | "stats_id": 536870912 15 | }, 16 | { 17 | "id": 10989, 18 | "name": "DarkSeid", 19 | "ping": 92, 20 | "score": 1410, 21 | "stats_id": 536870912 22 | }, 23 | { 24 | "id": 10947, 25 | "name": "Jorji", 26 | "ping": 92, 27 | "score": 376, 28 | "stats_id": 536870912 29 | }, 30 | { 31 | "id": 10931, 32 | "name": "TurboGlist", 33 | "ping": 68, 34 | "score": 38020, 35 | "stats_id": 536870912 36 | }, 37 | { 38 | "id": 10663, 39 | "name": "CitruS", 40 | "ping": 40, 41 | "score": 522, 42 | "stats_id": 536870912 43 | }, 44 | { 45 | "id": 10419, 46 | "name": "Latusicosta", 47 | "ping": 20, 48 | "score": 1189, 49 | "stats_id": 536870912 50 | }, 51 | { 52 | "id": 9881, 53 | "name": "[RUS]SEREGA", 54 | "ping": 68, 55 | "score": 7291, 56 | "stats_id": 536870912 57 | }, 58 | { 59 | "id": 9786, 60 | "name": "hoodrich", 61 | "ping": 84, 62 | "score": 234, 63 | "stats_id": 536870912 64 | }, 65 | { 66 | "id": 9506, 67 | "name": "Dex", 68 | "ping": 48, 69 | "score": 10933, 70 | "stats_id": 536870912 71 | }, 72 | { 73 | "id": 9156, 74 | "name": "F@$T2", 75 | "ping": 56, 76 | "score": 14000, 77 | "stats_id": 536870912 78 | }, 79 | { 80 | "id": 9049, 81 | "name": "AntiParazite", 82 | "ping": 44, 83 | "score": 13536, 84 | "stats_id": 536870912 85 | }, 86 | { 87 | "id": 8498, 88 | "name": "Perrito", 89 | "ping": 140, 90 | "score": 4079, 91 | "stats_id": 536870912 92 | }, 93 | { 94 | "id": 8393, 95 | "name": "[LTU]ASTRALAS:)P", 96 | "ping": 44, 97 | "score": 4127, 98 | "stats_id": 536870912 99 | }, 100 | { 101 | "id": 8377, 102 | "name": "Shrine", 103 | "ping": 88, 104 | "score": 9238, 105 | "stats_id": 536870912 106 | }, 107 | { 108 | "id": 7460, 109 | "name": "Goldstar", 110 | "ping": 32, 111 | "score": 5544, 112 | "stats_id": 536870912 113 | }, 114 | { 115 | "id": 5778, 116 | "name": "MeSka", 117 | "ping": 20, 118 | "score": 11086, 119 | "stats_id": 536870912 120 | }, 121 | { 122 | "id": 4346, 123 | "name": "Kojotas", 124 | "ping": 28, 125 | "score": 13007, 126 | "stats_id": 536870912 127 | } 128 | ] 129 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_killingfloor/test_get_rules.rst: -------------------------------------------------------------------------------- 1 | test_get_rules 2 | ============== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "Mutators": [ 10 | "KillingFloorMut", 11 | "SpecimenHPConfigMut", 12 | "CleanAppIDMut", 13 | "MutSlotMachine", 14 | "KFShareCashMut", 15 | "ServerPerksMut" 16 | ], 17 | "ServerMode": "dedicated", 18 | "AdminName": "", 19 | "AdminEmail": "kf@ws-gaming.eu", 20 | "ServerVersion": "1065", 21 | "IsVacSecured": "true", 22 | "MaxSpectators": "20", 23 | "MapVoting": "true", 24 | "KickVoting": "true", 25 | "Slot Machines": "Ver 200", 26 | "Veterancy Handler": "Ver 700", 27 | "Veterancy saving": "Enabled", 28 | "Min perk level": "0", 29 | "Max perk level": "60", 30 | "Num trader weapons": "123" 31 | } 32 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_minecraft/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_minecraft: 2 | 3 | test_minecraft 4 | ============== 5 | 6 | .. toctree:: 7 | test_get_status 8 | test_get_status_pre17 9 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_minecraft/test_get_status_pre17.rst: -------------------------------------------------------------------------------- 1 | test_get_status_pre17 2 | ===================== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "protocol": "127", 10 | "version": "Waterfall 1.8.x, 1.9.x, 1.10.x, 1.11.x, 1.12.x, 1.13.x, 1.14.x, 1.15.x, 1.16.x, 1.17.x, 1.18.x, 1.19.x, 1.20.x", 11 | "motd": " ---|!\\!GOLDCRAFT.IR!\\!|--- v1.20.1", 12 | "num_players": 0, 13 | "max_players": 2023 14 | } 15 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_nadeo/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_nadeo: 2 | 3 | test_nadeo 4 | ========== 5 | 6 | .. toctree:: 7 | test_get_status 8 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_nadeo/test_get_status.rst: -------------------------------------------------------------------------------- 1 | test_get_status 2 | =============== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "version": { 10 | "name": "TmForever", 11 | "version": "2.11.26", 12 | "build": "2011-02-21" 13 | }, 14 | "server_options": { 15 | "name": "GamedigTest", 16 | "comment": "FixtheBug", 17 | "password": "", 18 | "max_players": 32, 19 | "max_spectators": 32, 20 | "current_game_mode": 0, 21 | "current_chat_time": 0, 22 | "hide_server": 0, 23 | "ladder_mode": 1, 24 | "vehicle_quality": 0 25 | }, 26 | "players": [], 27 | "map_info": { 28 | "name": "C15-Speed", 29 | "author": "Nadeo", 30 | "environment": "" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_palworld/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_palworld: 2 | 3 | test_palworld 4 | ================= 5 | 6 | .. toctree:: 7 | test_get_status 8 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_palworld/test_get_status.rst: -------------------------------------------------------------------------------- 1 | test_get_status 2 | =============== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "num_players": 3, 10 | "max_players": 32, 11 | "server_name": "A Palworld Server" 12 | } 13 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_quake1/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_quake1: 2 | 3 | test_quake1 4 | =========== 5 | 6 | .. toctree:: 7 | test_get_status 8 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_quake1/test_get_status.rst: -------------------------------------------------------------------------------- 1 | test_get_status 2 | =============== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "info": { 10 | "spawn": "0", 11 | "*z_ext": "32", 12 | "*gamedir": "fortress", 13 | "hostname": "[BOTS] CustomTF For The People [BOTS]", 14 | "community": "https://discord.gg/fp9sSZ5", 15 | "admin/dev": "Pulse-", 16 | "n": "4", 17 | "rj": "2", 18 | "curse": "5", 19 | "deathmatch": "3", 20 | "timelimit": "50", 21 | "fraglimit": "500", 22 | "maxclients": "32", 23 | "maxspectators": "16", 24 | "samelevel": "0", 25 | "money": "11000", 26 | "watervis": "0", 27 | "map": "dm3", 28 | "teamplay": "0" 29 | }, 30 | "players": [ 31 | { 32 | "id": 102, 33 | "score": 0, 34 | "time": 0, 35 | "ping": 807, 36 | "name": "Data", 37 | "skin": "tf_pyro", 38 | "color1": 4, 39 | "color2": 9 40 | }, 41 | { 42 | "id": 90, 43 | "score": -1, 44 | "time": 48, 45 | "ping": 807, 46 | "name": "Max", 47 | "skin": "tf_spy", 48 | "color1": 7, 49 | "color2": 4 50 | }, 51 | { 52 | "id": 91, 53 | "score": 1, 54 | "time": 48, 55 | "ping": 807, 56 | "name": "T-1000", 57 | "skin": "tf_sold", 58 | "color1": 3, 59 | "color2": 1 60 | }, 61 | { 62 | "id": 93, 63 | "score": 1, 64 | "time": 48, 65 | "ping": 807, 66 | "name": "Bender", 67 | "skin": "tf_eng", 68 | "color1": 12, 69 | "color2": 13 70 | }, 71 | { 72 | "id": 94, 73 | "score": 0, 74 | "time": 48, 75 | "ping": 807, 76 | "name": "Optimus Prime", 77 | "skin": "tf_pyro", 78 | "color1": 6, 79 | "color2": 7 80 | }, 81 | { 82 | "id": 95, 83 | "score": 2, 84 | "time": 48, 85 | "ping": 807, 86 | "name": "Gort", 87 | "skin": "tf_snipe", 88 | "color1": 5, 89 | "color2": 2 90 | }, 91 | { 92 | "id": 96, 93 | "score": 0, 94 | "time": 48, 95 | "ping": 807, 96 | "name": "Lore", 97 | "skin": "tf_pyro", 98 | "color1": 6, 99 | "color2": 12 100 | }, 101 | { 102 | "id": 97, 103 | "score": 0, 104 | "time": 48, 105 | "ping": 807, 106 | "name": "Servo", 107 | "skin": "tf_spy", 108 | "color1": 4, 109 | "color2": 4 110 | }, 111 | { 112 | "id": 98, 113 | "score": 0, 114 | "time": 48, 115 | "ping": 807, 116 | "name": "R2-D2", 117 | "skin": "tf_hwguy", 118 | "color1": 9, 119 | "color2": 8 120 | }, 121 | { 122 | "id": 99, 123 | "score": -1, 124 | "time": 48, 125 | "ping": 807, 126 | "name": "Hadaly", 127 | "skin": "tf_sold", 128 | "color1": 2, 129 | "color2": 0 130 | }, 131 | { 132 | "id": 100, 133 | "score": 0, 134 | "time": 48, 135 | "ping": 807, 136 | "name": "Gypsy", 137 | "skin": "tf_sold", 138 | "color1": 9, 139 | "color2": 8 140 | }, 141 | { 142 | "id": 101, 143 | "score": 0, 144 | "time": 0, 145 | "ping": 48, 146 | "name": "", 147 | "skin": "base", 148 | "color1": 0, 149 | "color2": 0 150 | } 151 | ] 152 | } 153 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_quake2/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_quake2: 2 | 3 | test_quake2 4 | =========== 5 | 6 | .. toctree:: 7 | test_get_status 8 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_quake2/test_get_status.rst: -------------------------------------------------------------------------------- 1 | test_get_status 2 | =============== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "info": { 10 | "JumpMod": "1.35ger", 11 | "capturelimit": "0", 12 | "cheats": "0", 13 | "competition": "0", 14 | "ctf": "1", 15 | "deathmatch": "1", 16 | "dmflags": "524308", 17 | "fraglimit": "0", 18 | "game": "jump", 19 | "gamedate": "May 31 2022", 20 | "gamedir": "jump", 21 | "gamename": "Q2JUMP 1.35ger", 22 | "hostname": ".german q2 jump", 23 | "mapname": "as16", 24 | "matchlock": "1", 25 | "matchtime": "20", 26 | "maxclients": "15", 27 | "port": "27910", 28 | "protocol": "34", 29 | "time_remaining": "08:08", 30 | "timelimit": "0", 31 | "version": "q2proded r1826~7ad132a Dec 28 2018 Linux x86_64", 32 | "uptime": "2 days, 14 hours, 34 mins, 13 secs" 33 | }, 34 | "players": [ 35 | { 36 | "frags": 0, 37 | "ping": 117, 38 | "name": "Killa", 39 | "address": "" 40 | }, 41 | { 42 | "frags": 0, 43 | "ping": 31, 44 | "name": "sata", 45 | "address": "" 46 | }, 47 | { 48 | "frags": 0, 49 | "ping": 214, 50 | "name": "Barry_Allen", 51 | "address": "" 52 | }, 53 | { 54 | "frags": 0, 55 | "ping": 120, 56 | "name": "bob jr", 57 | "address": "" 58 | } 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_quake3/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_quake3: 2 | 3 | test_quake3 4 | =========== 5 | 6 | .. toctree:: 7 | test_get_info 8 | test_get_status 9 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_quake3/test_get_info.rst: -------------------------------------------------------------------------------- 1 | test_get_info 2 | ============= 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "challenge": "opengsq", 10 | "version": "ET Legacy v2.81.1-406-g1428029 linux-i386 Nov 24 2023", 11 | "protocol": "84", 12 | "hostname": "F|A RECRUITING XP SAVE", 13 | "serverload": "2", 14 | "mapname": "river_port", 15 | "clients": "46", 16 | "humans": "46", 17 | "sv_maxclients": "60", 18 | "sv_privateclients": "0", 19 | "gametype": "2", 20 | "pure": "1", 21 | "game": "jaymod", 22 | "friendlyFire": "0", 23 | "maxlives": "0", 24 | "needpass": "0", 25 | "gamename": "et", 26 | "g_antilag": "1", 27 | "weaprestrict": "100", 28 | "balancedteams": "1" 29 | } 30 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_raknet/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_raknet: 2 | 3 | test_raknet 4 | =========== 5 | 6 | .. toctree:: 7 | test_get_status 8 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_raknet/test_get_status.rst: -------------------------------------------------------------------------------- 1 | test_get_status 2 | =============== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "edition": "MCPE", 10 | "motd_line1": "Advancius Network", 11 | "protocol_version": 630, 12 | "version_name": "1.20.50", 13 | "num_players": 171, 14 | "max_players": 400, 15 | "server_unique_id": "17184725635914652259", 16 | "motd_line2": "discord.advancius.net", 17 | "game_mode": "Survival", 18 | "game_mode_numeric": 1, 19 | "port_ipv4": 19132, 20 | "port_ipv6": 0 21 | } 22 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_renegadex/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_renegadex: 2 | 3 | test_renegadex 4 | ============== 5 | 6 | .. toctree:: 7 | test_renegadex_status -------------------------------------------------------------------------------- /docs/tests/protocols/test_renegadex/test_renegadex_status.rst: -------------------------------------------------------------------------------- 1 | test_renegadex_status 2 | ===================== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "name": "Ich bin ein ziemlich langer Server der nicht weis wie lang das geht", 10 | "map": "CNC-Field", 11 | "port": 7777, 12 | "players": 0, 13 | "game_version": "Open Beta 5.85.815", 14 | "variables": { 15 | "player_limit": 64, 16 | "vehicle_limit": 20, 17 | "mine_limit": 24, 18 | "time_limit": 50, 19 | "passworded": false, 20 | "steam_required": false, 21 | "team_mode": 6, 22 | "spawn_crates": true, 23 | "game_type": 1, 24 | "ranked": false 25 | }, 26 | "raw": { 27 | "Current Map": "CNC-Field", 28 | "Players": 0, 29 | "Port": 7777, 30 | "Name": "Ich bin ein ziemlich langer Server der nicht weis wie lang das geht", 31 | "IP": "10.13.37.149", 32 | "Game Version": "Open Beta 5.85.815", 33 | "Variables": { 34 | "Player Limit": 64, 35 | "Vehicle Limit": 20, 36 | "Mine Limit": 24, 37 | "Time Limit": 50, 38 | "bPassworded": false, 39 | "bSteamRequired": false, 40 | "Team Mode": 6, 41 | "bSpawnCrates": true, 42 | "Game Type": 1, 43 | "bRanked": false 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /docs/tests/protocols/test_samp/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_samp: 2 | 3 | test_samp 4 | ========= 5 | 6 | .. toctree:: 7 | test_get_players 8 | test_get_rules 9 | test_get_status 10 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_samp/test_get_players.rst: -------------------------------------------------------------------------------- 1 | test_get_players 2 | ================ 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | [ 9 | { 10 | "id": 0, 11 | "name": "Bofade_Eznuts", 12 | "score": 26, 13 | "ping": 51 14 | }, 15 | { 16 | "id": 1, 17 | "name": "[FSD] Thomas_Carlton", 18 | "score": 28, 19 | "ping": 113 20 | }, 21 | { 22 | "id": 2, 23 | "name": "Lars_Vegas", 24 | "score": 27, 25 | "ping": 211 26 | }, 27 | { 28 | "id": 3, 29 | "name": "[SAPD] Thaven_Wright", 30 | "score": 25, 31 | "ping": 299 32 | }, 33 | { 34 | "id": 4, 35 | "name": "Brian_Dolf", 36 | "score": 18, 37 | "ping": 83 38 | }, 39 | { 40 | "id": 5, 41 | "name": "Frankie_OConnor", 42 | "score": 21, 43 | "ping": 40 44 | }, 45 | { 46 | "id": 7, 47 | "name": "[VIP] Valentino_Reiner", 48 | "score": 28, 49 | "ping": 56 50 | }, 51 | { 52 | "id": 8, 53 | "name": "[CDM] Gustav_Gaviria", 54 | "score": 27, 55 | "ping": 86 56 | }, 57 | { 58 | "id": 9, 59 | "name": "Manuel_Noriega", 60 | "score": 14, 61 | "ping": 55 62 | }, 63 | { 64 | "id": 12, 65 | "name": "Brandon_Carter", 66 | "score": 18, 67 | "ping": 172 68 | }, 69 | { 70 | "id": 14, 71 | "name": "jesse_pinkmann", 72 | "score": 14, 73 | "ping": 143 74 | }, 75 | { 76 | "id": 19, 77 | "name": "Matthew_Reiner", 78 | "score": 33, 79 | "ping": 46 80 | }, 81 | { 82 | "id": 48, 83 | "name": "Robin_Masters", 84 | "score": 30, 85 | "ping": 91 86 | } 87 | ] 88 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_samp/test_get_rules.rst: -------------------------------------------------------------------------------- 1 | test_get_rules 2 | ============== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "SAMPCAC version": "0.10.0", 10 | "lagcomp": "On", 11 | "mapname": "San Andreas", 12 | "version": "0.3.7-R2", 13 | "weather": "10", 14 | "weburl": "valrisegaming.com", 15 | "worldtime": "12:00" 16 | } 17 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_samp/test_get_status.rst: -------------------------------------------------------------------------------- 1 | test_get_status 2 | =============== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "password": false, 10 | "num_players": 13, 11 | "max_players": 250, 12 | "server_name": "[ENG] Valrise RPG [Est. 2017]", 13 | "game_type": "Valrise RP 2.0.14 (RPG/RP)", 14 | "language": "English" 15 | } 16 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_satisfactory/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_satisfactory: 2 | 3 | test_satisfactory 4 | ================= 5 | 6 | .. toctree:: 7 | test_get_status 8 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_satisfactory/test_get_status.rst: -------------------------------------------------------------------------------- 1 | test_get_status 2 | =============== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "state": 3, 10 | "version": 273254, 11 | "beacon_port": 15000 12 | } 13 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_scum/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_scum: 2 | 3 | test_scum 4 | ========= 5 | 6 | .. toctree:: 7 | test_get_status 8 | test_query_master_servers 9 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_scum/test_get_status.rst: -------------------------------------------------------------------------------- 1 | test_get_status 2 | =============== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "ip": "15.235.181.19", 10 | "port": 7042, 11 | "name": "2.4新开★ 1100★ 全图PVP★ 特色地堡黑市★ 小队福利★免费炼体", 12 | "num_players": 15, 13 | "max_players": 100, 14 | "time": 10, 15 | "password": false, 16 | "version": "0.9.515.81588" 17 | } 18 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_source/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_source: 2 | 3 | test_source 4 | =========== 5 | 6 | .. toctree:: 7 | test_get_info 8 | test_get_players 9 | test_get_rules 10 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_source/test_get_info.rst: -------------------------------------------------------------------------------- 1 | test_get_info 2 | ============= 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "protocol": 17, 10 | "name": "shounic trenches (USA Chicago)", 11 | "map": "pl_dbz_b3", 12 | "folder": "tf", 13 | "game": "Team Fortress", 14 | "players": 100, 15 | "max_players": 100, 16 | "bots": 0, 17 | "server_type": 100, 18 | "environment": 119, 19 | "visibility": 0, 20 | "vac": 1, 21 | "id": 440, 22 | "version": "8622567", 23 | "edf": 241, 24 | "port": 27015, 25 | "steam_id": 85568392929506771, 26 | "spectator_port": 27020, 27 | "spectator_name": "(stv bot)", 28 | "keywords": "alltalk,increased_maxplayers,payload,shounic", 29 | "game_id": 440, 30 | "mode": null, 31 | "witnesses": null, 32 | "duration": null 33 | } 34 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_teamspeak3/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_teamspeak3: 2 | 3 | test_teamspeak3 4 | =============== 5 | 6 | .. toctree:: 7 | test_get_channels 8 | test_get_clients 9 | test_get_info 10 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_toxikk/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_toxikk: 2 | 3 | test_toxikk 4 | =========== 5 | 6 | .. toctree:: 7 | test_toxikk_status -------------------------------------------------------------------------------- /docs/tests/protocols/test_unreal2/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_unreal2: 2 | 3 | test_unreal2 4 | ============ 5 | 6 | .. toctree:: 7 | test_get_details 8 | test_get_players 9 | test_get_rules 10 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_unreal2/test_get_details.rst: -------------------------------------------------------------------------------- 1 | test_get_details 2 | ================ 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "server_id": 0, 10 | "server_ip": "", 11 | "game_port": 7777, 12 | "query_port": 0, 13 | "server_name": "[MiA] WARFARE | miasma.rocks", 14 | "map_name": "VCTF-FaceClassic-RezQ-MIA-Rev-5", 15 | "game_type": "ONSOnslaughtGame", 16 | "num_players": 14, 17 | "max_players": 28, 18 | "ping": 0, 19 | "flags": 0, 20 | "skill": "0" 21 | } 22 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_unreal2/test_get_players.rst: -------------------------------------------------------------------------------- 1 | test_get_players 2 | ================ 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | [ 9 | { 10 | "id": 32, 11 | "name": "Rounin_Napoli (IT)", 12 | "ping": 40, 13 | "score": 1, 14 | "stats_id": 1073741824 15 | }, 16 | { 17 | "id": 29, 18 | "name": "zath77 (FR)", 19 | "ping": 16, 20 | "score": 23, 21 | "stats_id": 1073741824 22 | }, 23 | { 24 | "id": 27, 25 | "name": "MrHuhuhu (UK)", 26 | "ping": 24, 27 | "score": 11, 28 | "stats_id": 1073741824 29 | }, 30 | { 31 | "id": 21, 32 | "name": "A_rabbit (NL)", 33 | "ping": 28, 34 | "score": 13, 35 | "stats_id": 1073741824 36 | }, 37 | { 38 | "id": 20, 39 | "name": "ABC (IT)", 40 | "ping": 56, 41 | "score": 13, 42 | "stats_id": 1073741824 43 | }, 44 | { 45 | "id": 19, 46 | "name": "gerbil (RO)", 47 | "ping": 48, 48 | "score": 24, 49 | "stats_id": 536870912 50 | }, 51 | { 52 | "id": 17, 53 | "name": "Plassy (UK)", 54 | "ping": 68, 55 | "score": 5, 56 | "stats_id": 1073741824 57 | }, 58 | { 59 | "id": 16, 60 | "name": "T-800 (PL)", 61 | "ping": 48, 62 | "score": 46, 63 | "stats_id": 536870912 64 | }, 65 | { 66 | "id": 15, 67 | "name": "Lung-2.0 (US)", 68 | "ping": 140, 69 | "score": 18, 70 | "stats_id": 536870912 71 | }, 72 | { 73 | "id": 14, 74 | "name": "Scofieldd (NL)", 75 | "ping": 24, 76 | "score": 0, 77 | "stats_id": 0 78 | }, 79 | { 80 | "id": 10, 81 | "name": "44 (US)", 82 | "ping": 120, 83 | "score": 0, 84 | "stats_id": 0 85 | }, 86 | { 87 | "id": 9, 88 | "name": "hulkjak (IL)", 89 | "ping": 68, 90 | "score": 0, 91 | "stats_id": 0 92 | }, 93 | { 94 | "id": 8, 95 | "name": "Plakatowy (PL)", 96 | "ping": 48, 97 | "score": 22, 98 | "stats_id": 1073741824 99 | }, 100 | { 101 | "id": 7, 102 | "name": "KFCx0 (ES)", 103 | "ping": 32, 104 | "score": 60, 105 | "stats_id": 536870912 106 | }, 107 | { 108 | "id": 6, 109 | "name": "spin (AT)", 110 | "ping": 36, 111 | "score": 5, 112 | "stats_id": 536870912 113 | }, 114 | { 115 | "id": 5, 116 | "name": "team-spec*Satin (UK)", 117 | "ping": 24, 118 | "score": 0, 119 | "stats_id": 536870912 120 | } 121 | ] 122 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_unreal2/test_get_rules.rst: -------------------------------------------------------------------------------- 1 | test_get_rules 2 | ============== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "Mutators": [ 10 | "DMMutator" 11 | ], 12 | "ServerMode": "dedicated", 13 | "AdminName": "Server Admins", 14 | "AdminEmail": "piglet@miasma.rocks", 15 | "ServerVersion": "3369", 16 | "GameStats": "True", 17 | "MaxSpectators": "16", 18 | "MapVoting": "true", 19 | "KickVoting": "false", 20 | "MinPlayers": "14", 21 | "EndTimeDelay": "4.00", 22 | "GoalScore": "5", 23 | "TimeLimit": "20", 24 | "Translocator": "False", 25 | "WeaponStay": "True", 26 | "ForceRespawn": "False", 27 | "BalanceTeams": "True", 28 | "PlayersBalanceTeams": "True", 29 | "FriendlyFireScale": "0%" 30 | } 31 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_ut3/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_ut3: 2 | 3 | test_ut3 4 | ======== 5 | 6 | .. toctree:: 7 | test_ut3_status -------------------------------------------------------------------------------- /docs/tests/protocols/test_vcmp/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_vcmp: 2 | 3 | test_vcmp 4 | ========= 5 | 6 | .. toctree:: 7 | test_get_players 8 | test_get_status 9 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_vcmp/test_get_players.rst: -------------------------------------------------------------------------------- 1 | test_get_players 2 | ================ 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | [ 9 | { 10 | "name": "Aitor_Tilla" 11 | }, 12 | { 13 | "name": "Elena_Nito" 14 | }, 15 | { 16 | "name": "Lola_Mento" 17 | }, 18 | { 19 | "name": "Dolores_Delano" 20 | }, 21 | { 22 | "name": "Elba_Ginon" 23 | }, 24 | { 25 | "name": "Elvis_Ko" 26 | }, 27 | { 28 | "name": "Helen_Chufe" 29 | }, 30 | { 31 | "name": "Paco_Gerte" 32 | }, 33 | { 34 | "name": "Tommy_Vercetti" 35 | }, 36 | { 37 | "name": "Jorge_Nitales" 38 | }, 39 | { 40 | "name": "Paul_Vazo" 41 | }, 42 | { 43 | "name": "Susana_Torio" 44 | }, 45 | { 46 | "name": "Rosa_Melano" 47 | }, 48 | { 49 | "name": "Larry_Capija" 50 | }, 51 | { 52 | "name": "Benito_Camelo" 53 | }, 54 | { 55 | "name": "Elton_Tito" 56 | } 57 | ] 58 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_vcmp/test_get_status.rst: -------------------------------------------------------------------------------- 1 | test_get_status 2 | =============== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "version": "04rel006", 10 | "password": 1, 11 | "num_players": 16, 12 | "max_players": 30, 13 | "server_name": "[0.4] Vice Paradise - Roleplay - OneVice Hosting", 14 | "game_type": "Roleplay EN/SPA", 15 | "language": "Vice City" 16 | } 17 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_warcraft3/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_warcraft3: 2 | 3 | test_warcraft3 4 | ============== 5 | 6 | .. toctree:: 7 | test_warcraft3_status 8 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_warcraft3/test_warcraft3_status.rst: -------------------------------------------------------------------------------- 1 | test_warcraft3_status 2 | ===================== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "game_version": "PX3W 26", 10 | "hostname": "Lokales Spiel (Banane)", 11 | "map_name": "Map name unavailable", 12 | "game_type": "Custom Game", 13 | "num_players": 1, 14 | "max_players": 2, 15 | "raw": { 16 | "product": "PX3W", 17 | "version": 26, 18 | "host_counter": 6, 19 | "entry_key": 52398541, 20 | "settings_raw": "0103490701015501a955010fc791334d8b6171735d47736f857b656f5569736f456f655d293329555b6973697367616d6b476d616565732f477733790143616f8b616f650101272d35cd8315819ba93f8be953214553e7c513a1bd9b4b", 21 | "game_flags": 9, 22 | "remaining_data": "" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_won/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_won: 2 | 3 | test_won 4 | ======== 5 | 6 | .. toctree:: 7 | test_get_info 8 | test_get_players 9 | test_get_rules 10 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_won/test_get_info.rst: -------------------------------------------------------------------------------- 1 | test_get_info 2 | ============= 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "protocol": 46, 10 | "name": "[Murka] NEW CS1.5 DeathMatch s4ke murka-terroristka.de", 11 | "map": "cs_assault", 12 | "folder": "cstrike", 13 | "game": "CounterStrike", 14 | "players": 6, 15 | "max_players": 32, 16 | "bots": 6, 17 | "server_type": 100, 18 | "environment": 108, 19 | "visibility": 0, 20 | "vac": 0, 21 | "address": "212.227.190.150:27020", 22 | "mod": 1, 23 | "link": "murka-terroristka.de", 24 | "download_link": "", 25 | "version": 1, 26 | "size": 184000000, 27 | "type": 0, 28 | "dll": 1 29 | } 30 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_won/test_get_players.rst: -------------------------------------------------------------------------------- 1 | test_get_players 2 | ================ 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | [ 9 | { 10 | "name": "[P*D]DaTa (21)", 11 | "score": 8, 12 | "duration": 845772.5625, 13 | "deaths": null, 14 | "money": null 15 | }, 16 | { 17 | "name": "[P0D]Stacy_Keech (41)", 18 | "score": 6, 19 | "duration": 845772.5625, 20 | "deaths": null, 21 | "money": null 22 | }, 23 | { 24 | "name": "[POD]Al_Pacino (61)", 25 | "score": 7, 26 | "duration": 845772.5625, 27 | "deaths": null, 28 | "money": null 29 | }, 30 | { 31 | "name": "[P*D]Bruce_Lee (21)", 32 | "score": 6, 33 | "duration": 845772.5625, 34 | "deaths": null, 35 | "money": null 36 | }, 37 | { 38 | "name": "[P0D]Adam_Sandler (41)", 39 | "score": 8, 40 | "duration": 845772.5625, 41 | "deaths": null, 42 | "money": null 43 | }, 44 | { 45 | "name": "[POD]Die Humans (61)", 46 | "score": 9, 47 | "duration": 845772.5625, 48 | "deaths": null, 49 | "money": null 50 | } 51 | ] 52 | -------------------------------------------------------------------------------- /docs/tests/protocols/test_won/test_get_rules.rst: -------------------------------------------------------------------------------- 1 | test_get_rules 2 | ============== 3 | 4 | Here are the results for the test method. 5 | 6 | .. code-block:: json 7 | 8 | { 9 | "allow_spectators": "0", 10 | "amx_client_languages": "0", 11 | "amx_nextmap": "cs_backalley", 12 | "amx_timeleft": "03:47", 13 | "amxmodx_version": "1.8.1.3746", 14 | "coop": "0", 15 | "csdmsake_version": "1.1e", 16 | "deathmatch": "1", 17 | "decalfrequency": "30", 18 | "max_queries_sec": "1", 19 | "max_queries_sec_global": "1", 20 | "max_queries_window": "1", 21 | "metamod_version": "1.19", 22 | "mp_allowmonsters": "0", 23 | "mp_autokick": "0", 24 | "mp_autoteambalance": "1", 25 | "mp_buytime": "0.50", 26 | "mp_c4timer": "45", 27 | "mp_chattime": "10", 28 | "mp_consistency": "1", 29 | "mp_fadetoblack": "0", 30 | "mp_footsteps": "1", 31 | "mp_forcecamera": "0", 32 | "mp_forcechasecam": "0", 33 | "mp_freezetime": "6", 34 | "mp_friendlyfire": "1", 35 | "mp_ghostfrequency": "0.1", 36 | "mp_hostagepenalty": "13", 37 | "mp_kickpercent": "0.66", 38 | "mp_limitteams": "1", 39 | "mp_logdetail": "0", 40 | "mp_logfile": "1", 41 | "mp_logmessages": "0", 42 | "mp_mapvoteratio": "0.67", 43 | "mp_maxrounds": "0", 44 | "mp_mirrordamage": "0", 45 | "mp_playerid": "0", 46 | "mp_roundtime": "3", 47 | "mp_startmoney": "800", 48 | "mp_timeleft": "0", 49 | "mp_timelimit": "15", 50 | "mp_tkpunish": "0", 51 | "mp_winlimit": "0", 52 | "nsv_list": "", 53 | "pb_aim_damper_coefficient_x": "0.22", 54 | "pb_aim_damper_coefficient_y": "0.22", 55 | "pb_aim_deviation_x": "2.0", 56 | "pb_aim_deviation_y": "1.0", 57 | "pb_aim_influence_x_on_y": "0.25", 58 | "pb_aim_influence_y_on_x": "0.17", 59 | "pb_aim_notarget_slowdown_ratio": "0.5", 60 | "pb_aim_offset_delay": "1.2", 61 | "pb_aim_spring_stiffness_x": "13.0", 62 | "pb_aim_spring_stiffness_y": "13.0", 63 | "pb_aim_target_anticipation_ratio": "2.2", 64 | "pb_aim_type": "4", 65 | "pb_autokill": "0", 66 | "pb_autokilldelay": "5", 67 | "pb_bot_join_team": "ANY", 68 | "pb_bot_quota_match": "0", 69 | "pb_chat": "0", 70 | "pb_dangerfactor": "800", 71 | "pb_detailnames": "1", 72 | "pb_ffa": "0", 73 | "pb_jasonmode": "0", 74 | "pb_latencybot": "1", 75 | "pb_mapstartbotdelay": "2", 76 | "pb_maxbots": "0", 77 | "pb_maxbotskill": "100", 78 | "pb_maxcamptime": "30", 79 | "pb_maxweaponpickup": "10", 80 | "pb_minbots": "0", 81 | "pb_minbotskill": "1", 82 | "pb_numfollowuser": "5", 83 | "pb_radio": "0", 84 | "pb_restrequipammo": "000000000", 85 | "pb_restrweapons": "00000000000000000000000000", 86 | "pb_shootthruwalls": "1", 87 | "pb_skin": "1", 88 | "pb_spray": "1", 89 | "pb_timer_grenade": "0.5", 90 | "pb_timer_pickup": "0.3", 91 | "pb_timer_sound": "0.5", 92 | "pb_usespeech": "0", 93 | "pb_version": "V3B20q", 94 | "pb_welcomemsgs": "0", 95 | "pb_wptfolder": "wptdefault", 96 | "sv_aim": "0", 97 | "sv_cheats": "0", 98 | "sv_contact": "www.murka-terroristka.de", 99 | "sv_friction": "4", 100 | "sv_godmodetime": "1.5", 101 | "sv_gravity": "800", 102 | "sv_logblocks": "1", 103 | "sv_maxrate": "0", 104 | "sv_maxspeed": "320", 105 | "sv_minrate": "0", 106 | "sv_password": "0", 107 | "sv_proxies": "0", 108 | "sv_restart": "0", 109 | "sv_restartround": "0", 110 | "sv_voiceenable": "1", 111 | "sv_weapons": "4194303" 112 | } 113 | -------------------------------------------------------------------------------- /docs/tests/rcon_protocols/index.rst: -------------------------------------------------------------------------------- 1 | .. _rcon_protocols_tests: 2 | 3 | Rcon Protocols Tests 4 | ==================== 5 | 6 | .. toctree:: 7 | test_source_rcon/index 8 | -------------------------------------------------------------------------------- /docs/tests/rcon_protocols/test_source_rcon/index.rst: -------------------------------------------------------------------------------- 1 | .. _test_source_rcon: 2 | 3 | test_source_rcon 4 | ================ 5 | 6 | .. toctree:: 7 | test_authenticate 8 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | Usage 2 | ===== 3 | 4 | Here's an example of how to query a server using the Source protocol: 5 | 6 | .. code-block:: python 7 | 8 | import asyncio 9 | from opengsq.protocols import Source 10 | 11 | async def main(): 12 | source = Source(host='45.147.5.5', port=27015) 13 | info = await source.get_info() 14 | print(info) 15 | 16 | asyncio.run(main()) 17 | 18 | You can also use the Source Remote Console: 19 | 20 | .. code-block:: python 21 | 22 | import asyncio 23 | from opengsq.exceptions import AuthenticationException 24 | from opengsq.rcon_protocols.source_rcon import SourceRcon 25 | 26 | async def main(): 27 | with SourceRcon("123.123.123.123", 27015) as source_rcon: 28 | try: 29 | await source_rcon.authenticate("serverRconPassword") 30 | except AuthenticationException: 31 | print('Failed to authenticate') 32 | 33 | response = await source_rcon.send_command("cvarlist") 34 | print(response) 35 | 36 | asyncio.run(main()) 37 | -------------------------------------------------------------------------------- /opengsq/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # flake8: noqa 3 | 4 | """ 5 | OpenGSQ Python Library 6 | ~~~~~~~~~~~~~~~~~~~~~~ 7 | 8 | Python library for querying game servers 9 | 10 | :copyright: (c) 2021 BattlefieldDuck 11 | :license: MIT, see LICENSE for more details. 12 | 13 | """ 14 | 15 | from opengsq.protocols import * 16 | -------------------------------------------------------------------------------- /opengsq/binary_reader.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | 4 | class BinaryReader: 5 | def __init__(self, data: bytes): 6 | self.__data = data 7 | self.stream_position = 0 8 | 9 | def remaining_bytes(self) -> int: 10 | return len(self.__data) - self.stream_position 11 | 12 | def is_end(self) -> bool: 13 | return self.stream_position >= len(self.__data) 14 | 15 | def prepend_bytes(self, data): 16 | self.__data = data + self.__data 17 | 18 | def read(self) -> bytes: 19 | return self.__data[self.stream_position:] 20 | 21 | def read_byte(self) -> int: 22 | data = self.__data[self.stream_position] 23 | self.stream_position += 1 24 | 25 | return data 26 | 27 | def read_bytes(self, count: int) -> bytes: 28 | data = self.__data[self.stream_position:self.stream_position + count] 29 | self.stream_position += count 30 | 31 | return data 32 | 33 | def read_short(self, unsigned=True) -> int: 34 | format = 'H' if unsigned else 'h' 35 | data = struct.unpack(f'<{format}', self.__data[self.stream_position:self.stream_position + 2])[0] 36 | self.stream_position += 2 37 | 38 | return data 39 | 40 | def read_long(self, unsigned=False) -> int: 41 | format = 'L' if unsigned else 'l' 42 | data = struct.unpack(f'<{format}', self.__data[self.stream_position:self.stream_position + 4])[0] 43 | self.stream_position += 4 44 | 45 | return data 46 | 47 | def read_long_long(self) -> int: 48 | data = struct.unpack(' float: 54 | data = struct.unpack(' str: 60 | bytes_string = b'' 61 | 62 | while self.remaining_bytes() > 0: 63 | stream_byte = bytes([self.read_byte()]) 64 | 65 | if stream_byte in delimiters: 66 | break 67 | 68 | bytes_string += stream_byte 69 | 70 | return str(bytes_string, encoding=encoding, errors=errors) 71 | 72 | def read_pascal_string(self, encoding='utf-8', errors='ignore'): 73 | length = self.read_byte() 74 | pascal_string = str(self.read_bytes(length - 1), encoding=encoding, errors=errors) 75 | return pascal_string 76 | 77 | -------------------------------------------------------------------------------- /opengsq/exceptions/__init__.py: -------------------------------------------------------------------------------- 1 | from .authentication_exception import AuthenticationException 2 | from .invalid_packet_exception import InvalidPacketException 3 | from .server_not_found_exception import ServerNotFoundException 4 | -------------------------------------------------------------------------------- /opengsq/exceptions/authentication_exception.py: -------------------------------------------------------------------------------- 1 | class AuthenticationException(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /opengsq/exceptions/invalid_packet_exception.py: -------------------------------------------------------------------------------- 1 | class InvalidPacketException(Exception): 2 | """Represents errors that occur during application execution when a packet is invalid.""" 3 | 4 | def __init__(self, message: str): 5 | """ 6 | Initializes a new instance of the InvalidPacketException class with a specified error message. 7 | 8 | Args: 9 | message (str): The message that describes the error. 10 | """ 11 | super().__init__(message) 12 | 13 | @staticmethod 14 | def throw_if_not_equal(received, expected): 15 | """ 16 | Checks if the received value is equal to the expected value. 17 | 18 | Args: 19 | received: The received value. 20 | expected: The expected value. 21 | 22 | Raises: 23 | InvalidPacketException: Thrown when the received value does not match the expected value. 24 | """ 25 | if isinstance(received, bytes) and isinstance(expected, bytes): 26 | if received != expected: 27 | raise InvalidPacketException( 28 | InvalidPacketException.get_message(received, expected) 29 | ) 30 | elif received != expected: 31 | raise InvalidPacketException( 32 | InvalidPacketException.get_message(received, expected) 33 | ) 34 | 35 | @staticmethod 36 | def get_message(received, expected): 37 | """ 38 | Returns a formatted error message. 39 | 40 | Args: 41 | received: The received value. 42 | expected: The expected value. 43 | 44 | Returns: 45 | str: The formatted error message. 46 | """ 47 | if isinstance(received, bytes) and isinstance(expected, bytes): 48 | received_str = " ".join(format(x, "02x") for x in received) 49 | expected_str = " ".join(format(x, "02x") for x in expected) 50 | else: 51 | received_str = str(received) 52 | expected_str = str(expected) 53 | 54 | return f"Packet header mismatch. Received: {received_str}. Expected: {expected_str}." 55 | -------------------------------------------------------------------------------- /opengsq/exceptions/server_not_found_exception.py: -------------------------------------------------------------------------------- 1 | class ServerNotFoundException(Exception): 2 | pass -------------------------------------------------------------------------------- /opengsq/protocol_base.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | 4 | class ProtocolBase(abc.ABC): 5 | @property 6 | @abc.abstractmethod 7 | def full_name(self) -> str: 8 | pass 9 | 10 | def __init__(self, host: str, port: int, timeout: float = 5.0): 11 | self._host = host 12 | self._port = port 13 | self._timeout = timeout 14 | self._allow_broadcast = False -------------------------------------------------------------------------------- /opengsq/protocols/__init__.py: -------------------------------------------------------------------------------- 1 | from opengsq.protocols.ase import ASE 2 | from opengsq.protocols.battlefield import Battlefield 3 | from opengsq.protocols.doom3 import Doom3 4 | from opengsq.protocols.eos import EOS 5 | from opengsq.protocols.fivem import FiveM 6 | from opengsq.protocols.flatout2 import Flatout2 7 | from opengsq.protocols.gamespy1 import GameSpy1 8 | from opengsq.protocols.gamespy2 import GameSpy2 9 | from opengsq.protocols.gamespy3 import GameSpy3 10 | from opengsq.protocols.gamespy4 import GameSpy4 11 | from opengsq.protocols.kaillera import Kaillera 12 | from opengsq.protocols.killingfloor import KillingFloor 13 | from opengsq.protocols.minecraft import Minecraft 14 | from opengsq.protocols.nadeo import Nadeo 15 | from opengsq.protocols.palworld import Palworld 16 | from opengsq.protocols.quake1 import Quake1 17 | from opengsq.protocols.quake2 import Quake2 18 | from opengsq.protocols.quake3 import Quake3 19 | from opengsq.protocols.raknet import RakNet 20 | from opengsq.protocols.renegadex import RenegadeX 21 | from opengsq.protocols.samp import Samp 22 | from opengsq.protocols.satisfactory import Satisfactory 23 | from opengsq.protocols.scum import Scum 24 | from opengsq.protocols.source import Source 25 | from opengsq.protocols.teamspeak3 import TeamSpeak3 26 | from opengsq.protocols.toxikk import Toxikk 27 | from opengsq.protocols.udk import UDK 28 | from opengsq.protocols.unreal2 import Unreal2 29 | from opengsq.protocols.ut3 import UT3 30 | from opengsq.protocols.vcmp import Vcmp 31 | from opengsq.protocols.warcraft3 import Warcraft3 32 | from opengsq.protocols.won import WON -------------------------------------------------------------------------------- /opengsq/protocols/ase.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from opengsq.binary_reader import BinaryReader 4 | from opengsq.exceptions import InvalidPacketException 5 | from opengsq.protocol_base import ProtocolBase 6 | from opengsq.protocol_socket import UdpClient 7 | from opengsq.responses.ase import Player, Status 8 | 9 | 10 | class ASE(ProtocolBase): 11 | """ 12 | This class represents the All-Seeing Eye Protocol. It provides methods to interact with the All-Seeing Eye API. 13 | """ 14 | 15 | full_name = "All-Seeing Eye Protocol" 16 | 17 | _request = b"s" 18 | _response = b"EYE1" 19 | 20 | async def get_status(self) -> Status: 21 | """ 22 | Asynchronously retrieves the status of the game server. 23 | 24 | This method sends a request to the server and parses the response to create a Status object. 25 | 26 | :return: The status of the game server. 27 | """ 28 | response = await UdpClient.communicate(self, self._request) 29 | 30 | br = BinaryReader(response) 31 | header = br.read_bytes(4) 32 | InvalidPacketException.throw_if_not_equal(header, self._response) 33 | 34 | return Status( 35 | game_name=br.read_pascal_string(), 36 | game_port=int(br.read_pascal_string()), 37 | hostname=br.read_pascal_string(), 38 | game_type=br.read_pascal_string(), 39 | map=br.read_pascal_string(), 40 | version=br.read_pascal_string(), 41 | password=br.read_pascal_string() != "0", 42 | num_players=int(br.read_pascal_string()), 43 | max_players=int(br.read_pascal_string()), 44 | rules=self.__parse_rules(br), 45 | players=self.__parse_players(br), 46 | ) 47 | 48 | def __parse_rules(self, br: BinaryReader) -> dict[str, str]: 49 | rules = {} 50 | 51 | while not br.is_end(): 52 | key = br.read_pascal_string() 53 | 54 | if not key: 55 | break 56 | 57 | rules[key] = br.read_pascal_string() 58 | 59 | return rules 60 | 61 | def __parse_players(self, br: BinaryReader) -> list[Player]: 62 | players: list[Player] = [] 63 | 64 | while not br.is_end(): 65 | flags = br.read_byte() 66 | player = {} 67 | 68 | if flags & 1 == 1: 69 | player["name"] = br.read_pascal_string() 70 | 71 | if flags & 2 == 2: 72 | player["team"] = br.read_pascal_string() 73 | 74 | if flags & 4 == 4: 75 | player["skin"] = br.read_pascal_string() 76 | 77 | if flags & 8 == 8: 78 | try: 79 | player["score"] = int(br.read_pascal_string()) 80 | except ValueError: 81 | player["score"] = 0 82 | 83 | if flags & 16 == 16: 84 | try: 85 | player["ping"] = int(br.read_pascal_string()) 86 | except ValueError: 87 | player["ping"] = 0 88 | 89 | if flags & 32 == 32: 90 | try: 91 | player["time"] = int(br.read_pascal_string()) 92 | except ValueError: 93 | player["time"] = 0 94 | 95 | players.append(Player(**player)) 96 | 97 | return players 98 | 99 | 100 | if __name__ == "__main__": 101 | import asyncio 102 | 103 | async def main_async(): 104 | # mtasa 105 | ase = ASE(host="79.137.97.3", port=22126, timeout=10.0) 106 | status = await ase.get_status() 107 | print(status) 108 | 109 | asyncio.run(main_async()) 110 | -------------------------------------------------------------------------------- /opengsq/protocols/fivem.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Any 4 | import aiohttp 5 | import time 6 | 7 | from opengsq.protocol_base import ProtocolBase 8 | 9 | 10 | class FiveM(ProtocolBase): 11 | """ 12 | This class represents the FiveM Protocol (https://docs.fivem.net/docs/server-manual/proxy-setup/). It provides methods to interact with the FiveM API. 13 | """ 14 | 15 | full_name = "FiveM Protocol" 16 | 17 | async def _get(self, filename: str) -> dict[str, Any]: 18 | """ 19 | Asynchronously retrieves the JSON data from the given filename on the server. 20 | 21 | :param filename: The filename to retrieve data from. 22 | :return: A dictionary containing the JSON data. 23 | """ 24 | url = f"http://{self._host}:{self._port}/{filename}.json?v={int(time.time())}" 25 | 26 | async with aiohttp.ClientSession() as session: 27 | async with session.get(url) as response: 28 | return await response.json(content_type=None) 29 | 30 | async def get_info(self) -> dict[str, Any]: 31 | """ 32 | Asynchronously retrieves the information of the game server. 33 | 34 | :return: A dictionary containing the information of the game server. 35 | """ 36 | return await self._get("info") 37 | 38 | async def get_players(self) -> list[dict[str, Any]]: 39 | """ 40 | Asynchronously retrieves the list of players on the game server. 41 | 42 | :return: A list of players on the game server. 43 | """ 44 | return await self._get("players") 45 | 46 | async def get_dynamic(self) -> dict[str, Any]: 47 | """ 48 | Asynchronously retrieves the dynamic data of the game server. 49 | 50 | :return: A dictionary containing the dynamic data of the game server. 51 | """ 52 | return await self._get("dynamic") 53 | 54 | 55 | if __name__ == "__main__": 56 | import asyncio 57 | 58 | async def main_async(): 59 | fivem = FiveM(host="144.217.10.12", port=30120, timeout=5.0) 60 | info = await fivem.get_info() 61 | print(info) 62 | players = await fivem.get_players() 63 | print(players) 64 | dynamic = await fivem.get_dynamic() 65 | print(dynamic) 66 | 67 | asyncio.run(main_async()) 68 | -------------------------------------------------------------------------------- /opengsq/protocols/gamespy4.py: -------------------------------------------------------------------------------- 1 | from opengsq.protocols import GameSpy3 2 | 3 | 4 | class GameSpy4(GameSpy3): 5 | """ 6 | This class represents the GameSpy Protocol version 4. It inherits from the GameSpy3 class and overrides some of its properties. 7 | """ 8 | 9 | full_name = "GameSpy Protocol version 4" 10 | challenge_required = True 11 | 12 | 13 | if __name__ == "__main__": 14 | import asyncio 15 | 16 | async def main_async(): 17 | gs4 = GameSpy4(host="play.avengetech.me", port=19132, timeout=5.0) 18 | server = await gs4.get_status() 19 | print(server) 20 | 21 | asyncio.run(main_async()) 22 | -------------------------------------------------------------------------------- /opengsq/protocols/kaillera.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import aiohttp 4 | 5 | from opengsq.responses.kaillera import Status 6 | from opengsq.protocol_base import ProtocolBase 7 | from opengsq.protocol_socket import UdpClient 8 | 9 | 10 | class Kaillera(ProtocolBase): 11 | """ 12 | A class used to represent the Kaillera Network Protocol. 13 | """ 14 | 15 | full_name = "Kaillera Network Protocol" 16 | 17 | async def get_status(self) -> bool: 18 | """ 19 | Checks the status of the server by sending a PING message and expecting a PONG response. 20 | 21 | Returns: 22 | bool: True if the server responds with PONG, False otherwise. 23 | """ 24 | response = await UdpClient.communicate(self, b"PING\0") 25 | return response == b"PONG\x00" 26 | 27 | @staticmethod 28 | async def query_master_servers() -> list[Status]: 29 | """ 30 | Queries the master servers for a list of all active servers. 31 | 32 | Returns: 33 | list[Status]: A list of Status objects representing each server. 34 | """ 35 | url = "http://www.kaillera.com/raw_server_list2.php" 36 | 37 | async with aiohttp.ClientSession() as session: 38 | async with session.get(url) as response: 39 | data = await response.text() 40 | 41 | # Format: serverName[LF]ipAddress:port;users/maxusers;gameCount;version;location[LF] 42 | servers = data.strip().split("\n") 43 | master_servers = [] 44 | 45 | for i in range(0, len(servers), 2): 46 | server_name, info = servers[i : i + 2] 47 | items = info.split(";") 48 | ip_address, port = items[0].split(":") 49 | users, maxusers = items[1].split("/") 50 | game_count, version, location = items[2], items[3], items[4] 51 | 52 | master_servers.append( 53 | Status( 54 | server_name, 55 | ip_address, 56 | int(port), 57 | int(users), 58 | int(maxusers), 59 | int(game_count), 60 | version, 61 | location, 62 | ) 63 | ) 64 | 65 | return master_servers 66 | 67 | 68 | if __name__ == "__main__": 69 | import asyncio 70 | 71 | async def main_async(): 72 | master_servers = await Kaillera.query_master_servers() 73 | print(master_servers) 74 | 75 | kaillera = Kaillera(host="112.161.44.113", port=27888, timeout=5.0) 76 | status = await kaillera.get_status() 77 | print(status) 78 | 79 | asyncio.run(main_async()) 80 | -------------------------------------------------------------------------------- /opengsq/protocols/killingfloor.py: -------------------------------------------------------------------------------- 1 | from opengsq.responses.killingfloor import Status 2 | from opengsq.binary_reader import BinaryReader 3 | from opengsq.exceptions import InvalidPacketException 4 | from opengsq.protocol_socket import UdpClient 5 | from opengsq.protocols.unreal2 import Unreal2 6 | 7 | 8 | class KillingFloor(Unreal2): 9 | """ 10 | This class represents the Killing Floor Protocol. It provides methods to interact with the Killing Floor API. 11 | """ 12 | 13 | full_name = "Killing Floor Protocol" 14 | 15 | async def get_details(self, strip_color=True) -> Status: 16 | """ 17 | Asynchronously retrieves the details of the game server. 18 | 19 | Args: 20 | strip_color (bool, optional): If True, strips color codes from the server name. Defaults to True. 21 | 22 | :return: A Status object containing the details of the game server. 23 | """ 24 | response = await UdpClient.communicate( 25 | self, b"\x79\x00\x00\x00" + bytes([self._DETAILS]) 26 | ) 27 | 28 | # Remove the first 4 bytes \x80\x00\x00\x00 29 | br = BinaryReader(response[4:]) 30 | header = br.read_byte() 31 | 32 | if header != self._DETAILS: 33 | raise InvalidPacketException( 34 | "Packet header mismatch. Received: {}. Expected: {}.".format( 35 | chr(header), chr(self._DETAILS) 36 | ) 37 | ) 38 | 39 | return Status( 40 | server_id=br.read_long(), 41 | server_ip=br.read_string(), 42 | game_port=br.read_long(), 43 | query_port=br.read_long(), 44 | server_name=self._read_string(br, strip_color, False), 45 | map_name=self._read_string(br, strip_color), 46 | game_type=self._read_string(br, strip_color), 47 | num_players=br.read_long(), 48 | max_players=br.read_long(), 49 | wave_current=br.read_long(), 50 | wave_total=br.read_long(), 51 | ping=br.read_long(), 52 | flags=br.read_long(), 53 | skill=self._read_string(br, strip_color), 54 | ) 55 | 56 | 57 | if __name__ == "__main__": 58 | import asyncio 59 | 60 | async def main_async(): 61 | # killingfloor 62 | killingFloor = KillingFloor(host="185.80.128.168", port=7708, timeout=10.0) 63 | # killingFloor = KillingFloor(host="45.235.99.76", port=7710, timeout=10.0) 64 | details = await killingFloor.get_details() 65 | print(details) 66 | # rules = await killingFloor.get_rules() 67 | # print(rules) 68 | # players = await killingFloor.get_players() 69 | # print(players) 70 | 71 | asyncio.run(main_async()) 72 | -------------------------------------------------------------------------------- /opengsq/protocols/nadeo.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | import struct 5 | import xmlrpc.client as xmlrpclib 6 | from typing import Any, Optional 7 | 8 | from opengsq.exceptions import InvalidPacketException 9 | from opengsq.protocol_base import ProtocolBase 10 | from opengsq.responses.nadeo import Status 11 | 12 | 13 | class Nadeo(ProtocolBase): 14 | full_name = "Nadeo GBXRemote Protocol" 15 | INITIAL_HANDLER = 0x80000000 16 | MAXIMUM_HANDLER = 0xFFFFFFFF 17 | 18 | def __init__(self, host: str, port: int = 5000, timeout: float = 5.0): 19 | super().__init__(host, port, timeout) 20 | self.handler = self.MAXIMUM_HANDLER 21 | self._reader: Optional[asyncio.StreamReader] = None 22 | self._writer: Optional[asyncio.StreamWriter] = None 23 | 24 | async def connect(self) -> None: 25 | self._reader, self._writer = await asyncio.open_connection(self._host, self._port) 26 | 27 | # Read and validate header 28 | data = await self._reader.read(4) 29 | header_length = struct.unpack(' None: 38 | if self._writer: 39 | self._writer.close() 40 | await self._writer.wait_closed() 41 | 42 | async def __aenter__(self): 43 | await self.connect() 44 | return self 45 | 46 | async def __aexit__(self, exc_type, exc_value, traceback): 47 | await self.close() 48 | 49 | async def _execute(self, method: str, *args) -> Any: 50 | if self.handler == self.MAXIMUM_HANDLER: 51 | self.handler = self.INITIAL_HANDLER 52 | else: 53 | self.handler += 1 54 | 55 | handler_bytes = self.handler.to_bytes(4, byteorder='little') 56 | data = xmlrpclib.dumps(args, method).encode() 57 | packet_len = len(data) 58 | 59 | packet = packet_len.to_bytes(4, byteorder='little') + handler_bytes + data 60 | 61 | self._writer.write(packet) 62 | await self._writer.drain() 63 | 64 | # Read response 65 | header = await self._reader.read(8) 66 | size = struct.unpack(' bool: 81 | await self.connect() 82 | result = await self._execute('Authenticate', username, password) 83 | return bool(result) 84 | 85 | async def get_status(self) -> Status: 86 | version = await self._execute('GetVersion') 87 | server_info = await self._execute('GetServerOptions') 88 | player_list = await self._execute('GetPlayerList', 100, 0) 89 | current_map = await self._execute('GetCurrentChallengeInfo') 90 | 91 | return Status.from_raw_data( 92 | version_data=version, 93 | server_data=server_info, 94 | players_data=player_list, 95 | map_data=current_map 96 | ) -------------------------------------------------------------------------------- /opengsq/protocols/palworld.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import time 3 | import aiohttp 4 | 5 | from opengsq.responses.palworld import Player, Status 6 | from opengsq.protocol_base import ProtocolBase 7 | 8 | 9 | class Palworld(ProtocolBase): 10 | """ 11 | This class represents the Palworld Protocol. It provides methods to interact with the Palworld REST API. 12 | """ 13 | 14 | full_name = "Palworld Protocol" 15 | 16 | def __init__(self, host: str, port: int, api_username: str, api_password: str, timeout: float = 5): 17 | """ 18 | Initializes the Palworld object with the given parameters. 19 | 20 | :param host: The host of the server. 21 | :param port: The port of the server. 22 | :param api_username: The API username. 23 | :param api_password: The API password. 24 | :param timeout: The timeout for the server connection. 25 | """ 26 | 27 | super().__init__(host, port, timeout) 28 | 29 | if api_username is None: 30 | raise ValueError("api_username must not be None") 31 | if api_password is None: 32 | raise ValueError("api_password must not be None") 33 | 34 | self.api_url = f"http://{self._host}:{self._port}/v1/api" 35 | self.api_username = api_username 36 | self.api_password = api_password 37 | 38 | async def api_request(self,url): 39 | """ 40 | Asynchronously retrieves data from the game server through the REST API. 41 | """ 42 | auth = aiohttp.BasicAuth(self.api_username,self.api_password) 43 | async with aiohttp.ClientSession(auth=auth) as session: 44 | async with session.get(url) as response: 45 | data = await response.json() 46 | return data 47 | 48 | async def get_status(self) -> Status: 49 | """ 50 | Retrieves the status of the game server. The status includes the server state, name, player count and max player count. 51 | """ 52 | info_data = await self.api_request(f"{self.api_url}/info") 53 | metrics_data = await self.api_request(f"{self.api_url}/metrics") 54 | 55 | server_name = info_data["servername"] 56 | server_cur_players = metrics_data["currentplayernum"] 57 | server_max_players = metrics_data["maxplayernum"] 58 | 59 | return Status( 60 | server_name=server_name, 61 | num_players=server_cur_players, 62 | max_players=server_max_players, 63 | ) 64 | 65 | async def get_players(self) -> list[Player]: 66 | players = [] 67 | player_data = await self.api_request(f"{self.api_url}/players") 68 | for obj in player_data["players"]: 69 | players.append( 70 | Player( 71 | name=obj["name"], 72 | ping=obj["ping"], 73 | level=obj["level"], 74 | ) 75 | ) 76 | return players 77 | 78 | if __name__ == "__main__": 79 | import asyncio 80 | 81 | async def main_async(): 82 | palworld = Palworld( 83 | host="79.136.0.124", 84 | port=8212, 85 | timeout=5.0, 86 | api_username="admin", 87 | api_password="", 88 | ) 89 | status = await palworld.get_status() 90 | print(status) 91 | players = await palworld.get_players() 92 | print(players) 93 | 94 | asyncio.run(main_async()) 95 | -------------------------------------------------------------------------------- /opengsq/protocols/quake2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import re 4 | 5 | from opengsq.responses.quake2 import Player, Status 6 | from opengsq.binary_reader import BinaryReader 7 | from opengsq.protocols.quake1 import Quake1 8 | 9 | 10 | class Quake2(Quake1): 11 | """ 12 | This class represents the Quake2 Protocol. It provides methods to interact with the Quake2 API. 13 | """ 14 | 15 | full_name = "Quake2 Protocol" 16 | 17 | def __init__(self, host: str, port: int, timeout: float = 5.0): 18 | """ 19 | Initializes the Quake2 Query Protocol. 20 | 21 | :param host: The host of the game server. 22 | :param port: The port of the game server. 23 | :param timeout: The timeout for the connection. Defaults to 5.0. 24 | """ 25 | super().__init__(host, port, timeout) 26 | self._response_header = "print\n" 27 | 28 | async def get_status(self) -> Status: 29 | """ 30 | Asynchronously retrieves the status of the game server. 31 | 32 | :return: A Status object containing the status of the game server. 33 | """ 34 | br = await self._get_response_binary_reader() 35 | return Status(info=self._parse_info(br), players=self._parse_players(br)) 36 | 37 | def _parse_players(self, br: BinaryReader): 38 | """ 39 | Parses the players from the given BinaryReader object. 40 | 41 | :param br: The BinaryReader object to parse the players from. 42 | :return: A list containing the players. 43 | """ 44 | players = [] 45 | 46 | for matches in self._get_player_match_collections(br): 47 | matches: list[re.Match] = [match.group() for match in matches] 48 | 49 | player = Player( 50 | frags=int(matches[0]), 51 | ping=int(matches[1]), 52 | name=str(matches[2]).strip('"') if len(matches) > 2 else "", 53 | address=str(matches[3]).strip('"') if len(matches) > 3 else "", 54 | ) 55 | 56 | players.append(player) 57 | 58 | return players 59 | 60 | 61 | if __name__ == "__main__": 62 | import asyncio 63 | 64 | async def main_async(): 65 | quake2 = Quake2(host="46.165.236.118", port=27910, timeout=5.0) 66 | status = await quake2.get_status() 67 | print(status) 68 | 69 | asyncio.run(main_async()) 70 | -------------------------------------------------------------------------------- /opengsq/protocols/quake3.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import re 4 | 5 | from opengsq.responses.quake2 import Status 6 | from opengsq.binary_reader import BinaryReader 7 | from opengsq.protocols.quake2 import Quake2 8 | from opengsq.exceptions import InvalidPacketException 9 | 10 | 11 | class Quake3(Quake2): 12 | """ 13 | This class represents the Quake3 Protocol. It provides methods to interact with the Quake3 API. 14 | """ 15 | 16 | full_name = "Quake3 Protocol" 17 | 18 | def __init__(self, host: str, port: int, timeout: float = 5.0): 19 | """ 20 | Initializes the Quake3 object with the given parameters. 21 | 22 | :param host: The host of the server. 23 | :param port: The port of the server. 24 | :param timeout: The timeout for the server connection. 25 | """ 26 | super().__init__(host, port, timeout) 27 | self._request_header = b"getstatus" 28 | self._response_header = "statusResponse\n" 29 | 30 | async def get_info(self, strip_color=True) -> dict[str, str]: 31 | """ 32 | Asynchronously retrieves the information of the game server. 33 | 34 | :param strip_color: A boolean indicating whether to strip color codes from the information. 35 | :return: A dictionary containing the information of the game server. 36 | """ 37 | response_data = await self._connect_and_send(b"getinfo opengsq") 38 | 39 | br = BinaryReader(response_data) 40 | header = br.read_string(self._delimiter1) 41 | response_header = "infoResponse\n" 42 | 43 | if header != response_header: 44 | raise InvalidPacketException( 45 | f"Packet header mismatch. Received: {header}. Expected: {response_header}." 46 | ) 47 | 48 | info = self._parse_info(br) 49 | 50 | if not strip_color: 51 | return info 52 | 53 | if "hostname" in info: 54 | info["hostname"] = Quake3.strip_colors(info["hostname"]) 55 | 56 | return info 57 | 58 | async def get_status(self, strip_color=True) -> Status: 59 | br = await self._get_response_binary_reader() 60 | 61 | status = Status(info=self._parse_info(br), players=self._parse_players(br)) 62 | 63 | if not strip_color: 64 | return status 65 | 66 | if "sv_hostname" in status.info: 67 | status.info["sv_hostname"] = Quake3.strip_colors(status.info["sv_hostname"]) 68 | 69 | for player in status.players: 70 | if player.name: 71 | player.name = Quake3.strip_colors(player.name) 72 | 73 | return status 74 | 75 | @staticmethod 76 | def strip_colors(text: str) -> str: 77 | """ 78 | Strips color codes from the given text. 79 | 80 | :param text: The text to strip color codes from. 81 | :return: The text with color codes stripped. 82 | """ 83 | return re.compile("\\^(X.{6}|.)").sub("", text) 84 | 85 | 86 | if __name__ == "__main__": 87 | import asyncio 88 | 89 | async def main_async(): 90 | quake3 = Quake3(host="108.61.18.110", port=27960, timeout=5.0) 91 | info = await quake3.get_info() 92 | print(info) 93 | status = await quake3.get_status() 94 | print(status) 95 | 96 | asyncio.run(main_async()) 97 | -------------------------------------------------------------------------------- /opengsq/protocols/raknet.py: -------------------------------------------------------------------------------- 1 | from opengsq.responses.raknet import Status 2 | from opengsq.binary_reader import BinaryReader 3 | from opengsq.exceptions import InvalidPacketException 4 | from opengsq.protocol_base import ProtocolBase 5 | from opengsq.protocol_socket import UdpClient 6 | 7 | 8 | class RakNet(ProtocolBase): 9 | """ 10 | This class represents the RakNet Protocol. It provides methods to interact with the RakNet API. 11 | (https://wiki.vg/Raknet_Protocol) 12 | """ 13 | 14 | full_name = "RakNet Protocol" 15 | 16 | __ID_UNCONNECTED_PING = b"\x01" 17 | __ID_UNCONNECTED_PONG = b"\x1C" 18 | __TIMESTAMP = b"\x12\x23\x34\x45\x56\x67\x78\x89" 19 | __OFFLINE_MESSAGE_DATA_ID = ( 20 | b"\x00\xFF\xFF\x00\xFE\xFE\xFE\xFE\xFD\xFD\xFD\xFD\x12\x34\x56\x78" 21 | ) 22 | __CLIENT_GUID = b"\x00\x00\x00\x00\x00\x00\x00\x00" 23 | 24 | async def get_status(self) -> Status: 25 | """ 26 | Asynchronously retrieves the status of the game server. 27 | 28 | :return: A Status object containing the status of the game server. 29 | """ 30 | request = ( 31 | self.__ID_UNCONNECTED_PING 32 | + self.__TIMESTAMP 33 | + self.__OFFLINE_MESSAGE_DATA_ID 34 | + self.__CLIENT_GUID 35 | ) 36 | response = await UdpClient.communicate(self, request) 37 | 38 | br = BinaryReader(response) 39 | header = br.read_bytes(1) 40 | 41 | if header != self.__ID_UNCONNECTED_PONG: 42 | raise InvalidPacketException( 43 | "Packet header mismatch. Received: {}. Expected: {}.".format( 44 | header, self.__ID_UNCONNECTED_PONG 45 | ) 46 | ) 47 | 48 | br.read_bytes( 49 | len(self.__TIMESTAMP) + len(self.__CLIENT_GUID) 50 | ) # skip timestamp and guid 51 | magic = br.read_bytes(len(self.__OFFLINE_MESSAGE_DATA_ID)) 52 | 53 | if magic != self.__OFFLINE_MESSAGE_DATA_ID: 54 | raise InvalidPacketException( 55 | "Magic value mismatch. Received: {}. Expected: {}.".format( 56 | magic, self.__OFFLINE_MESSAGE_DATA_ID 57 | ) 58 | ) 59 | 60 | br.read_short() # skip remaining packet length 61 | 62 | d = [b";"] # delimiter 63 | 64 | return Status( 65 | edition=br.read_string(d), 66 | motd_line1=br.read_string(d), 67 | protocol_version=int(br.read_string(d)), 68 | version_name=br.read_string(d), 69 | num_players=int(br.read_string(d)), 70 | max_players=int(br.read_string(d)), 71 | server_unique_id=br.read_string(d), 72 | motd_line2=br.read_string(d), 73 | game_mode=br.read_string(d), 74 | game_mode_numeric=int(br.read_string(d)), 75 | port_ipv4=int(br.read_string(d)), 76 | port_ipv6=int(br.read_string(d)), 77 | ) 78 | 79 | 80 | if __name__ == "__main__": 81 | import asyncio 82 | 83 | async def main_async(): 84 | raknet = RakNet(host="mc.advancius.net", port=19132, timeout=5.0) 85 | status = await raknet.get_status() 86 | print(status) 87 | 88 | asyncio.run(main_async()) 89 | -------------------------------------------------------------------------------- /opengsq/protocols/renegadex.py: -------------------------------------------------------------------------------- 1 | import json 2 | import asyncio 3 | from opengsq.protocol_base import ProtocolBase 4 | from opengsq.responses.renegadex import Status 5 | 6 | class RenegadeX(ProtocolBase): 7 | full_name = "Renegade X Protocol" 8 | BROADCAST_PORT = 45542 9 | 10 | def __init__(self, host: str, port: int = 7777, timeout: float = 5.0): 11 | super().__init__(host, port, timeout) 12 | 13 | async def get_status(self) -> Status: 14 | loop = asyncio.get_running_loop() 15 | queue = asyncio.Queue() 16 | 17 | class BroadcastProtocol(asyncio.DatagramProtocol): 18 | def __init__(self, queue, host): 19 | self.queue = queue 20 | self.target_host = host 21 | 22 | def datagram_received(self, data, addr): 23 | if addr[0] == self.target_host: 24 | self.queue.put_nowait(data) 25 | 26 | transport, _ = await loop.create_datagram_endpoint( 27 | lambda: BroadcastProtocol(queue, self._host), 28 | local_addr=('0.0.0.0', self.BROADCAST_PORT) 29 | ) 30 | 31 | try: 32 | complete_data = bytearray() 33 | while True: 34 | try: 35 | data = await asyncio.wait_for(queue.get(), timeout=self._timeout) 36 | complete_data.extend(data) 37 | 38 | try: 39 | json_str = complete_data.decode('utf-8') 40 | server_info = json.loads(json_str) 41 | return Status.from_dict(server_info) 42 | except (UnicodeDecodeError, json.JSONDecodeError): 43 | continue 44 | 45 | except asyncio.TimeoutError: 46 | raise TimeoutError("No broadcast received from the specified server") 47 | 48 | finally: 49 | transport.close() -------------------------------------------------------------------------------- /opengsq/protocols/toxikk.py: -------------------------------------------------------------------------------- 1 | from opengsq.protocols.udk import UDK 2 | from opengsq.responses.toxikk.status import Status 3 | 4 | class Toxikk(UDK): 5 | GAMEMODE_NAMES = { 6 | "cruzade.CRZBloodLust": "BloodLust", 7 | "cruzade.CRZTeamGame": "Squad Assault", 8 | "cruzade.CRZSquadSurvival": "Squad Survival", 9 | "cruzade.CRZCellCapture": "Cell Capture", 10 | "cruzade.CRZAreaDomination": "Area Domination", 11 | "cruzade.CRZArchRivals": "Arch Rivals" 12 | } 13 | 14 | BOT_SKILL_NAMES = { 15 | 0: "Novice", 16 | 1: "Average", 17 | 2: "Experienced", 18 | 3: "Skilled", 19 | 4: "Adept", 20 | 5: "Masterful", 21 | 6: "Inhuman", 22 | 7: "Godlike" 23 | } 24 | 25 | VS_BOTS_NAMES = { 26 | 0: "None", 27 | 1: "1:1", 28 | 2: "3:2", 29 | 3: "2:1" 30 | } 31 | 32 | full_name = "Toxikk Protocol" 33 | 34 | def __init__(self, host: str, port: int = 14001, timeout: float = 5.0): 35 | super().__init__(host, port, timeout) 36 | self.game_id = 0x4D5707DB 37 | self.packet_version = 7 38 | 39 | def _parse_response(self, buffer: bytes) -> dict: 40 | base_response = super()._parse_response(buffer) 41 | toxikk_properties = {} 42 | 43 | for prop in base_response['raw']['settings_properties']: 44 | prop_id = prop['id'] 45 | if prop_id == 1073741825: # Map 46 | base_response['map'] = prop['data'] 47 | toxikk_properties['map'] = prop['data'] 48 | elif prop_id == 1073741826: # Game Type 49 | base_response['game_type'] = prop['data'] 50 | toxikk_properties['gametype'] = self.GAMEMODE_NAMES.get(prop['data'], prop['data']) 51 | elif prop_id == 268435704: # Frag Limit 52 | toxikk_properties['frag_limit'] = prop['data'] 53 | elif prop_id == 268435705: # Time Limit 54 | toxikk_properties['time_limit'] = prop['data'] 55 | elif prop_id == 268435703: # Number of Bots 56 | toxikk_properties['numbots'] = prop['data'] 57 | elif prop_id == 1073741828: # Mutators 58 | toxikk_properties['mutators'] = self._parse_mutators(prop['data']) 59 | 60 | for setting in base_response['raw']['localized_settings']: 61 | setting_id = setting['id'] 62 | value_index = setting['value_index'] 63 | 64 | if setting_id == 0: 65 | toxikk_properties['bot_skill'] = self.BOT_SKILL_NAMES.get(value_index) 66 | elif setting_id == 6: 67 | toxikk_properties['pure_server'] = value_index 68 | elif setting_id == 7: 69 | base_response['password_protected'] = value_index == 1 70 | toxikk_properties['password'] = value_index 71 | elif setting_id == 8: 72 | toxikk_properties['vs_bots'] = self.VS_BOTS_NAMES.get(value_index) 73 | elif setting_id == 10: 74 | toxikk_properties['force_respawn'] = value_index 75 | 76 | base_response['raw'].update(toxikk_properties) 77 | return base_response 78 | 79 | def _parse_mutators(self, mutator_value: any) -> list: 80 | if not mutator_value or not isinstance(mutator_value, str): 81 | return [] 82 | return [m.title() for m in mutator_value.split('\x1c') if m] -------------------------------------------------------------------------------- /opengsq/protocols/vcmp.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import struct 4 | from opengsq.responses.vcmp import Player, Status 5 | 6 | from opengsq.binary_reader import BinaryReader 7 | from opengsq.exceptions import InvalidPacketException 8 | from opengsq.protocol_base import ProtocolBase 9 | from opengsq.protocol_socket import Socket, UdpClient 10 | 11 | 12 | class Vcmp(ProtocolBase): 13 | """ 14 | This class represents the Vice City Multiplayer Protocol. It provides methods to interact with the Vice City Multiplayer API. 15 | """ 16 | 17 | full_name = "Vice City Multiplayer Protocol" 18 | 19 | _request_header = b"VCMP" 20 | _response_header = b"MP04" 21 | 22 | async def get_status(self) -> Status: 23 | """ 24 | Asynchronously retrieves the status of the game server. 25 | 26 | :return: A Status object containing the status of the game server. 27 | """ 28 | response = await self.__send_and_receive(b"i") 29 | 30 | br = BinaryReader(response) 31 | 32 | return Status( 33 | version=str( 34 | br.read_bytes(12).strip(b"\x00"), encoding="utf-8", errors="ignore" 35 | ), 36 | password=br.read_byte(), 37 | num_players=br.read_short(), 38 | max_players=br.read_short(), 39 | server_name=self.__read_string(br, 4), 40 | game_type=self.__read_string(br, 4), 41 | language=self.__read_string(br, 4), 42 | ) 43 | 44 | async def get_players(self) -> list[Player]: 45 | """ 46 | Asynchronously retrieves the list of players on the game server. 47 | 48 | :return: A list of Player objects representing the players on the game server. 49 | """ 50 | """Server may not response when numplayers > 100""" 51 | response = await self.__send_and_receive(b"c") 52 | 53 | br = BinaryReader(response) 54 | numplayers = br.read_short() 55 | players = [Player(self.__read_string(br)) for _ in range(numplayers)] 56 | 57 | return players 58 | 59 | async def __send_and_receive(self, data: bytes): 60 | """ 61 | Asynchronously sends the given data to the game server and receives the response. 62 | 63 | :param data: The data to send to the game server. 64 | :return: The response from the game server. 65 | """ 66 | # Format the address 67 | host = await Socket.gethostbyname(self._host) 68 | packet_header = ( 69 | struct.pack("BBBBH", *map(int, host.split(".") + [self._port])) + data 70 | ) 71 | request = self._request_header + packet_header 72 | 73 | # Validate the response 74 | response = await UdpClient.communicate(self, request) 75 | header = response[: len(self._response_header)] 76 | 77 | if header != self._response_header: 78 | raise InvalidPacketException( 79 | f"Packet header mismatch. Received: {header}. Expected: {self._response_header}." 80 | ) 81 | 82 | return response[len(self._response_header) + len(packet_header) :] 83 | 84 | def __read_string(self, br: BinaryReader, read_offset=1): 85 | """ 86 | Reads a string from the given BinaryReader object. 87 | 88 | :param br: The BinaryReader object to read the string from. 89 | :param read_offset: The offset to start reading from. 90 | :return: The string read from the BinaryReader object. 91 | """ 92 | length = br.read_byte() if read_offset == 1 else br.read_long() 93 | return str(br.read_bytes(length), encoding="utf-8", errors="ignore") 94 | 95 | 96 | if __name__ == "__main__": 97 | import asyncio 98 | 99 | async def main_async(): 100 | vcmp = Vcmp(host="51.178.65.136", port=8114, timeout=5.0) 101 | status = await vcmp.get_status() 102 | print(status) 103 | players = await vcmp.get_players() 104 | print(players) 105 | 106 | asyncio.run(main_async()) 107 | -------------------------------------------------------------------------------- /opengsq/protocols/warfork.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import re 4 | 5 | from opengsq.responses.warfork import Player 6 | from opengsq.responses.quake2 import Status 7 | from opengsq.binary_reader import BinaryReader 8 | from opengsq.protocols.quake3 import Quake3 9 | 10 | 11 | class Warfork(Quake3): 12 | """ 13 | This class represents the Quake3 Protocol for Warfork. It provides methods to interact with the Warfork API. 14 | """ 15 | 16 | full_name = "Warfork Protocol" 17 | 18 | def __init__(self, host: str, port: int, timeout: float = 5.0): 19 | """ 20 | Initializes the Quake3 object with the given parameters. 21 | 22 | :param host: The host of the server. 23 | :param port: The port of the server. 24 | :param timeout: The timeout for the server connection. 25 | """ 26 | super().__init__(host, port, timeout) 27 | 28 | async def get_status(self, strip_color=True) -> Status: 29 | br = await self._get_response_binary_reader() 30 | 31 | status = Status(info=self._parse_info(br), players=self._parse_players(br)) 32 | if not strip_color: 33 | return status 34 | 35 | if "sv_hostname" in status.info: 36 | status.info["sv_hostname"] = Quake3.strip_colors(status.info["sv_hostname"]) 37 | 38 | for player in status.players: 39 | if player.name: 40 | player.name = Quake3.strip_colors(player.name) 41 | 42 | return status 43 | 44 | def _parse_players(self, br: BinaryReader): 45 | """ 46 | Parses the players from the given BinaryReader object. 47 | 48 | :param br: The BinaryReader object to parse the players from. 49 | :return: A list containing the players. 50 | """ 51 | players = [] 52 | 53 | for matches in self._get_player_match_collections(br): 54 | matches: list[re.Match] = [match.group() for match in matches] 55 | player = Player( 56 | frags=int(matches[0]), 57 | ping=int(matches[1]), 58 | name=str(matches[2]).strip('"') if len(matches) > 2 else "", 59 | team=int(matches[3]) if len(matches) > 3 else 0, 60 | ) 61 | players.append(player) 62 | 63 | return players 64 | 65 | 66 | if __name__ == "__main__": 67 | import asyncio 68 | 69 | async def main_async(): 70 | quake3 = Quake3(host="108.61.18.110", port=27960, timeout=5.0) 71 | info = await quake3.get_info() 72 | print(info) 73 | status = await quake3.get_status() 74 | print(status) 75 | 76 | asyncio.run(main_async()) 77 | -------------------------------------------------------------------------------- /opengsq/protocols/won.py: -------------------------------------------------------------------------------- 1 | from opengsq.protocols.source import Source 2 | 3 | 4 | class WON(Source): 5 | """World Opponent Network (WON) Query Protocol""" 6 | 7 | full_name = "World Opponent Network (WON) Protocol" 8 | 9 | _A2S_INFO = b"details\0" 10 | _A2S_PLAYER = b"players" 11 | _A2S_RULES = b"rules" 12 | 13 | 14 | if __name__ == "__main__": 15 | import asyncio 16 | 17 | async def main_async(): 18 | won = WON(host="212.227.190.150", port=27020, timeout=5.0) 19 | info = await won.get_info() 20 | print(info) 21 | players = await won.get_players() 22 | print(players) 23 | rules = await won.get_rules() 24 | print(rules) 25 | 26 | asyncio.run(main_async()) 27 | -------------------------------------------------------------------------------- /opengsq/rcon_protocols/__init__.py: -------------------------------------------------------------------------------- 1 | from .source_rcon import SourceRcon 2 | -------------------------------------------------------------------------------- /opengsq/responses/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opengsq/opengsq-python/b34071f3822bd7e22e47dc9b696fe2a4f6828ff9/opengsq/responses/__init__.py -------------------------------------------------------------------------------- /opengsq/responses/ase/__init__.py: -------------------------------------------------------------------------------- 1 | from .player import Player 2 | from .status import Status 3 | -------------------------------------------------------------------------------- /opengsq/responses/ase/player.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | from typing import Optional 5 | 6 | 7 | @dataclass 8 | class Player: 9 | """ 10 | Represents a player in the game. 11 | """ 12 | 13 | name: Optional[str] = None 14 | """The name of the player.""" 15 | 16 | team: Optional[str] = None 17 | """The team of the player.""" 18 | 19 | skin: Optional[str] = None 20 | """The skin of the player.""" 21 | 22 | score: Optional[int] = None 23 | """The score of the player.""" 24 | 25 | ping: Optional[int] = None 26 | """The ping of the player.""" 27 | 28 | time: Optional[int] = None 29 | """The time of the player.""" 30 | -------------------------------------------------------------------------------- /opengsq/responses/ase/status.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass, field 4 | 5 | from opengsq.responses.ase.player import Player 6 | 7 | 8 | @dataclass 9 | class Status: 10 | """ 11 | Represents the status of a game server. 12 | """ 13 | 14 | game_name: str 15 | """The name of the game.""" 16 | 17 | game_port: int 18 | """The port number of the game server.""" 19 | 20 | hostname: str 21 | """The hostname of the game server.""" 22 | 23 | game_type: str 24 | """The type of the game.""" 25 | 26 | map: str 27 | """The current map of the game.""" 28 | 29 | version: str 30 | """The version of the game.""" 31 | 32 | password: bool 33 | """Whether a password is required to join the game.""" 34 | 35 | num_players: int 36 | """The number of players currently in the game.""" 37 | 38 | max_players: int 39 | """The maximum number of players allowed in the game.""" 40 | 41 | rules: dict[str, str] = field(default_factory=dict) 42 | """The rules of the game. Defaults to an empty dictionary.""" 43 | 44 | players: list[Player] = field(default_factory=list) 45 | """The players currently in the game. Defaults to an empty list.""" 46 | -------------------------------------------------------------------------------- /opengsq/responses/battlefield/__init__.py: -------------------------------------------------------------------------------- 1 | from .info import Info 2 | from .version_info import VersionInfo 3 | -------------------------------------------------------------------------------- /opengsq/responses/battlefield/info.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import Optional 3 | from dataclasses import dataclass 4 | 5 | 6 | @dataclass 7 | class Info: 8 | """ 9 | Represents the info of a game. 10 | """ 11 | 12 | hostname: str 13 | """The hostname of the game server.""" 14 | 15 | num_players: int 16 | """The number of players in the game.""" 17 | 18 | max_players: int 19 | """The maximum number of players allowed in the game.""" 20 | 21 | game_type: str 22 | """The type of the game.""" 23 | 24 | map: str 25 | """The current map of the game.""" 26 | 27 | rounds_played: int 28 | """The number of rounds played.""" 29 | 30 | rounds_total: int 31 | """The total number of rounds.""" 32 | 33 | teams: list[float] 34 | """The list of teams.""" 35 | 36 | target_score: int 37 | """The target score.""" 38 | 39 | status: str 40 | """The status of the game.""" 41 | 42 | ranked: bool 43 | """Whether the game is ranked.""" 44 | 45 | punk_buster: bool 46 | """Whether PunkBuster is enabled.""" 47 | 48 | password: bool 49 | """Whether a password is required.""" 50 | 51 | uptime: int 52 | """The uptime of the game server.""" 53 | 54 | round_time: int 55 | """The round time.""" 56 | 57 | mod: Optional[str] = None 58 | """The game mod. This property is optional.""" 59 | 60 | ip_port: Optional[str] = None 61 | """The IP port of the game server. This property is optional.""" 62 | 63 | punk_buster_version: Optional[str] = None 64 | """The version of PunkBuster. This property is optional.""" 65 | 66 | join_queue: Optional[bool] = None 67 | """Whether the join queue is enabled. This property is optional.""" 68 | 69 | region: Optional[str] = None 70 | """The region of the game server. This property is optional.""" 71 | 72 | ping_site: Optional[str] = None 73 | """The ping site of the game server. This property is optional.""" 74 | 75 | country: Optional[str] = None 76 | """The country of the game server. This property is optional.""" 77 | 78 | blaze_player_count: Optional[int] = None 79 | """The number of players in the Blaze game state. This property is optional.""" 80 | 81 | blaze_game_state: Optional[str] = None 82 | """The Blaze game state. This property is optional.""" 83 | 84 | quick_match: Optional[bool] = None 85 | """Whether quick match is enabled. This property is optional.""" 86 | -------------------------------------------------------------------------------- /opengsq/responses/battlefield/version_info.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | 5 | 6 | @dataclass 7 | class VersionInfo: 8 | """ 9 | Represents the version of a game mod. 10 | """ 11 | 12 | mod: str 13 | """The mod of the game.""" 14 | 15 | version: str 16 | """The version of the mod.""" 17 | -------------------------------------------------------------------------------- /opengsq/responses/doom3/__init__.py: -------------------------------------------------------------------------------- 1 | from .status import Status 2 | -------------------------------------------------------------------------------- /opengsq/responses/doom3/status.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | from typing import Union 5 | 6 | 7 | @dataclass 8 | class Status: 9 | """ 10 | Represents the server status. 11 | """ 12 | 13 | info: dict[str, str] 14 | """Server's info.""" 15 | 16 | players: list[dict[str, Union[int, str]]] 17 | """Server's players.""" 18 | -------------------------------------------------------------------------------- /opengsq/responses/eos/__init__.py: -------------------------------------------------------------------------------- 1 | from .matchmaking import Matchmaking 2 | -------------------------------------------------------------------------------- /opengsq/responses/eos/matchmaking.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import Any 3 | from dataclasses import dataclass 4 | 5 | 6 | @dataclass 7 | class Matchmaking: 8 | """ 9 | Represents the response from a matchmaking request. 10 | """ 11 | 12 | sessions: list[dict[str, Any]] 13 | """The list of sessions returned by the matchmaking request. 14 | Each session is represented as a dictionary of string keys and object values.""" 15 | 16 | count: int 17 | """The count of sessions returned by the matchmaking request.""" 18 | -------------------------------------------------------------------------------- /opengsq/responses/flatout2.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Dict, List, Any 3 | 4 | 5 | @dataclass 6 | class Status: 7 | """ 8 | Represents the status response from a Flatout 2 server. 9 | Contains server information and player data. 10 | """ 11 | info: Dict[str, Any] 12 | players: List[Dict[str, Any]] 13 | 14 | def __str__(self) -> str: 15 | """ 16 | Returns a human-readable string representation of the server status. 17 | """ 18 | output = ["Server Information:"] 19 | for key, value in self.info.items(): 20 | output.append(f"{key}: {value}") 21 | 22 | if self.players: 23 | output.append("\nPlayers:") 24 | for player in self.players: 25 | output.append(f"{player['name']} - Score: {player['score']}") 26 | 27 | return "\n".join(output) -------------------------------------------------------------------------------- /opengsq/responses/flatout2/__init__.py: -------------------------------------------------------------------------------- 1 | from .status import Status -------------------------------------------------------------------------------- /opengsq/responses/flatout2/status.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | from typing import Dict 5 | 6 | 7 | @dataclass 8 | class Status: 9 | """ 10 | Represents the status of a Flatout 2 server. 11 | """ 12 | 13 | info: Dict[str, str] 14 | """ 15 | Server information dictionary containing: 16 | - hostname: Server name (UTF-16 encoded) 17 | - timestamp: Server timestamp 18 | - flags: Server configuration flags 19 | - status: Server status flags 20 | - config: Additional configuration data in hex format 21 | """ 22 | 23 | def __str__(self) -> str: 24 | """ 25 | Returns a human-readable string representation of the server status. 26 | 27 | :return: Formatted server status string 28 | """ 29 | result = [] 30 | 31 | # Add server info 32 | result.append("Server Information:") 33 | for key, value in self.info.items(): 34 | result.append(f" {key}: {value}") 35 | 36 | return "\n".join(result) -------------------------------------------------------------------------------- /opengsq/responses/gamespy1/__init__.py: -------------------------------------------------------------------------------- 1 | from .status import Status 2 | -------------------------------------------------------------------------------- /opengsq/responses/gamespy1/status.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | 5 | 6 | @dataclass 7 | class Status: 8 | """ 9 | Represents the server status. 10 | """ 11 | 12 | info: dict[str, str] 13 | """Server's info. If is_XServerQuery is True, then it includes \\info\\xserverquery\\rules\\xserverquery, else \\basic\\info\\rules\\""" 14 | 15 | players: list[dict[str, str]] 16 | """Server's players.""" 17 | 18 | teams: list[dict[str, str]] 19 | """Server's teams. Only when is_x_server_query is True.""" 20 | 21 | @property 22 | def is_XServerQuery(self) -> bool: 23 | """Indicates whether the response is XServerQuery or old response.""" 24 | return "XServerQuery" in self.info 25 | -------------------------------------------------------------------------------- /opengsq/responses/gamespy2/__init__.py: -------------------------------------------------------------------------------- 1 | from .status import Status 2 | -------------------------------------------------------------------------------- /opengsq/responses/gamespy2/status.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | 5 | 6 | @dataclass 7 | class Status: 8 | """ 9 | Represents the status of the server. 10 | """ 11 | 12 | info: dict[str, str] 13 | """The server information.""" 14 | 15 | players: list[dict[str, str]] 16 | """The list of players.""" 17 | 18 | teams: list[dict[str, str]] 19 | """The list of teams.""" 20 | -------------------------------------------------------------------------------- /opengsq/responses/kaillera/__init__.py: -------------------------------------------------------------------------------- 1 | from .status import Status 2 | -------------------------------------------------------------------------------- /opengsq/responses/kaillera/status.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | 5 | 6 | @dataclass 7 | class Status: 8 | """ 9 | Represents the status of the server. 10 | """ 11 | 12 | server_name: str 13 | 14 | ip_address: str 15 | 16 | port: int 17 | 18 | users: int 19 | 20 | max_users: int 21 | 22 | game_count: int 23 | 24 | version: str 25 | 26 | location: str 27 | -------------------------------------------------------------------------------- /opengsq/responses/killingfloor/__init__.py: -------------------------------------------------------------------------------- 1 | from .status import Status 2 | -------------------------------------------------------------------------------- /opengsq/responses/killingfloor/status.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from ..unreal2 import Status as Unreal2Status 3 | 4 | 5 | @dataclass 6 | class Status(Unreal2Status): 7 | """ 8 | Represents the status of a server. 9 | """ 10 | 11 | wave_current: int 12 | """The current wave number in a game.""" 13 | 14 | wave_total: int 15 | """The total number of waves in a game.""" 16 | -------------------------------------------------------------------------------- /opengsq/responses/minecraft/__init__.py: -------------------------------------------------------------------------------- 1 | from .status_pre_17 import StatusPre17 2 | -------------------------------------------------------------------------------- /opengsq/responses/minecraft/status_pre_17.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class StatusPre17: 6 | """ 7 | Represents the status of a game for versions prior to 1.7. 8 | """ 9 | 10 | protocol: str 11 | """The protocol of the game.""" 12 | 13 | version: str 14 | """The version of the game.""" 15 | 16 | motd: str 17 | """The message of the day.""" 18 | 19 | num_players: int 20 | """The number of players in the game.""" 21 | 22 | max_players: int 23 | """The maximum number of players allowed in the game.""" 24 | -------------------------------------------------------------------------------- /opengsq/responses/nadeo/__init__.py: -------------------------------------------------------------------------------- 1 | from .status import Status 2 | -------------------------------------------------------------------------------- /opengsq/responses/nadeo/status.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from dataclasses import dataclass 3 | from typing import Any, Optional 4 | 5 | 6 | @dataclass 7 | class Player: 8 | """Represents a player on the server.""" 9 | login: str 10 | nickname: str 11 | player_id: int 12 | team_id: int 13 | is_spectator: bool 14 | ladder_ranking: int 15 | flags: int 16 | 17 | 18 | @dataclass 19 | class ServerOptions: 20 | """Represents server configuration options.""" 21 | name: str 22 | comment: str 23 | password: bool 24 | max_players: int 25 | max_spectators: int 26 | current_game_mode: int 27 | current_chat_time: int 28 | hide_server: int 29 | ladder_mode: int 30 | vehicle_quality: int 31 | 32 | 33 | @dataclass 34 | class Version: 35 | """Represents the server version information.""" 36 | name: str 37 | version: str 38 | build: str 39 | 40 | 41 | @dataclass 42 | class Status: 43 | version: Version 44 | server_options: ServerOptions 45 | players: list[Player] 46 | map_info: MapInfo # Add this 47 | 48 | @classmethod 49 | def from_raw_data(cls, version_data: dict[str, str], 50 | server_data: dict[str, Any], 51 | players_data: list[dict[str, Any]], 52 | map_data: dict[str, Any]) -> Status: 53 | version = Version( 54 | name=version_data.get('Name', ''), 55 | version=version_data.get('Version', ''), 56 | build=version_data.get('Build', '') 57 | ) 58 | 59 | server_options = ServerOptions( 60 | name=server_data.get('Name', ''), 61 | comment=server_data.get('Comment', ''), 62 | password=server_data.get('Password', False), 63 | max_players=server_data.get('CurrentMaxPlayers', 0), 64 | max_spectators=server_data.get('CurrentMaxSpectators', 0), 65 | current_game_mode=server_data.get('CurrentGameMode', 0), 66 | current_chat_time=server_data.get('CurrentChatTime', 0), 67 | hide_server=server_data.get('HideServer', 0), 68 | ladder_mode=server_data.get('CurrentLadderMode', 0), 69 | vehicle_quality=server_data.get('CurrentVehicleNetQuality', 0) 70 | ) 71 | 72 | players = [ 73 | Player( 74 | login=p.get('Login', ''), 75 | nickname=p.get('NickName', ''), 76 | player_id=p.get('PlayerId', -1), 77 | team_id=p.get('TeamId', -1), 78 | is_spectator=p.get('IsSpectator', False), 79 | ladder_ranking=p.get('LadderRanking', 0), 80 | flags=p.get('Flags', 0) 81 | ) 82 | for p in players_data 83 | ] 84 | 85 | map_info = MapInfo.from_dict(map_data) 86 | 87 | return cls(version, server_options, players, map_info) 88 | 89 | @dataclass 90 | class MapInfo: 91 | """Represents current map information.""" 92 | name: str 93 | author: str 94 | environment: str 95 | 96 | @classmethod 97 | def from_dict(cls, data: dict[str, Any]) -> MapInfo: 98 | return cls( 99 | name=data.get('Name', ''), 100 | author=data.get('Author', ''), 101 | environment=data.get('Environment', '') 102 | ) -------------------------------------------------------------------------------- /opengsq/responses/palworld/__init__.py: -------------------------------------------------------------------------------- 1 | from .status import Status 2 | from .player import Player 3 | -------------------------------------------------------------------------------- /opengsq/responses/palworld/player.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | @dataclass 4 | class Player: 5 | """ 6 | Represents a player in the game. 7 | """ 8 | 9 | name: str 10 | """The player's name.""" 11 | 12 | ping: int 13 | """The player's ping.""" 14 | 15 | level: int 16 | """The player's level.""" 17 | -------------------------------------------------------------------------------- /opengsq/responses/palworld/status.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class Status: 6 | """ 7 | Represents the status response from a server. 8 | """ 9 | 10 | num_players: int 11 | """The number of players currently connected to the server.""" 12 | 13 | max_players: int 14 | """The maximum number of players that can connect to the server.""" 15 | 16 | server_name: str 17 | """The name of the server.""" -------------------------------------------------------------------------------- /opengsq/responses/quake1/__init__.py: -------------------------------------------------------------------------------- 1 | from .player import Player 2 | from .status import Status 3 | -------------------------------------------------------------------------------- /opengsq/responses/quake1/player.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class Player: 6 | """ 7 | Represents a player in the game. 8 | """ 9 | 10 | id: int 11 | """The player's ID.""" 12 | 13 | score: int 14 | """The player's score.""" 15 | 16 | time: int 17 | """The player's time.""" 18 | 19 | ping: int 20 | """The player's ping.""" 21 | 22 | name: str 23 | """The player's name.""" 24 | 25 | skin: str 26 | """The player's skin.""" 27 | 28 | color1: int 29 | """The player's first color.""" 30 | 31 | color2: int 32 | """The player's second color.""" 33 | -------------------------------------------------------------------------------- /opengsq/responses/quake1/status.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | from .player import Player 5 | 6 | 7 | @dataclass 8 | class Status: 9 | """ 10 | Represents the status of the server. 11 | """ 12 | 13 | info: dict[str, str] 14 | """The server information.""" 15 | 16 | players: list[Player] 17 | """The list of players.""" 18 | -------------------------------------------------------------------------------- /opengsq/responses/quake2/__init__.py: -------------------------------------------------------------------------------- 1 | from .player import Player 2 | from .status import Status 3 | -------------------------------------------------------------------------------- /opengsq/responses/quake2/player.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class Player: 6 | """ 7 | Represents a player in the game. 8 | """ 9 | 10 | frags: int 11 | """The player's frags.""" 12 | 13 | ping: int 14 | """The player's ping.""" 15 | 16 | name: str 17 | """The player's name.""" 18 | 19 | address: str 20 | """The player's address.""" 21 | -------------------------------------------------------------------------------- /opengsq/responses/quake2/status.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | from .player import Player 5 | 6 | 7 | @dataclass 8 | class Status: 9 | """ 10 | Represents the status of the server. 11 | """ 12 | 13 | info: dict[str, str] 14 | """The server information.""" 15 | 16 | players: list[Player] 17 | """The list of players.""" 18 | -------------------------------------------------------------------------------- /opengsq/responses/raknet/__init__.py: -------------------------------------------------------------------------------- 1 | from .status import Status 2 | -------------------------------------------------------------------------------- /opengsq/responses/raknet/status.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class Status: 6 | """ 7 | Represents the status response from a Minecraft server. 8 | """ 9 | 10 | edition: str 11 | """The edition of the server (MCPE or MCEE for Education Edition).""" 12 | 13 | motd_line1: str 14 | """The first line of the Message of the Day (MOTD).""" 15 | 16 | protocol_version: int 17 | """The protocol version of the server.""" 18 | 19 | version_name: str 20 | """The version name of the server.""" 21 | 22 | num_players: int 23 | """The number of players currently on the server.""" 24 | 25 | max_players: int 26 | """The maximum number of players that can join the server.""" 27 | 28 | server_unique_id: str 29 | """The unique ID of the server.""" 30 | 31 | motd_line2: str 32 | """The second line of the Message of the Day (MOTD).""" 33 | 34 | game_mode: str 35 | """The game mode of the server.""" 36 | 37 | game_mode_numeric: int 38 | """The numeric representation of the game mode.""" 39 | 40 | port_ipv4: int 41 | """The IPv4 port of the server.""" 42 | 43 | port_ipv6: int 44 | """The IPv6 port of the server.""" 45 | -------------------------------------------------------------------------------- /opengsq/responses/renegadex/__init__.py: -------------------------------------------------------------------------------- 1 | from .status import Status 2 | -------------------------------------------------------------------------------- /opengsq/responses/renegadex/status.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Any 3 | 4 | @dataclass 5 | class Variables: 6 | player_limit: int 7 | vehicle_limit: int 8 | mine_limit: int 9 | time_limit: int 10 | passworded: bool 11 | steam_required: bool 12 | team_mode: int 13 | spawn_crates: bool 14 | game_type: int 15 | ranked: bool 16 | 17 | @classmethod 18 | def from_dict(cls, data: dict[str, Any]) -> 'Variables': 19 | return cls( 20 | player_limit=data["Player Limit"], 21 | vehicle_limit=data["Vehicle Limit"], 22 | mine_limit=data["Mine Limit"], 23 | time_limit=data["Time Limit"], 24 | passworded=data["bPassworded"], 25 | steam_required=data["bSteamRequired"], 26 | team_mode=data["Team Mode"], 27 | spawn_crates=data["bSpawnCrates"], 28 | game_type=data["Game Type"], 29 | ranked=data["bRanked"] 30 | ) 31 | 32 | @dataclass 33 | class Status: 34 | name: str 35 | map: str 36 | port: int 37 | players: int 38 | game_version: str 39 | variables: Variables 40 | raw: dict[str, Any] 41 | 42 | @classmethod 43 | def from_dict(cls, data: dict[str, Any]) -> 'Status': 44 | return cls( 45 | name=data["Name"], 46 | map=data["Current Map"], 47 | port=data["Port"], 48 | players=data["Players"], 49 | game_version=data["Game Version"], 50 | variables=Variables.from_dict(data["Variables"]), 51 | raw=data 52 | ) -------------------------------------------------------------------------------- /opengsq/responses/samp/__init__.py: -------------------------------------------------------------------------------- 1 | from .player import Player 2 | from .status import Status 3 | -------------------------------------------------------------------------------- /opengsq/responses/samp/player.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class Player: 6 | """ 7 | Represents the Player class. 8 | """ 9 | 10 | id: int 11 | """The ID of the player.""" 12 | 13 | name: str 14 | """The name of the player.""" 15 | 16 | score: int 17 | """The score of the player.""" 18 | 19 | ping: int 20 | """The ping of the player.""" 21 | -------------------------------------------------------------------------------- /opengsq/responses/samp/status.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class Status: 6 | """ 7 | Represents the status response from a server. 8 | """ 9 | 10 | password: bool 11 | """A value indicating whether a password is required to connect to the server.""" 12 | 13 | num_players: int 14 | """The number of players currently connected to the server.""" 15 | 16 | max_players: int 17 | """The maximum number of players that can connect to the server.""" 18 | 19 | server_name: str 20 | """The name of the server.""" 21 | 22 | game_type: str 23 | """The type of game being played on the server.""" 24 | 25 | language: str 26 | """The language of the server.""" 27 | -------------------------------------------------------------------------------- /opengsq/responses/satisfactory/__init__.py: -------------------------------------------------------------------------------- 1 | from .status import Status 2 | -------------------------------------------------------------------------------- /opengsq/responses/satisfactory/status.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class Status: 6 | """ 7 | Represents the status response. 8 | """ 9 | 10 | state: int 11 | """The state.""" 12 | 13 | num_players: int 14 | """The number of players currently connected to the server.""" 15 | 16 | max_players: int 17 | """The maximum number of players that can connect to the server.""" 18 | 19 | name: str 20 | """The name of the server.""" 21 | -------------------------------------------------------------------------------- /opengsq/responses/scum/__init__.py: -------------------------------------------------------------------------------- 1 | from .status import Status 2 | -------------------------------------------------------------------------------- /opengsq/responses/scum/status.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class Status: 6 | """ 7 | Represents the response status of a server. 8 | """ 9 | 10 | ip: str 11 | """The IP address of the server.""" 12 | 13 | port: int 14 | """The port number of the server.""" 15 | 16 | name: str 17 | """The name of the server.""" 18 | 19 | num_players: int 20 | """The number of players currently connected to the server.""" 21 | 22 | max_players: int 23 | """The maximum number of players that can connect to the server.""" 24 | 25 | time: int 26 | """The server time.""" 27 | 28 | password: bool 29 | """A value indicating whether a password is required to connect to the server.""" 30 | 31 | version: str 32 | """The version of the server.""" 33 | -------------------------------------------------------------------------------- /opengsq/responses/source/__init__.py: -------------------------------------------------------------------------------- 1 | from .environment import Environment 2 | from .extra_data_flag import ExtraDataFlag 3 | from .gold_source_info import GoldSourceInfo 4 | from .partial_info import PartialInfo 5 | from .player import Player 6 | from .server_type import ServerType 7 | from .source_info import SourceInfo 8 | from .vac import VAC 9 | from .visibility import Visibility 10 | -------------------------------------------------------------------------------- /opengsq/responses/source/environment.py: -------------------------------------------------------------------------------- 1 | from enum import IntEnum 2 | 3 | 4 | class Environment(IntEnum): 5 | """ 6 | Indicates the operating system of the server. 7 | """ 8 | 9 | Linux = 0x6C 10 | """Linux""" 11 | 12 | Windows = 0x77 13 | """Windows""" 14 | 15 | Mac = 0x6D 16 | """Mac""" 17 | 18 | @staticmethod 19 | def parse(byte: int): 20 | """ 21 | Parses the given byte to an Environment value. If the byte does not correspond to a valid 22 | Environment value, it defaults to Environment.Mac. 23 | 24 | Args: 25 | byte (int): The byte to parse. 26 | 27 | Returns: 28 | Environment: The corresponding Environment value, or Environment.Mac if the byte is not valid. 29 | """ 30 | try: 31 | return Environment(ord(chr(byte).lower())) 32 | except ValueError: 33 | # 'm' or 'o' for Mac (the code changed after L4D1) 34 | return Environment.Mac 35 | -------------------------------------------------------------------------------- /opengsq/responses/source/extra_data_flag.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | 4 | class ExtraDataFlag(int): 5 | """ 6 | Extra Data Flag (EDF) 7 | """ 8 | 9 | Port = 0x80 10 | """Port""" 11 | 12 | SteamID = 0x10 13 | """SteamID""" 14 | 15 | Spectator = 0x40 16 | """Spectator""" 17 | 18 | Keywords = 0x20 19 | """Keywords""" 20 | 21 | GameID = 0x01 22 | """GameID""" 23 | 24 | def __init__(self, flags: int) -> None: 25 | """ 26 | Initializes the ExtraDataFlag with the given flags. 27 | 28 | :param flags: The flags to initialize the ExtraDataFlag with. 29 | """ 30 | super().__init__() 31 | self.flags = flags 32 | 33 | def has_flag(self, flag: int): 34 | """ 35 | Checks if the ExtraDataFlag has the given flag. 36 | 37 | :param flag: The flag to check. 38 | :return: True if the ExtraDataFlag has the flag, False otherwise. 39 | """ 40 | return self.flags & flag 41 | -------------------------------------------------------------------------------- /opengsq/responses/source/gold_source_info.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | from typing import Optional 5 | from .partial_info import PartialInfo 6 | 7 | 8 | @dataclass 9 | class GoldSourceInfo(PartialInfo): 10 | """ 11 | Obsolete GoldSource Response 12 | """ 13 | 14 | address: str 15 | """IP address and port of the server.""" 16 | 17 | mod: int 18 | """ 19 | Indicates whether the game is a mod 20 | 0 for Half-Life 21 | 1 for Half-Life mod 22 | """ 23 | 24 | link: Optional[str] = None 25 | """URL to mod website.""" 26 | 27 | download_link: Optional[str] = None 28 | """URL to download the mod.""" 29 | 30 | version: Optional[int] = None 31 | """Version of mod installed on server.""" 32 | 33 | size: Optional[int] = None 34 | """Space (in bytes) the mod takes up.""" 35 | 36 | type: Optional[int] = None 37 | """ 38 | Indicates the type of mod: 39 | 0 for single and multiplayer mod 40 | 1 for multiplayer only mod 41 | """ 42 | 43 | dll: Optional[int] = None 44 | """ 45 | Indicates whether mod uses its own DLL: 46 | 0 if it uses the Half-Life DLL 47 | 1 if it uses its own DLL 48 | """ 49 | -------------------------------------------------------------------------------- /opengsq/responses/source/partial_info.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from .server_type import ServerType 3 | from .environment import Environment 4 | from .visibility import Visibility 5 | from .vac import VAC 6 | 7 | 8 | @dataclass 9 | class PartialInfo: 10 | """ 11 | A2S_INFO Partial Info 12 | """ 13 | 14 | protocol: int 15 | """Protocol version used by the server.""" 16 | 17 | name: str 18 | """Name of the server.""" 19 | 20 | map: str 21 | """Map the server has currently loaded.""" 22 | 23 | folder: str 24 | """Name of the folder containing the game files.""" 25 | 26 | game: str 27 | """Full name of the game.""" 28 | 29 | players: int 30 | """Number of players on the server.""" 31 | 32 | max_players: int 33 | """Maximum number of players the server reports it can hold.""" 34 | 35 | bots: int 36 | """Number of bots on the server.""" 37 | 38 | server_type: ServerType 39 | """Indicates the type of server.""" 40 | 41 | environment: Environment 42 | """Indicates the operating system of the server.""" 43 | 44 | visibility: Visibility 45 | """Indicates whether the server requires a password.""" 46 | 47 | vac: VAC 48 | """Specifies whether the server uses VAC.""" 49 | -------------------------------------------------------------------------------- /opengsq/responses/source/player.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Optional 3 | 4 | 5 | @dataclass 6 | class Player: 7 | """ 8 | Player Data 9 | """ 10 | 11 | name: str 12 | """Player Name""" 13 | 14 | score: int 15 | """Player Score""" 16 | 17 | duration: float 18 | """Player Duration""" 19 | 20 | deaths: Optional[int] = None 21 | """Player Deaths""" 22 | 23 | money: Optional[int] = None 24 | """Player Money""" 25 | -------------------------------------------------------------------------------- /opengsq/responses/source/server_type.py: -------------------------------------------------------------------------------- 1 | from enum import IntEnum 2 | 3 | 4 | class ServerType(IntEnum): 5 | """ 6 | Indicates the type of server. 7 | """ 8 | 9 | Dedicated = 0x64 10 | """Dedicated server""" 11 | 12 | Listen = 0x6C 13 | """Listen server""" 14 | 15 | Proxy = 0x70 16 | """SourceTV relay (proxy)""" 17 | 18 | @staticmethod 19 | def parse(byte: int): 20 | """ 21 | Parses the given byte to a ServerType value. If the byte does not correspond to a valid 22 | ServerType value, a ValueError is raised. 23 | 24 | Args: 25 | byte (int): The byte to parse. 26 | 27 | Returns: 28 | ServerType: The corresponding ServerType value. 29 | """ 30 | return ServerType(ord(chr(byte).lower())) 31 | -------------------------------------------------------------------------------- /opengsq/responses/source/source_info.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Optional 3 | from .partial_info import PartialInfo 4 | from .extra_data_flag import ExtraDataFlag 5 | 6 | 7 | @dataclass 8 | class SourceInfo(PartialInfo): 9 | """ 10 | Source Response 11 | """ 12 | 13 | id: int 14 | """Steam Application ID of game.""" 15 | 16 | version: str 17 | """Version of the game installed on the server.""" 18 | 19 | edf: Optional[ExtraDataFlag] = None 20 | """If present, this specifies which additional data fields will be included.""" 21 | 22 | port: Optional[int] = None 23 | """The server's game port number.""" 24 | 25 | steam_id: Optional[int] = None 26 | """Server's SteamID.""" 27 | 28 | spectator_port: Optional[int] = None 29 | """Spectator port number for SourceTV.""" 30 | 31 | spectator_name: Optional[str] = None 32 | """Name of the spectator server for SourceTV.""" 33 | 34 | keywords: Optional[str] = None 35 | """Tags that describe the game according to the server (for future use.)""" 36 | 37 | game_id: Optional[int] = None 38 | """The server's 64-bit GameID. If this is present, a more accurate AppID is present in the low 24 bits. The earlier AppID could have been truncated as it was forced into 16-bit storage.""" 39 | 40 | mode: Optional[int] = None 41 | """Indicates the game mode.""" 42 | 43 | witnesses: Optional[int] = None 44 | """The number of witnesses necessary to have a player arrested.""" 45 | 46 | duration: Optional[int] = None 47 | """Time (in seconds) before a player is arrested while being witnessed.""" 48 | -------------------------------------------------------------------------------- /opengsq/responses/source/vac.py: -------------------------------------------------------------------------------- 1 | from enum import IntEnum 2 | 3 | 4 | class VAC(IntEnum): 5 | """ 6 | Specifies whether the server uses VAC. 7 | """ 8 | 9 | Unsecured = 0 10 | """Unsecured""" 11 | 12 | Secured = 1 13 | """Secured""" 14 | -------------------------------------------------------------------------------- /opengsq/responses/source/visibility.py: -------------------------------------------------------------------------------- 1 | from enum import IntEnum 2 | 3 | 4 | class Visibility(IntEnum): 5 | """ 6 | Indicates whether the server requires a password. 7 | """ 8 | 9 | Public = 0 10 | """Public""" 11 | 12 | Private = 1 13 | """Private""" 14 | -------------------------------------------------------------------------------- /opengsq/responses/toxikk/__init__.py: -------------------------------------------------------------------------------- 1 | from .status import Status -------------------------------------------------------------------------------- /opengsq/responses/toxikk/status.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from dataclasses import dataclass, field 3 | from typing import List 4 | from opengsq.responses.udk.status import Status as UDKStatus 5 | 6 | @dataclass 7 | class Status(UDKStatus): 8 | """Toxikk-specific status response""" 9 | mutators: List[str] = field(default_factory=list) -------------------------------------------------------------------------------- /opengsq/responses/udk/__init__.py: -------------------------------------------------------------------------------- 1 | from .status import Status -------------------------------------------------------------------------------- /opengsq/responses/udk/status.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Union, List 3 | from enum import IntEnum 4 | 5 | class PlatformType(IntEnum): 6 | Unknown = 0 7 | Windows = 1 8 | Xenon = 4 9 | PS3 = 8 10 | Linux = 16 11 | MacOSX = 32 12 | 13 | @dataclass 14 | class Player: 15 | name: str 16 | score: int = 0 17 | ping: int = 0 18 | team: int = 0 19 | 20 | @dataclass 21 | class Status: 22 | name: str 23 | map: str 24 | game_type: str 25 | num_players: int 26 | max_players: int 27 | password_protected: bool 28 | stats_enabled: bool 29 | lan_mode: bool 30 | players: List[Player] 31 | raw: dict[str, Union[str, int, bool, list]] -------------------------------------------------------------------------------- /opengsq/responses/unreal2/__init__.py: -------------------------------------------------------------------------------- 1 | from .player import Player 2 | from .status import Status 3 | -------------------------------------------------------------------------------- /opengsq/responses/unreal2/player.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class Player: 6 | """ 7 | Represents a player in the game. 8 | """ 9 | 10 | id: int 11 | """The ID of the player.""" 12 | 13 | name: str 14 | """The name of the player.""" 15 | 16 | ping: int 17 | """The ping of the player.""" 18 | 19 | score: int 20 | """The score of the player.""" 21 | 22 | stats_id: int 23 | """The stats ID of the player.""" 24 | -------------------------------------------------------------------------------- /opengsq/responses/unreal2/status.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class Status: 6 | """ 7 | Represents the status of a server. 8 | """ 9 | 10 | server_id: int 11 | """The server ID.""" 12 | 13 | server_ip: str 14 | """The IP address of the server.""" 15 | 16 | game_port: int 17 | """The game port of the server.""" 18 | 19 | query_port: int 20 | """The query port of the server.""" 21 | 22 | server_name: str 23 | """The name of the server.""" 24 | 25 | map_name: str 26 | """The name of the map.""" 27 | 28 | game_type: str 29 | """The type of the game.""" 30 | 31 | num_players: int 32 | """The number of players.""" 33 | 34 | max_players: int 35 | """The maximum number of players.""" 36 | 37 | ping: int 38 | """The ping.""" 39 | 40 | flags: int 41 | """The flags.""" 42 | 43 | skill: str 44 | """The skill level.""" 45 | -------------------------------------------------------------------------------- /opengsq/responses/ut3/__init__.py: -------------------------------------------------------------------------------- 1 | from .status import Status -------------------------------------------------------------------------------- /opengsq/responses/ut3/status.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from dataclasses import dataclass 3 | from typing import List 4 | from opengsq.responses.udk.status import Status as UDKStatus 5 | 6 | @dataclass 7 | class Status(UDKStatus): 8 | """UT3-specific status response""" 9 | mutators: List[str] = None 10 | stock_mutators: List[str] = None 11 | custom_mutators: List[str] = None -------------------------------------------------------------------------------- /opengsq/responses/vcmp/__init__.py: -------------------------------------------------------------------------------- 1 | from .player import Player 2 | from .status import Status 3 | -------------------------------------------------------------------------------- /opengsq/responses/vcmp/player.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class Player: 6 | """ 7 | Represents a player in the game. 8 | """ 9 | 10 | name: str 11 | """The player's name.""" 12 | -------------------------------------------------------------------------------- /opengsq/responses/vcmp/status.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from dataclasses import dataclass 3 | 4 | 5 | @dataclass 6 | class Status: 7 | """ 8 | Represents the status response from a server. 9 | """ 10 | 11 | version: str 12 | """The version of the server.""" 13 | 14 | password: bool 15 | """Indicates whether a password is required to connect to the server.""" 16 | 17 | num_players: int 18 | """The number of players currently connected to the server.""" 19 | 20 | max_players: int 21 | """The maximum number of players that can connect to the server.""" 22 | 23 | server_name: str 24 | """The name of the server.""" 25 | 26 | game_type: str 27 | """The type of game being played on the server.""" 28 | 29 | language: str 30 | """The language of the server.""" 31 | -------------------------------------------------------------------------------- /opengsq/responses/warcraft3/__init__.py: -------------------------------------------------------------------------------- 1 | from opengsq.responses.warcraft3.status import Status 2 | 3 | __all__ = ["Status"] -------------------------------------------------------------------------------- /opengsq/responses/warcraft3/status.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Dict, Any 3 | 4 | @dataclass 5 | class Status: 6 | """ 7 | Represents the status of a Warcraft 3 game server. 8 | """ 9 | game_version: str 10 | hostname: str 11 | map_name: str 12 | game_type: str 13 | num_players: int 14 | max_players: int 15 | raw: Dict[str, Any] -------------------------------------------------------------------------------- /opengsq/responses/warfork/__init__.py: -------------------------------------------------------------------------------- 1 | from .player import Player 2 | -------------------------------------------------------------------------------- /opengsq/responses/warfork/player.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class Player: 6 | """ 7 | Represents a player in the game. 8 | """ 9 | 10 | frags: int 11 | """The player's frags.""" 12 | 13 | ping: int 14 | """The player's ping.""" 15 | 16 | name: str 17 | """The player's name.""" 18 | 19 | team: int 20 | """The player's team.""" 21 | -------------------------------------------------------------------------------- /opengsq/version.py: -------------------------------------------------------------------------------- 1 | __version__ = '3.3.0' 2 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel"] 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp>=3.7.4,<4 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | per-file-ignores = 3 | */__init__.py: F401 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from setuptools import find_packages, setup 4 | 5 | current_dir = os.path.abspath(os.path.dirname(__file__)) 6 | version_contents = {} 7 | 8 | with open(os.path.join(current_dir, 'opengsq', 'version.py'), encoding='utf-8') as f: 9 | exec(f.read(), version_contents) 10 | 11 | with open('README.md', 'r', encoding='utf-8') as f: 12 | long_description = f.read() 13 | 14 | try: 15 | with open('requirements.txt', 'r', encoding='utf-8') as f: 16 | install_requires = f.read().splitlines() 17 | except FileNotFoundError: 18 | with open(os.path.join(current_dir, 'opengsq.egg-info', 'requires.txt'), 'r', encoding='utf-8') as f: 19 | install_requires = f.read().splitlines() 20 | 21 | setup( 22 | name='opengsq', 23 | version=version_contents['__version__'], 24 | description='🐍 OpenGSQ - Python library for querying game servers', 25 | long_description=long_description, 26 | long_description_content_type='text/markdown', 27 | install_requires=install_requires, 28 | entry_points={'console_scripts': ['opengsq=opengsq.cli:main']}, 29 | packages=find_packages(exclude=['tests', 'tests.*']), 30 | python_requires='>=3.7', 31 | url='https://github.com/opengsq/opengsq-python', 32 | project_urls={ 33 | 'Bug Tracker': 'https://github.com/opengsq/opengsq-python/issues', 34 | 'Source Code': 'https://github.com/opengsq/opengsq-python', 35 | 'Documentation': 'https://python.opengsq.com/', 36 | }, 37 | license='MIT', 38 | author='OpenGSQ', 39 | classifiers=[ 40 | 'Development Status :: 5 - Production/Stable', 41 | 'Intended Audience :: Developers', 42 | 'License :: OSI Approved :: MIT License', 43 | 'Operating System :: OS Independent', 44 | 'Programming Language :: Python', 45 | 'Programming Language :: Python :: 3.9', 46 | 'Programming Language :: Python :: 3.10', 47 | 'Programming Language :: Python :: 3.11', 48 | 'Programming Language :: Python :: 3.12', 49 | 'Programming Language :: Python :: 3.13', 50 | 'Topic :: Software Development :: Libraries :: Python Modules', 51 | ], 52 | ) 53 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opengsq/opengsq-python/b34071f3822bd7e22e47dc9b696fe2a4f6828ff9/tests/__init__.py -------------------------------------------------------------------------------- /tests/protocols/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opengsq/opengsq-python/b34071f3822bd7e22e47dc9b696fe2a4f6828ff9/tests/protocols/__init__.py -------------------------------------------------------------------------------- /tests/protocols/test_ase.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.ase import ASE 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | 9 | # Grand Theft Auto: San Andreas - Multi Theft Auto 10 | ase = ASE(host="79.137.97.3", port=22126) 11 | 12 | 13 | @pytest.mark.asyncio 14 | async def test_get_status(): 15 | result = await ase.get_status() 16 | await handler.save_result("test_get_status", result) 17 | -------------------------------------------------------------------------------- /tests/protocols/test_battlefield.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.battlefield import Battlefield 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | 9 | # bf4 10 | test = Battlefield(host="74.91.124.140", port=47200) 11 | 12 | 13 | @pytest.mark.asyncio 14 | async def test_get_info(): 15 | result = await test.get_info() 16 | await handler.save_result("test_get_info", result) 17 | 18 | 19 | @pytest.mark.asyncio 20 | async def test_get_version(): 21 | result = await test.get_version() 22 | await handler.save_result("test_get_version", result) 23 | 24 | 25 | @pytest.mark.asyncio 26 | async def test_get_players(): 27 | result = await test.get_players() 28 | await handler.save_result("test_get_players", result) 29 | -------------------------------------------------------------------------------- /tests/protocols/test_doom3.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.doom3 import Doom3 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | 9 | # Quake 4 10 | doom3 = Doom3(host="178.162.135.83", port=27735) 11 | 12 | 13 | @pytest.mark.asyncio 14 | async def test_get_status(): 15 | result = await doom3.get_status() 16 | await handler.save_result("test_get_status", result) 17 | -------------------------------------------------------------------------------- /tests/protocols/test_eos.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.eos import EOS 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | 9 | 10 | @pytest.mark.asyncio 11 | async def test_get_matchmaking(): 12 | # The Isle - EVRIMA 13 | client_id = "xyza7891gk5PRo3J7G9puCJGFJjmEguW" 14 | client_secret = "pKWl6t5i9NJK8gTpVlAxzENZ65P8hYzodV8Dqe5Rlc8" 15 | deployment_id = "6db6bea492f94b1bbdfcdfe3e4f898dc" 16 | grant_type = "client_credentials" 17 | external_auth_type = "" 18 | external_auth_token = "" 19 | 20 | access_token = await EOS.get_access_token( 21 | client_id=client_id, 22 | client_secret=client_secret, 23 | deployment_id=deployment_id, 24 | grant_type=grant_type, 25 | external_auth_type=external_auth_type, 26 | external_auth_token=external_auth_token, 27 | ) 28 | 29 | matchmaking = await EOS.get_matchmaking(deployment_id, access_token) 30 | await handler.save_result("test_get_matchmaking", matchmaking) 31 | 32 | 33 | @pytest.mark.asyncio 34 | async def test_get_info(): 35 | # Ark: Survival Ascended 36 | client_id = "xyza7891muomRmynIIHaJB9COBKkwj6n" 37 | client_secret = "PP5UGxysEieNfSrEicaD1N2Bb3TdXuD7xHYcsdUHZ7s" 38 | deployment_id = "ad9a8feffb3b4b2ca315546f038c3ae2" 39 | grant_type = "client_credentials" 40 | external_auth_type = "" 41 | external_auth_token = "" 42 | 43 | access_token = await EOS.get_access_token( 44 | client_id=client_id, 45 | client_secret=client_secret, 46 | deployment_id=deployment_id, 47 | grant_type=grant_type, 48 | external_auth_type=external_auth_type, 49 | external_auth_token=external_auth_token, 50 | ) 51 | 52 | eos = EOS( 53 | host="5.62.115.46", 54 | port=7783, 55 | deployment_id=deployment_id, 56 | access_token=access_token, 57 | timeout=5.0, 58 | ) 59 | 60 | result = await eos.get_info() 61 | await handler.save_result("test_get_info", result) 62 | 63 | 64 | @pytest.mark.asyncio 65 | async def test_get_info_palworld(): 66 | # Palworld 67 | client_id = "xyza78916PZ5DF0fAahu4tnrKKyFpqRE" 68 | client_secret = "j0NapLEPm3R3EOrlQiM8cRLKq3Rt02ZVVwT0SkZstSg" 69 | deployment_id = "0a18471f93d448e2a1f60e47e03d3413" 70 | grant_type = "external_auth" 71 | external_auth_type = "deviceid_access_token" # https://dev.epicgames.com/docs/web-api-ref/connect-web-api 72 | external_auth_token = await EOS.get_external_auth_token( 73 | client_id=client_id, 74 | client_secret=client_secret, 75 | external_auth_type=external_auth_type, 76 | ) 77 | 78 | access_token = await EOS.get_access_token( 79 | client_id=client_id, 80 | client_secret=client_secret, 81 | deployment_id=deployment_id, 82 | grant_type=grant_type, 83 | external_auth_type=external_auth_type, 84 | external_auth_token=external_auth_token, 85 | ) 86 | 87 | eos = EOS( 88 | host="35.226.201.18", 89 | port=30111, 90 | deployment_id=deployment_id, 91 | access_token=access_token, 92 | timeout=5.0, 93 | ) 94 | 95 | result = await eos.get_info() 96 | await handler.save_result("test_get_info_palworld", result) 97 | -------------------------------------------------------------------------------- /tests/protocols/test_fivem.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.fivem import FiveM 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | 9 | # FiveM 10 | fivem = FiveM(host="185.254.99.12", port=30120) 11 | 12 | 13 | @pytest.mark.asyncio 14 | async def test_get_info(): 15 | result = await fivem.get_info() 16 | await handler.save_result("test_get_info", result) 17 | 18 | 19 | @pytest.mark.asyncio 20 | async def test_get_players(): 21 | result = await fivem.get_players() 22 | await handler.save_result("test_get_players", result) 23 | 24 | 25 | @pytest.mark.asyncio 26 | async def test_get_dynamic(): 27 | result = await fivem.get_dynamic() 28 | await handler.save_result("test_get_dynamic", result) 29 | -------------------------------------------------------------------------------- /tests/protocols/test_gamespy1.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.gamespy1 import GameSpy1 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | handler.delay_per_test = 1 9 | 10 | test = GameSpy1(host="139.162.235.20", port=7778) 11 | 12 | 13 | @pytest.mark.asyncio 14 | async def test_get_basic(): 15 | result = await test.get_basic() 16 | await handler.save_result("test_get_basic", result) 17 | 18 | 19 | @pytest.mark.asyncio 20 | async def test_get_info(): 21 | result = await test.get_info() 22 | await handler.save_result("test_get_info", result) 23 | 24 | 25 | @pytest.mark.asyncio 26 | async def test_get_rules(): 27 | result = await test.get_rules() 28 | await handler.save_result("test_get_rules", result) 29 | 30 | 31 | @pytest.mark.asyncio 32 | async def test_get_players(): 33 | result = await test.get_players() 34 | await handler.save_result("test_get_players", result) 35 | 36 | 37 | @pytest.mark.asyncio 38 | async def test_get_status(): 39 | result = await test.get_status() 40 | await handler.save_result("test_get_status", result) 41 | 42 | 43 | @pytest.mark.asyncio 44 | async def test_get_teams(): 45 | result = await test.get_teams() 46 | await handler.save_result("test_get_teams", result) 47 | -------------------------------------------------------------------------------- /tests/protocols/test_gamespy2.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.gamespy2 import GameSpy2 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | 9 | # bfv 10 | test = GameSpy2(host="108.61.236.22", port=23000) 11 | 12 | 13 | @pytest.mark.asyncio 14 | async def test_get_status(): 15 | result = await test.get_status() 16 | await handler.save_result("test_get_status", result) 17 | -------------------------------------------------------------------------------- /tests/protocols/test_gamespy3.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.gamespy3 import GameSpy3 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | 9 | test = GameSpy3(host="95.172.92.116", port=29900) 10 | 11 | 12 | @pytest.mark.asyncio 13 | async def test_get_status(): 14 | result = await test.get_status() 15 | await handler.save_result("test_get_status", result) 16 | -------------------------------------------------------------------------------- /tests/protocols/test_gamespy4.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.gamespy4 import GameSpy4 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | 9 | test = GameSpy4(host="play.avengetech.me", port=19132) 10 | 11 | 12 | @pytest.mark.asyncio 13 | async def test_get_status(): 14 | result = await test.get_status() 15 | await handler.save_result("test_get_status", result) 16 | -------------------------------------------------------------------------------- /tests/protocols/test_kaillera.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.kaillera import Kaillera 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | 9 | test = Kaillera(host="112.161.44.113", port=27888) 10 | 11 | 12 | @pytest.mark.asyncio 13 | async def test_get_status(): 14 | result = await test.get_status() 15 | await handler.save_result("test_get_status", result) 16 | 17 | 18 | @pytest.mark.asyncio 19 | async def test_query_master_servers(): 20 | result = await test.query_master_servers() 21 | await handler.save_result("test_query_master_servers", result) 22 | -------------------------------------------------------------------------------- /tests/protocols/test_killingfloor.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.killingfloor import KillingFloor 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | 9 | # Killing Floor 10 | test = KillingFloor(host="normal.ws-gaming.eu", port=7708) 11 | 12 | 13 | @pytest.mark.asyncio 14 | async def test_get_details(): 15 | result = await test.get_details() 16 | await handler.save_result("test_get_details", result) 17 | 18 | 19 | @pytest.mark.asyncio 20 | async def test_get_rules(): 21 | result = await test.get_rules() 22 | await handler.save_result("test_get_rules", result) 23 | 24 | 25 | @pytest.mark.asyncio 26 | async def test_get_players(): 27 | result = await test.get_players() 28 | await handler.save_result("test_get_players", result) 29 | -------------------------------------------------------------------------------- /tests/protocols/test_minecraft.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.minecraft import Minecraft 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | 9 | # Minecraft 10 | test = Minecraft(host="mc.goldcraft.ir", port=25565) 11 | 12 | 13 | @pytest.mark.asyncio 14 | async def test_get_status(): 15 | result = await test.get_status() 16 | await handler.save_result("test_get_status", result) 17 | 18 | 19 | @pytest.mark.asyncio 20 | async def test_get_status_pre17(): 21 | result = await test.get_status_pre17() 22 | await handler.save_result("test_get_status_pre17", result) 23 | -------------------------------------------------------------------------------- /tests/protocols/test_nadeo.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.nadeo import Nadeo 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | 9 | # Test server configuration 10 | SERVER_IP = "192.168.100.2" 11 | SERVER_PORT = 5000 12 | SERVER_USER = "SuperAdmin" 13 | SERVER_PASSWORD = "SuperAdmin" 14 | 15 | nadeo = Nadeo(host=SERVER_IP, port=SERVER_PORT) 16 | 17 | 18 | @pytest.mark.asyncio 19 | async def test_authenticate(): 20 | result = await nadeo.authenticate(SERVER_USER, SERVER_PASSWORD) 21 | assert result is True 22 | 23 | 24 | @pytest.mark.asyncio 25 | async def test_get_status(): 26 | await nadeo.authenticate(SERVER_USER, SERVER_PASSWORD) 27 | result = await nadeo.get_status() 28 | await handler.save_result("test_get_status", result) 29 | 30 | 31 | @pytest.mark.asyncio 32 | async def test_get_map_info(): 33 | await nadeo.authenticate(SERVER_USER, SERVER_PASSWORD) 34 | result = await nadeo.get_status() 35 | print(f"Server: {result.server_options.name}") 36 | print(f"Map: {result.map_info.name}") 37 | print(f"Author: {result.map_info.author}") 38 | print(f"Players: {len(result.players)}/{result.server_options.max_players}") 39 | assert result.map_info.name != "" 40 | assert result.server_options.name != "" -------------------------------------------------------------------------------- /tests/protocols/test_palworld.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.palworld import Palworld 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | 9 | # Palworld 10 | test = Palworld( 11 | host="72.65.106.166", 12 | port=8212, 13 | api_username="admin", 14 | api_password="admin", 15 | ) 16 | 17 | 18 | @pytest.mark.asyncio 19 | async def test_get_status(): 20 | result = await test.get_status() 21 | await handler.save_result("test_get_status", result) 22 | -------------------------------------------------------------------------------- /tests/protocols/test_quake1.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.quake1 import Quake1 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | 9 | # https://www.quakeservers.net/quakeworld/servers/so=8/ 10 | test = Quake1(host="35.185.44.174", port=27500) 11 | 12 | 13 | @pytest.mark.asyncio 14 | async def test_get_status(): 15 | result = await test.get_status() 16 | await handler.save_result("test_get_status", result) 17 | -------------------------------------------------------------------------------- /tests/protocols/test_quake2.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.quake2 import Quake2 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | 9 | test = Quake2(host="46.165.236.118", port=27910) 10 | 11 | 12 | @pytest.mark.asyncio 13 | async def test_get_status(): 14 | result = await test.get_status() 15 | await handler.save_result("test_get_status", result) 16 | -------------------------------------------------------------------------------- /tests/protocols/test_quake3.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.quake3 import Quake3 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | 9 | test = Quake3(host="135.148.137.185", port=27960) 10 | 11 | 12 | @pytest.mark.asyncio 13 | async def test_get_info(): 14 | result = await test.get_info() 15 | await handler.save_result("test_get_info", result) 16 | 17 | 18 | @pytest.mark.asyncio 19 | async def test_get_status(): 20 | result = await test.get_status() 21 | await handler.save_result("test_get_status", result) 22 | -------------------------------------------------------------------------------- /tests/protocols/test_raknet.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.raknet import RakNet 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | 9 | # Raknet 10 | test = RakNet(host="mc.advancius.net", port=19132) 11 | 12 | 13 | @pytest.mark.asyncio 14 | async def test_get_status(): 15 | result = await test.get_status() 16 | await handler.save_result("test_get_status", result) 17 | -------------------------------------------------------------------------------- /tests/protocols/test_renegadex.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.renegadex import RenegadeX 3 | from ..result_handler import ResultHandler 4 | 5 | handler = ResultHandler(__file__) 6 | handler.enable_save = True 7 | 8 | @pytest.mark.asyncio 9 | async def test_renegadex_status(): 10 | rx = RenegadeX(host="10.13.37.149") 11 | result = await rx.get_status() 12 | 13 | print("\nRenegade X Server Details:") 14 | print(f"Server Name: {result.name}") 15 | print(f"Current Map: {result.current_map}") 16 | print(f"Game Version: {result.game_version}") 17 | print(f"Players: {result.players}/{result.variables.player_limit}") 18 | print(f"Port: {result.port}") 19 | 20 | print("\nServer Settings:") 21 | print(f"Vehicle Limit: {result.variables.vehicle_limit}") 22 | print(f"Mine Limit: {result.variables.mine_limit}") 23 | print(f"Time Limit: {result.variables.time_limit}") 24 | print(f"Team Mode: {result.variables.team_mode}") 25 | print(f"Game Type: {result.variables.game_type}") 26 | 27 | print("\nServer Flags:") 28 | print(f"Password Protected: {'Yes' if result.variables.passworded else 'No'}") 29 | print(f"Steam Required: {'Yes' if result.variables.steam_required else 'No'}") 30 | print(f"Spawn Crates: {'Yes' if result.variables.spawn_crates else 'No'}") 31 | print(f"Ranked: {'Yes' if result.variables.ranked else 'No'}") 32 | 33 | await handler.save_result("test_renegadex_status", result) -------------------------------------------------------------------------------- /tests/protocols/test_samp.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.samp import Samp 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | handler.delay_per_test = 1 9 | 10 | # San Andreas Multiplayer 11 | test = Samp(host="51.254.178.238", port=7777) 12 | 13 | 14 | @pytest.mark.asyncio 15 | async def test_get_status(): 16 | result = await test.get_status() 17 | await handler.save_result("test_get_status", result) 18 | 19 | 20 | @pytest.mark.asyncio 21 | async def test_get_players(): 22 | result = await test.get_players() 23 | await handler.save_result("test_get_players", result) 24 | 25 | 26 | @pytest.mark.asyncio 27 | async def test_get_rules(): 28 | result = await test.get_rules() 29 | await handler.save_result("test_get_rules", result) 30 | -------------------------------------------------------------------------------- /tests/protocols/test_satisfactory.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.satisfactory import Satisfactory 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | 9 | # Satisfactory 10 | test = Satisfactory( 11 | host="79.136.0.124", 12 | port=7777, 13 | app_token="ewoJInBsIjogIkFQSVRva2VuIgp9.EE80F05DAFE991AE8850CD4CFA55840D9F41705952A96AF054561ABA3676BE4D4893B162271D3BC0A0CC50797219D2C8E627F0737FC8776F3468EA44B3700EF7", 14 | ) 15 | 16 | 17 | @pytest.mark.asyncio 18 | async def test_get_status(): 19 | result = await test.get_status() 20 | await handler.save_result("test_get_status", result) 21 | -------------------------------------------------------------------------------- /tests/protocols/test_scum.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.scum import Scum 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | 9 | # Scum 10 | test = Scum(host="15.235.181.19", port=7042) 11 | 12 | 13 | @pytest.mark.asyncio 14 | async def test_get_status(): 15 | result = await test.get_status() 16 | await handler.save_result("test_get_status", result) 17 | 18 | 19 | @pytest.mark.asyncio 20 | async def test_query_master_servers(): 21 | result = await test.query_master_servers() 22 | await handler.save_result("test_query_master_servers", result) 23 | -------------------------------------------------------------------------------- /tests/protocols/test_source.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.source import Source 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | handler.delay_per_test = 1 9 | 10 | # tf2 11 | source = Source(host="45.62.160.71", port=27015) 12 | 13 | 14 | @pytest.mark.asyncio 15 | async def test_get_info(): 16 | result = await source.get_info() 17 | await handler.save_result("test_get_info", result) 18 | 19 | 20 | @pytest.mark.asyncio 21 | async def test_get_players(): 22 | result = await source.get_players() 23 | await handler.save_result("test_get_players", result) 24 | 25 | 26 | @pytest.mark.asyncio 27 | async def test_get_rules(): 28 | result = await source.get_rules() 29 | await handler.save_result("test_get_rules", result) 30 | 31 | 32 | @pytest.mark.asyncio 33 | async def test_remote_console(): 34 | return 35 | 36 | with Source.RemoteConsole("", 27015) as rcon: 37 | await rcon.authenticate("") 38 | result = await rcon.send_command("cvarlist") 39 | await handler.save_result("test_remote_console", result, is_json=False) 40 | -------------------------------------------------------------------------------- /tests/protocols/test_teamspeak3.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.teamspeak3 import TeamSpeak3 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | 9 | # TeamSpeak 3 10 | test = TeamSpeak3(host="145.239.200.2", port=10011, voice_port=9987) 11 | 12 | 13 | @pytest.mark.asyncio 14 | async def test_get_info(): 15 | result = await test.get_info() 16 | await handler.save_result("test_get_info", result) 17 | 18 | 19 | @pytest.mark.asyncio 20 | async def test_get_clients(): 21 | result = await test.get_clients() 22 | await handler.save_result("test_get_clients", result) 23 | 24 | 25 | @pytest.mark.asyncio 26 | async def test_get_channels(): 27 | result = await test.get_channels() 28 | await handler.save_result("test_get_channels", result) 29 | -------------------------------------------------------------------------------- /tests/protocols/test_toxikk.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.toxikk import Toxikk 3 | from ..result_handler import ResultHandler 4 | 5 | handler = ResultHandler(__file__) 6 | 7 | @pytest.mark.asyncio 8 | async def test_toxikk_status(): 9 | toxikk = Toxikk(host="10.13.37.149", port=14001) 10 | result = await toxikk.get_status() 11 | 12 | print("\nToxikk Server Details:") 13 | print(f"Server Name: {result.name}") 14 | print(f"Map: {result.map}") 15 | print(f"Game Type: {result.raw.get('gametype')}") 16 | print(f"Players: {result.num_players}/{result.max_players}") 17 | print(f"Time Limit: {result.raw.get('time_limit')} minutes") 18 | print(f"Score Limit: {result.raw.get('frag_limit')} frags") 19 | 20 | print("\nBot Settings:") 21 | print(f"Bot Count: {result.raw.get('numbots')}") 22 | print(f"Bot Skill: {result.raw.get('bot_skill')}") 23 | print(f"VS Bots Mode: {result.raw.get('vs_bots')}") 24 | 25 | print("\nServer Settings:") 26 | print(f"Password Protected: {'Yes' if result.password_protected else 'No'}") 27 | print(f"LAN Mode: {'Yes' if result.lan_mode else 'No'}") 28 | print(f"Stats Enabled: {'Yes' if result.stats_enabled else 'No'}") 29 | 30 | print("\nMutators:") 31 | mutators = result.raw.get('mutators', []) 32 | print(", ".join(mutators) if mutators else "None") 33 | 34 | await handler.save_result("test_toxikk_status", result) -------------------------------------------------------------------------------- /tests/protocols/test_unreal2.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.unreal2 import Unreal2 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | 9 | # Killing Floor 10 | # test = Unreal2(host="109.230.224.189", port=6970) 11 | test = Unreal2(host="80.4.151.145", port=7778) 12 | 13 | @pytest.mark.asyncio 14 | async def test_get_details(): 15 | result = await test.get_details() 16 | await handler.save_result("test_get_details", result) 17 | 18 | 19 | @pytest.mark.asyncio 20 | async def test_get_rules(): 21 | result = await test.get_rules() 22 | await handler.save_result("test_get_rules", result) 23 | 24 | 25 | @pytest.mark.asyncio 26 | async def test_get_players(): 27 | result = await test.get_players() 28 | await handler.save_result("test_get_players", result) 29 | -------------------------------------------------------------------------------- /tests/protocols/test_ut3.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.ut3 import UT3 3 | from ..result_handler import ResultHandler 4 | 5 | handler = ResultHandler(__file__) 6 | #handler.enable_save = True 7 | 8 | @pytest.mark.asyncio 9 | async def test_ut3_status(): 10 | ut3 = UT3(host="10.13.36.1", port=14001) 11 | result = await ut3.get_status() 12 | 13 | print("\nUT3 Server Details:") 14 | print(f"Server Name: {result.name}") 15 | print(f"Map: {result.map}") 16 | print(f"Game Type: {result.game_type}") 17 | print(f"Game Mode: {result.raw.get('gamemode')}") 18 | print(f"Players: {result.num_players}/{result.max_players}") 19 | print(f"Time Limit: {result.raw.get('time_limit')} minutes") 20 | print(f"Score Limit: {result.raw.get('frag_limit')} frags") 21 | 22 | print("\nBot Settings:") 23 | print(f"Bot Count: {result.raw.get('numbots')}") 24 | print(f"Bot Skill: {result.raw.get('bot_skill')}") 25 | print(f"VS Bots Mode: {result.raw.get('vs_bots')}") 26 | 27 | print("\nServer Settings:") 28 | print(f"Password Protected: {'Yes' if result.password_protected else 'No'}") 29 | print(f"LAN Mode: {'Yes' if result.lan_mode else 'No'}") 30 | print(f"Stats Enabled: {'Yes' if result.stats_enabled else 'No'}") 31 | 32 | print("\nMutators:") 33 | if 'stock_mutators' in result.raw: 34 | stock = result.raw['stock_mutators'] 35 | print("Stock:", ", ".join(stock) if stock else "None") 36 | if 'custom_mutators' in result.raw: 37 | custom = result.raw['custom_mutators'] 38 | print("Custom:", ", ".join(custom) if custom else "None") 39 | 40 | await handler.save_result("test_ut3_status", result) -------------------------------------------------------------------------------- /tests/protocols/test_vcmp.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.vcmp import Vcmp 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | 9 | # Vice City Multiplayer 10 | test = Vcmp(host="51.178.65.136", port=8114) 11 | 12 | 13 | @pytest.mark.asyncio 14 | async def test_get_status(): 15 | result = await test.get_status() 16 | await handler.save_result("test_get_status", result) 17 | 18 | 19 | @pytest.mark.asyncio 20 | async def test_get_players(): 21 | result = await test.get_players() 22 | await handler.save_result("test_get_players", result) 23 | -------------------------------------------------------------------------------- /tests/protocols/test_warcraft3.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.warcraft3 import Warcraft3 3 | from ..result_handler import ResultHandler 4 | 5 | handler = ResultHandler(__file__) 6 | handler.enable_save = True 7 | 8 | @pytest.mark.asyncio 9 | async def test_warcraft3_status(): 10 | warcraft3 = Warcraft3(host="10.10.101.4", port=6112) # Replace with your test server 11 | result = await warcraft3.get_status() 12 | 13 | print("\nWarcraft 3 Server Details:") 14 | print(f"Server Name: {result.hostname}") 15 | print(f"Game Version: {result.game_version}") 16 | print(f"Map: {result.map_name}") 17 | print(f"Game Type: {result.game_type}") 18 | print(f"Players: {result.num_players}/{result.max_players}") 19 | 20 | # Example raw data that might be useful for debugging 21 | print("\nRaw Data:") 22 | if hasattr(result, 'raw'): 23 | for key, value in result.raw.items(): 24 | print(f"{key}: {value}") 25 | 26 | await handler.save_result("test_warcraft3_status", result) -------------------------------------------------------------------------------- /tests/protocols/test_won.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.protocols.won import WON 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | 9 | # Counter-Strike 1.5 10 | won = WON(host="212.227.190.150", port=27020) 11 | 12 | 13 | @pytest.mark.asyncio 14 | async def test_get_info(): 15 | result = await won.get_info() 16 | await handler.save_result("test_get_info", result) 17 | 18 | 19 | @pytest.mark.asyncio 20 | async def test_get_players(): 21 | result = await won.get_players() 22 | await handler.save_result("test_get_players", result) 23 | 24 | 25 | @pytest.mark.asyncio 26 | async def test_get_rules(): 27 | result = await won.get_rules() 28 | await handler.save_result("test_get_rules", result) 29 | -------------------------------------------------------------------------------- /tests/rcon_protocols/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opengsq/opengsq-python/b34071f3822bd7e22e47dc9b696fe2a4f6828ff9/tests/rcon_protocols/__init__.py -------------------------------------------------------------------------------- /tests/rcon_protocols/test_source_rcon.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from opengsq.rcon_protocols.source_rcon import SourceRcon 3 | 4 | from ..result_handler import ResultHandler 5 | 6 | handler = ResultHandler(__file__) 7 | # handler.enable_save = True 8 | 9 | 10 | @pytest.mark.asyncio 11 | async def test_authenticate(): 12 | return 13 | 14 | with SourceRcon("", 27015) as rcon: 15 | await rcon.authenticate("") 16 | response = await rcon.send_command("cvarlist") 17 | await handler.save_result("test_authenticate", response, is_json=False) 18 | -------------------------------------------------------------------------------- /tests/result_handler.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | import os 4 | from pathlib import Path 5 | 6 | 7 | class ResultHandler: 8 | enable_save = False 9 | delay_per_test = 0 10 | 11 | def __init__(self, pathname: str): 12 | "ResultHandler" 13 | 14 | self.file_name = os.path.splitext(os.path.basename(pathname))[ 15 | 0 16 | ] # Returns: 'test_ase' 17 | self.last_dir = os.path.basename( 18 | os.path.dirname(pathname) 19 | ) # Returns: 'protocols' 20 | 21 | # docs/tests/protocols/ 22 | self.results_path = os.path.realpath( 23 | os.path.join( 24 | os.path.dirname(__file__), "..", "docs", "tests", self.last_dir 25 | ) 26 | ) 27 | Path(self.results_path).mkdir(exist_ok=True) 28 | 29 | # docs/tests/protocols/test_ase/ 30 | self.__protocol_path = os.path.join(self.results_path, self.file_name) 31 | Path(self.__protocol_path).mkdir(exist_ok=True) 32 | 33 | self.create_tests_index_rst() 34 | 35 | async def save_result(self, function_name, result, is_json=True): 36 | "Save and print the result" 37 | 38 | if self.enable_save: 39 | if is_json: 40 | result = json.dumps( 41 | result, indent=4, ensure_ascii=False, default=lambda o: o.__dict__ 42 | ) 43 | 44 | # with open(os.path.join(self.__protocol_path, f'{function_name}.{(is_json and "json" or "txt")}'), 'w', encoding='utf-8') as f: 45 | # print(result, file=f) 46 | 47 | with open( 48 | os.path.join(self.__protocol_path, f"{function_name}.rst"), 49 | "w", 50 | encoding="utf-8", 51 | ) as f: 52 | title = function_name 53 | f.write(title + "\n") 54 | f.write(("=" * len(title)) + "\n") 55 | f.write("\nHere are the results for the test method.\n") 56 | f.write(f'\n.. code-block:: {(is_json and "json" or "text")}\n\n') 57 | 58 | for line in result.splitlines(): 59 | f.write("\t" + line + "\n") 60 | 61 | self.create_tests_protocols_index_rst() 62 | 63 | await asyncio.sleep(self.delay_per_test) 64 | 65 | def create_tests_index_rst(self): 66 | test_files = Path(os.path.join(os.path.dirname(__file__), self.last_dir)).glob("test_*.py") 67 | 68 | with open( 69 | os.path.join(self.results_path, "index.rst"), "w", encoding="utf-8" 70 | ) as f: 71 | title = self.last_dir.title().replace("_", " ") + ' Tests' 72 | f.write(f".. _{self.last_dir}_tests:\n") 73 | f.write(f"\n{title}\n") 74 | f.write(f'{"=" * len(title)}\n') 75 | f.write("\n.. toctree::\n") 76 | 77 | for file in test_files: 78 | f.write(f"\t{file.name[:-3]}/index\n") 79 | 80 | def create_tests_protocols_index_rst(self): 81 | test_results_files = Path(self.__protocol_path).glob("test_*.rst") 82 | 83 | with open( 84 | os.path.join(self.__protocol_path, "index.rst"), "w", encoding="utf-8" 85 | ) as f: 86 | f.write(f".. _{self.file_name}:\n") 87 | f.write(f"\n{self.file_name}\n") 88 | f.write(f'{"=" * len(self.file_name)}\n') 89 | f.write("\n.. toctree::\n") 90 | 91 | for file in test_results_files: 92 | f.write(f"\t{file.name[:-4]}\n") 93 | --------------------------------------------------------------------------------