├── .env.example ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── python-example-tests.yml │ ├── python-flake8-lint.yml │ ├── python-publish.yml │ └── python-tests.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── Pipfile ├── Pipfile.lock ├── README.md ├── docs ├── Makefile ├── _async │ └── api.rst ├── _static │ └── css │ │ └── custom.css ├── api.rst ├── conf.py ├── exceptions.rst ├── extensions │ └── resourcelinks.py ├── faq.rst ├── index.rst ├── make.bat ├── quickstart.md ├── requirements.txt └── whats_new.md ├── examples ├── async-player.py ├── get-guild-data.py ├── get-player-data.py ├── get-watchdog-stats.py ├── user-to-uuid.py └── uuid-to-username.py ├── hypixelio ├── __init__.py ├── _async │ ├── __init__.py │ ├── client.py │ ├── converters.py │ └── utils.py ├── base.py ├── constants.py ├── endpoints.py ├── exceptions.py ├── lib │ ├── __init__.py │ ├── client.py │ ├── converters.py │ └── utils.py ├── models │ ├── __init__.py │ ├── boosters.py │ ├── find_guild.py │ ├── friends.py │ ├── games.py │ ├── guild.py │ ├── key.py │ ├── leaderboard.py │ ├── player.py │ ├── player_status.py │ ├── recent_games.py │ ├── skyblock │ │ ├── __init__.py │ │ ├── active_auctions │ │ │ ├── __init__.py │ │ │ └── active_auctions.py │ │ ├── auction.py │ │ ├── bazaar │ │ │ ├── __init__.py │ │ │ ├── bazaar_item.py │ │ │ └── skyblock_bazaar.py │ │ ├── news.py │ │ ├── profile │ │ │ ├── __init__.py │ │ │ ├── profile.py │ │ │ └── profile_member.py │ │ └── user_auction │ │ │ ├── __init__.py │ │ │ └── user_auction.py │ └── watchdog.py ├── py.typed └── utils.py ├── pyproject.toml ├── pyrightconfig.json ├── setup.py ├── tests ├── __init__.py ├── mock_data │ └── player_data.py ├── test_converters.py └── test_player.py └── tox.ini /.env.example: -------------------------------------------------------------------------------- 1 | HYPIXEL_KEY="" 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report issues and bugs with the template to help us fix and improve for everyone 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | ## Describe the bug 10 | 11 | A clear and concise description of what the bug is. 12 | 13 | ## Steps to reproduce 14 | 15 | Steps to reproduce the behavior: 16 | 17 | 1. Go to '...' 18 | 2. Click on '...' 19 | 3. Scroll down to '...' 20 | 4. See error 21 | 22 | ## Expected behaviour 23 | 24 | A clear and concise description of what you expected to happen. 25 | 26 | ## Screenshots 27 | 28 | If applicable, add screenshots to help explain your problem. 29 | 30 | ## Platform info 31 | 32 | ### Desktop (please complete the following information): 33 | 34 | - OS: [e.g. iOS] 35 | - Browser [e.g. chrome, safari] 36 | - Version [e.g. 22] 37 | 38 | ### Software info 39 | 40 | - Python version: [e.g. 3.8] 41 | 42 |
43 | 44 | Pipfile info 45 | 46 |

47 | 48 | ```toml 49 | # Pipfile contents here. 50 | ``` 51 | 52 |

53 |
54 | 55 |
56 | 57 | Output log 58 | 59 |

60 | 61 | ```log 62 | [Log output here] 63 | ``` 64 | 65 |

66 |
67 | 68 | ## Additional context 69 | 70 | Add any other context about the problem here. 71 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | ## Is your feature request related to a problem? Please describe. 10 | 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | ## Describe the solution you would like 14 | 15 | A clear and concise description of what you want to happen. 16 | 17 | ## Describe alternatives you've considered 18 | 19 | A clear and concise description of any alternative solutions or features you've considered. 20 | 21 | ## Additional context 22 | 23 | Add any other context or screenshots about the feature request here. 24 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: pip 5 | directory: "/" 6 | schedule: 7 | interval: daily 8 | 9 | - package-ecosystem: pip 10 | directory: "/docs" 11 | schedule: 12 | interval: daily 13 | 14 | - package-ecosystem: github-actions 15 | directory: "/" 16 | schedule: 17 | interval: daily 18 | -------------------------------------------------------------------------------- /.github/workflows/python-example-tests.yml: -------------------------------------------------------------------------------- 1 | name: Test Examples 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | test_examples: 11 | name: Test Examples 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v3 17 | 18 | - name: Set up Python 19 | id: python 20 | uses: actions/setup-python@v4.2.0 21 | with: 22 | python-version: "3.9" 23 | 24 | - name: Install dependencies 25 | run: | 26 | python -m pip install --upgrade pip 27 | pip install pipenv 28 | pipenv sync -d 29 | 30 | - name: Running examples as tests 31 | env: 32 | HYPIXEL_KEY: ${{ secrets.HYPIXEL_KEY }} 33 | run: | 34 | pipenv run python -m examples.async-player 35 | pipenv run python -m examples.get-guild-data 36 | pipenv run python -m examples.get-player-data 37 | pipenv run python -m examples.get-watchdog-stats 38 | pipenv run python -m examples.user-to-uuid 39 | pipenv run python -m examples.uuid-to-username 40 | -------------------------------------------------------------------------------- /.github/workflows/python-flake8-lint.yml: -------------------------------------------------------------------------------- 1 | name: Flake8 Linting 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | lint: 11 | name: Flake8 Linting 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v3 17 | 18 | - name: Set up Python 19 | id: python 20 | uses: actions/setup-python@v4.2.0 21 | with: 22 | python-version: "3.9" 23 | 24 | - name: Install dependencies 25 | run: | 26 | python -m pip install --upgrade pip 27 | pip install pipenv 28 | pipenv sync -d 29 | 30 | - name: Lint with flake8 31 | run: | 32 | pipenv run lint 33 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | name: Upload Python Package 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | deploy: 9 | name: Upload Python package 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@v3 15 | 16 | - name: Set up Python 17 | id: python 18 | uses: actions/setup-python@v4.2.0 19 | with: 20 | python-version: "3.x" 21 | 22 | - name: Install dependencies 23 | run: | 24 | python -m pip install --upgrade pip 25 | pip install pipenv setuptools wheel twine 26 | pipenv sync 27 | 28 | - name: Build 29 | run: | 30 | python setup.py sdist bdist_wheel 31 | 32 | - name: Check Build 33 | run: | 34 | twine check dist/* 35 | 36 | - name: Publish to Test PYPI 37 | if: github.event_name == 'release' || startsWith(github.event.ref, 'refs/tags') 38 | uses: pypa/gh-action-pypi-publish@master 39 | with: 40 | user: __token__ 41 | password: ${{ secrets.PYPI_TEST_TOKEN }} 42 | repository_url: https://test.pypi.org/legacy/ 43 | 44 | - name: Publish to PYPI 📦 45 | if: github.event_name == 'release' || startsWith(github.event.ref, 'refs/tags') 46 | uses: pypa/gh-action-pypi-publish@master 47 | with: 48 | user: __token__ 49 | password: ${{ secrets.PYPI_TOKEN }} 50 | -------------------------------------------------------------------------------- /.github/workflows/python-tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | test: 11 | name: Run tests 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v3 17 | 18 | - name: Set up Python 19 | id: python 20 | uses: actions/setup-python@v4.2.0 21 | with: 22 | python-version: "3.9" 23 | 24 | - name: Install dependencies 25 | run: | 26 | python -m pip install --upgrade pip 27 | pip install pipenv 28 | pipenv sync -d 29 | 30 | - name: Running tests 31 | env: 32 | HYPIXEL_KEY: ${{ secrets.HYPIXEL_KEY }} 33 | run: | 34 | pipenv run tests 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python cache 2 | __pycache__/ 3 | 4 | # Editor generated files 5 | .idea/ 6 | .vscode/ 7 | .spyproject/ 8 | 9 | # VirtualENV files 10 | env/ 11 | venv/ 12 | .venv/ 13 | 14 | # Environmental and personal files 15 | *.env 16 | TODO 17 | 18 | # Database files 19 | *.sqlite3 20 | *.sqlite 21 | *.sql 22 | *.db 23 | 24 | # Ignore personal log files 25 | logs/ 26 | 27 | # MacOS generated file 28 | .DS_STORE 29 | 30 | # Binary files generated 31 | *.so 32 | *.dll 33 | *.dylib 34 | 35 | # Testing and benchmarks 36 | .pytest_cache/ 37 | .benchmarks/ 38 | 39 | # Build 40 | docs/build 41 | build/ 42 | dist/ 43 | HypixelIO.egg-info/ 44 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.3.0 4 | hooks: 5 | - id: check-merge-conflict 6 | - id: check-toml 7 | - id: check-yaml 8 | - id: end-of-file-fixer 9 | - id: trailing-whitespace 10 | args: [--markdown-linebreak-ext=md] 11 | - id: mixed-line-ending 12 | args: [--fix=lf] 13 | 14 | - repo: https://github.com/pre-commit/pygrep-hooks 15 | rev: v1.9.0 16 | hooks: 17 | - id: python-check-blanket-noqa 18 | - id: python-use-type-annotations 19 | 20 | - repo: local 21 | hooks: 22 | - id: flake8 23 | name: Flake8 24 | description: Run flake8 checks on the code 25 | entry: pipenv run flake8 26 | language: system 27 | types: [python] 28 | 29 | - repo: local 30 | hooks: 31 | - id: pyright 32 | name: Pyright 33 | description: Run pyright type checker 34 | entry: pipenv run pyright 35 | language: system 36 | types: [python] 37 | pass_filenames: false 38 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | image: latest 5 | 6 | # Build documentation in the docs/ directory with Sphinx 7 | sphinx: 8 | configuration: docs/conf.py 9 | fail_on_warning: false 10 | builder: html 11 | 12 | # Optionally build your docs in additional formats such as PDF 13 | formats: 14 | - htmlzip 15 | - pdf 16 | 17 | # Optionally set the version of Python and requirements required to build your docs 18 | python: 19 | version: 3.8 20 | install: 21 | - requirements: docs/requirements.txt 22 | - method: setuptools 23 | path: . 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [1.4.0](https://github.com/janaSunrise/HypixelIO/releases/tag/v1.4.0) - 02-03-2021 9 | 10 | - Rewrite the Exceptions API in a cleaner way. 11 | - Convert all the variable names to lowercase in the models. 12 | - Remove caching from the library, useless complexity. 13 | - Fix examples and tests. 14 | - Import modules directly instead of namespacing them (Saves lookup time.) 15 | - Add news, skyblock skills and skyblock collection endpoints. 16 | 17 | ## [1.3.0](https://github.com/janaSunrise/HypixelIO/releases/tag/v1.3.0) - 23-07-2021 18 | 19 | ## Added 20 | 21 | - Base class client to inherit in `Client` and `AsyncClient` to keep code dry and easy to refer and read. 22 | 23 | ## Fixes 24 | 25 | - Fixed vulnerable dependency versions 26 | - Switched to relative imports 27 | 28 | ## Removed 29 | 30 | - Removed the redundant `ext` package and replaced `asyncio` package with simple `_async` since, There would 31 | probably be no external packages anymore, As far I know. 32 | 33 | ## [1.2.10](https://github.com/janaSunrise/HypixelIO/releases/tag/v1.2.10) - 27-05-2021 34 | 35 | ## Fixes 36 | 37 | - Fix import errors when not using Caching, By installing caching modules without backend 38 | to prevent bloat. 39 | - Fix the caching issues in the library internally and fix them. 40 | 41 | ## [1.2.9](https://github.com/janaSunrise/HypixelIO/releases/tag/v1.2.9) - 27-05-2021 42 | 43 | ## Fixes 44 | 45 | - Fixed several tiny bugs 46 | - Organized codebase 47 | - Fixed the warning and function return types. 48 | - Fixed a lot of vulnerable dependency versions. 49 | 50 | ## [1.2.8](https://github.com/janaSunrise/HypixelIO/releases/tag/v1.2.8) - 12-04-2021 51 | 52 | ## Fixes 53 | 54 | - Made the caching API compatible with higher API for libraries, and change the dependency 55 | versions to the latest for security patches. 56 | - Improve the caching experience 57 | - Fix the dependencies. 58 | - Remove outdated things. 59 | - Add a note on invoking the cache model functions. 60 | 61 | ## [1.2.7](https://github.com/janaSunrise/HypixelIO/releases/tag/v1.2.7) - 25-03-2021 62 | 63 | ## Added 64 | 65 | - Stable caching for async 66 | - More examples! 67 | - Automated caching based session fetch in the client. 68 | 69 | ## [1.2.6](https://github.com/janaSunrise/HypixelIO/releases/tag/v1.2.6) - 24-03-2021 70 | 71 | ## Fixed 72 | 73 | - Async caching removed due to bugs. 74 | - Variables exposed and bugs not allowing fetching in async. 75 | - Cleaned async client. 76 | 77 | ## [1.2.5](https://github.com/janaSunrise/HypixelIO/releases/tag/v1.2.5) - 23-03-2021 78 | 79 | ## Added 80 | 81 | - Caching for async code 82 | - Conversion of unix time into datetime 83 | - Ability to manipulate async cache 84 | - Ability to manipulate keys in the class 85 | 86 | ## Fixed 87 | 88 | - Hide the sensitive variables to fix security issues and apply security patches. 89 | 90 | ## [1.2.4](https://github.com/janaSunrise/HypixelIO/releases/tag/v1.2.4) - 16-03-2021 91 | 92 | ## Added 93 | 94 | - Support for `*` imports, you can now do this: `from hypixelio import *`! 95 | - Made the documentation better, and changed the theme. 96 | - Tweaked and fixed things here and there. 97 | 98 | ## Fixed 99 | 100 | - Async caching removed due to bugginess 101 | - Variables exposed and bugs not allowing fetching in async. 102 | - Cleaned async client. 103 | 104 | ## [1.2.3](https://github.com/janaSunrise/HypixelIO/releases/tag/v1.2.3) - 11-03-2021 105 | 106 | ## Added 107 | 108 | - Write more docs 109 | - Add more model fields 110 | - Add modular models 111 | - Add ratelimiting features. 112 | - Add more utility methods. 113 | - Add resources endpoints 114 | 115 | ## [1.2.2](https://github.com/janaSunrise/HypixelIO/releases/tag/v1.2.2) - 11-03-2021 116 | 117 | ## Added 118 | 119 | - A portal for async to sync with threading. 120 | 121 | ## Fixed 122 | 123 | - Fixed documentation. 124 | - Added asyncio locking to preserve the async coroutines running at same time. 125 | 126 | ## [1.2.1](https://github.com/janaSunrise/HypixelIO/releases/tag/v1.2.1) - 09-03-2021 127 | 128 | ## Fixed 129 | 130 | - The Changelog 131 | - The README 132 | - The documentation looks and content. 133 | 134 | ## [1.2.0](https://github.com/janaSunrise/HypixelIO/releases/tag/v1.2.0) - 09-03-2021 135 | 136 | ## Added 137 | 138 | - Skyblock models 139 | - Skyblock endpoints 140 | - Added HTML documentation 141 | - More utility functions and support to make it easier and reliable. 142 | - Asynchronous support for discord bot devs and other people to keep it non-blocking. 143 | 144 | ## Fixed 145 | 146 | - Rewrote most of the code base. 147 | - Rewrite of the inline documentation. 148 | - Pass API Key as `API-Key` header so it's secure and follows the standards. 149 | - Refactored the methods to get better support and converted into multiple methods so its cleaner. 150 | - Fixed the tests and the errors / exceptions raised when issues arrive. 151 | - Make the rest of the codebase usable by the endpoint user, if needed. Not compulsary. 152 | 153 | ## [1.1.1](https://github.com/janaSunrise/HypixelIO/releases/tag/v1.1.1) - 29-12-2020 154 | 155 | ## Added 156 | 157 | - Various skin fetch features. 158 | - More utility functions for public usage 159 | 160 | ## Fixed 161 | 162 | - Invalid data accepted error. 163 | - Data not being cached a lot in memory. 164 | - Better performance and integration. 165 | 166 | ## [1.1.0](https://github.com/janaSunrise/HypixelIO/releases/tag/v1.1.0) - 24-11-2020 167 | 168 | ### Added 169 | 170 | - Various Examples in the source repo 171 | - Added options for inheriting the the base classes, and add or override features. 172 | 173 | ### Changes: 174 | 175 | - Changes various types 176 | - Improved the internal docstrings 177 | - Removed `constants.py` as a source for the global version 178 | - Added Various caching options, and Changed the `CacheBackend` to a dataclass 179 | - Rewrote each function and directives 180 | - High bug fixes, and fixed security issues 181 | 182 | ### Removed 183 | 184 | - Removed the useless code which took over more space 185 | 186 | ## [0.0.7](https://github.com/janaSunrise/HypixelIO/releases/tag/v0.0.7) - 13-11-2020 187 | 188 | ## Changes 189 | 190 | - Change `tuple` instances to `typing.Union` 191 | - Link requirements.txt dependencies to `setup.py` 192 | - Remove all the build files of the dependencies, and documentations. 193 | 194 | ## [0.0.6](https://github.com/janaSunrise/HypixelIO/releases/tag/v0.0.6) - 13-11-2020 195 | 196 | ### Added 197 | 198 | - Added attributes like `__author__` and `__version__` 199 | - Added Manifest 200 | - Added support for Comparisons like 201 | - `obj1 == obj2` 202 | - `obj1 > obj2` 203 | - `obj1 >= obj2` 204 | 205 | ## [0.0.5](https://github.com/janaSunrise/HypixelIO/releases/tag/v0.0.5) - 04-11-2020 206 | 207 | ### Added 208 | 209 | - Modular caching of requests, 210 | - Added timeout for the cache and fetch 211 | - Allow users to specify the caching according to needs, and various functions to make experience better 212 | - Functions allowed: 213 | - Clearing Cache 214 | - Uninstall cache, If enabled 215 | - removing expired cache 216 | - Get the existsing cache, and check it. 217 | 218 | ## [0.0.4](https://github.com/janaSunrise/HypixelIO/releases/tag/v0.0.4) - 31-10-2020 219 | 220 | ### Added 221 | 222 | - The `__repr__` and `__str__` for some methods 223 | - Added `find_guild` to the client functions 224 | - New model: `FindGuild` containing the ID during finding. 225 | - Added Converter of `uuid_to_username` 226 | 227 | ### Changed 228 | 229 | - Fix The `__repr__` and `__str__` for some classes 230 | - Converted all the main usage files into a different library package again 231 | - Split and cleaned the things. 232 | 233 | ## [0.0.3](https://github.com/janaSunrise/HypixelIO/releases/tag/v0.0.3) - 23-10-2020 234 | 235 | ### Added 236 | 237 | - The `__repr__` and `__str__` for some methods 238 | - Added `Leaderboard` and `GameCount` Stats 239 | - Added Caching to the requests 240 | - Added Converter of `username_to_uuid` 241 | - Added New exception: `MojangAPIError` 242 | 243 | ### Changed 244 | 245 | - The class variables to global constants 246 | - Stored all the constants in a single unique file 247 | - Converted all the main usage files into a different library package 248 | 249 | ## [0.0.2](https://github.com/janaSunrise/HypixelIO/releases/tag/v0.0.2) - 22-10-2020 250 | 251 | ### Added 252 | 253 | - Fix the API Key error 254 | - Fix the API_Error raised on when the `get_key_info()` is utilized. 255 | - Fix the Boosters bug error due to small typos 256 | - Fix the Self argument error, when the function was moved out of the class, to be a utility function. 257 | 258 | ## Changed 259 | 260 | - Change Async and Coroutines and make them Synchronous 261 | 262 | ## Removed 263 | 264 | - Async features 265 | 266 | ## [0.0.1](https://github.com/janaSunrise/HypixelIO/releases/tag/v0.0.1) - 21-10-2020 267 | 268 | ### Added 269 | 270 | - Use the Code with multithreading, for Many process tasks. 271 | - Have several Search features. 272 | - Supports valid authentication 273 | - Basic, Important documentations added, Well explained, and better than other libraries. 274 | 275 | ### Changed 276 | 277 | - Change the Code style 278 | - Refactor things 279 | - Remove useless whitespaces 280 | - Change a Typo of `friends.Friend` to `friends.Friends` 281 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at warriordefenderz@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 40 | 41 | ## Linting and Pre-commit 42 | 43 | We make use of `flake8` and `pre-commit` to ensure that the code style is consistent across the code base. 44 | 45 | Running `flake8` will warn you about any potential style errors in your contribution. You must always check it before pushing. Your commit will be rejected by the build server if it fails to lint. 46 | 47 | `pre-commit` is a powerful tool that helps you automatically lint before you commit. If the linter complains, 48 | the commit is aborted so that you can fix the linting errors before committing again. That way, you never commit 49 | the problematic code in the first place! 50 | 51 | To make linting and checking easy, we have setup pipenv scripts to faciliate installing the commit hooks, and 52 | running the lint checks. 53 | 54 | Here is how you can setup pre-commit with ease: 55 | 56 | 1. Ensure that you have dependencies (and dev-dependencies) installed using pipenv. 57 | 2. Once you're ready, run `pipenv run precommit`, which install the precommit hooks to check your code style 58 | when you're commiting it. It stops code from getting commited, if issues are discovered. 59 | 3. Finally, To run the linting manually, just use `pipenv run lint`, and you should be good to go. 60 | 61 | **Note**: If you really need to commit code, and fix the issues or take assistance, run `git commit --no-verify` 62 | to skips the precommit checks. 63 | 64 | ## Style Guide 65 | 66 | We have enforced several rules related to the code-style which we are following. They have been added 67 | to ensure readable, clean and maintainable code is written, and enable working for everyone smooth and easy. 68 | 69 | We use `flake8` to perform linting, and `pre-commit` to ensure the code is perfect, before getting pushed. 70 | 71 | ### Type Hinting 72 | 73 | [PEP 484](https://www.python.org/dev/peps/pep-0484/) formally specifies type hints for Python functions, added 74 | to the Python Standard Library in version 3.5. Type hints are recognized by most modern code editing tools and 75 | provide useful insight into both the input and output types of a function, preventing the user from having to 76 | go through the codebase to determine these types. 77 | 78 | For an example, a function without annotations would look like: 79 | 80 | ```py 81 | def divide(a, b): 82 | """Divide the two given arguments.""" 83 | return a / b 84 | ``` 85 | 86 | With annotations, this is how the function would look: 87 | 88 | ```py 89 | def divide(a: int, b: int) -> float: 90 | """Divide the two given arguments.""" 91 | return a / b 92 | ``` 93 | 94 | Python type-hinting is relatively easy to use, but helps to keep the code more maintainable, clean, and also 95 | enables the use of type annotations in the future. In a lot of situations, the type-hinting enables your editors 96 | to give better intellisense and suggestions based on what you're working on. 97 | 98 | Python being a dynamically typed language, There is a neat tool called MyPy that enables static type hinting 99 | and checking on the code. You can read more about it [here](https://mypy.readthedocs.io/en/stable/). 100 | 101 | ### Docstring formatting directive 102 | 103 | Many documentation packages provide support for automatic documentation generation from the codebase's docstrings. 104 | These tools utilize special formatting directives to enable richer formatting in the generated documentation. 105 | 106 | For example: 107 | 108 | ```py 109 | import typing as t 110 | 111 | 112 | def foo(bar: int, baz: t.Optional[t.Dict[str, str]] = None) -> bool: 113 | """ 114 | Does some things with some stuff. 115 | 116 | :param bar: Some input 117 | :param baz: Optional, some dictionary with string keys and values 118 | 119 | :return: Some boolean 120 | """ 121 | ... 122 | ``` 123 | 124 | Since we don't utilize automatic documentation generation, use of this syntax should not be used in the code contributed here. 125 | 126 | Should the purpose and type of the input variables not be easily discernable from the variable name and type 127 | annotation a prose explanation can be used. Explicit references to variables, function, classes, etc. should be 128 | wrapped with backticks (`` ` ``), such as \`variable\`. 129 | 130 | For example, the above docstring would become: 131 | 132 | ```py 133 | import typing as t 134 | 135 | 136 | def foo(bar: int, baz: t.Optional[t.Dict[str, str]] = None) -> bool: 137 | """ 138 | Does some things with some stuff. 139 | 140 | This function takes an index, `bar` and checks for its presence in the database `baz`, passed as a dictionary. 141 | Returns `False` if `baz` is not passed or if `bar` wasn't found in `baz`. 142 | """ 143 | ... 144 | ``` 145 | 146 | ### Strings and Quotes 147 | 148 | Preference is to use double-quotes (`"`) wherever possible. Single quotes should only be used for cases where it is 149 | logical. Exceptions might include: 150 | 151 | - Using a key string within an f-string: f"Today is {data['day']}". 152 | - Using double quotes within a string: 'She said "oh dear" in response' 153 | 154 | Multi-line strings or Docstrings should be ensured that it's wrapped in triple double quotes (`"""my string"""`). 155 | 156 | Wildcard imports should be avoided. 157 | 158 | ### Work in Progress (WIP) PRs 159 | 160 | When any PR is actively being worked on, and is not ready for merging, it should be marked as a WIP. This provides 161 | both a visual and functional indicator that the PR is in a draft state, and is not ready for review or merge. 162 | 163 | Github provides a feature of marking a PR as Draft to indicate that it is not ready for review or merge. This 164 | feature should be utilized in place of the traditional method of prepending `[WIP]` to the PR title. 165 | 166 | As stated earlier, **ensure that "Allow edits from maintainers" is checked**. This gives permission for maintainers to commit changes directly to your fork, speeding up the review process. 167 | 168 | [Here](https://github.blog/2019-02-14-introducing-draft-pull-requests/) is all the info you need about Draft PRs 169 | in Github. 170 | 171 | ## Changes to this Arrangement 172 | 173 | Every kind of projects evolve over time, and these contributing guidelines are no different. 174 | 175 | This document is open to any kind of contributions, and PRs. Feel free to contribute to this guideline by 176 | adding, or changing anything, that you feel can change it, and improve it aswell. 177 | 178 | ## Credits 179 | 180 | This contributing guidelines file was inspired by 181 | [Python discord's Contributing Guidelines](https://github.com/python-discord/bot/blob/master/CONTRIBUTING.md). 182 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-present Sunrit Jana 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 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | requests = "==2.28.2" 8 | aiohttp = "==3.8.3" 9 | 10 | [dev-packages] 11 | flake8 = "~=5.0.4" 12 | flake8-annotations = "~=2.9.1" 13 | flake8-bugbear = "~=22.8.23" 14 | flake8-import-order = "~=0.18.1" 15 | flake8-tidy-imports = "~=4.8.0" 16 | pep8-naming = "~=0.13.2" 17 | pyright = "~=1.1.260" 18 | pre-commit = "~=2.20.0" 19 | ipython = "~=8.5.0" 20 | # Dependencies for documentation 21 | recommonmark = "~=0.7.1" 22 | myst-parser = "~=0.18.0" 23 | sphinx-rtd-theme = "~=1.0.0" 24 | # Development 25 | hypixelio = {editable = true, extras = ["all"], path = "."} 26 | 27 | [scripts] 28 | precommit = "pre-commit install" 29 | lint = "pre-commit run --all-files" 30 | tests = "python -m unittest" 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | __ __ _ __ ________ 3 | / / / /_ ______ (_) _____ / / / _/ __ \ 4 | / /_/ / / / / __ \/ / |/_/ _ \/ / / // / / / 5 | / __ / /_/ / /_/ / /> HypixelIO 11 | 12 |

A Modern, Efficient and Easy way of interacting with the Hypixel API!

13 | 14 |

15 | 16 | 17 | Made with Python 18 | 19 | 20 |

21 | 22 |

23 | 24 | 25 | PYPI - License 26 | 27 | 28 | 29 | PYPI Download per Month 30 | 31 | 32 | 33 | PYPI 34 | 35 | 36 | 37 | PYPI Python Version 38 | 39 | 40 | 41 | Maintenance 42 | 43 | 44 |

45 | 46 |

47 | 48 | 49 | Code Size 50 | 51 | 52 | 53 | Discord 54 | 55 | 56 |

57 | 58 | 59 |

60 | Docs 61 | · 62 | Report a bug 63 | · 64 | Discussions 65 | · 66 | Discord 67 |

68 | 69 | ## ✨ Why choose HypixelIO? 70 | 71 | - Modern way of handling requests 72 | - Modern OOP based structure 73 | - Both Async and blocking support 74 | - Simple ratelimit handling and caching 75 | - Elegant design with complete optimization 76 | - Easy to use with a modern and simple design 77 | - Complete API coverage 78 | 79 | ## 🚀 Installing 80 | 81 | **Python 3.7 or above is required!** 82 | 83 | ```sh 84 | # Windows 85 | py -3 -m pip install -U HypixelIO 86 | 87 | # Linux or MacOS 88 | python3 -m pip install -U HypixelIO 89 | 90 | # Install the nightly build 91 | python3 -m pip install -U git+https://github.com/janaSunrise/HypixelIO 92 | ``` 93 | 94 | You can also get extra features with this library. Here's how: 95 | 96 | ```sh 97 | # Use [speedups] to speed up only for async API 98 | python3 -m pip install -U "HypixelIO[speedups]" 99 | ``` 100 | 101 | ## Usage 102 | 103 | ```python 104 | from hypixelio import Client, Converters 105 | 106 | client = Client(api_key="your-api-key") 107 | 108 | boosters = client.get_boosters() # Get the boosters object 109 | 110 | friends = client.get_friends(uuid="user's-uuid") # Returns the Friends object 111 | # Or, if you don't know the UUID 112 | friends = client.get_friends(name="user's-username") 113 | 114 | print(boosters[0].id) 115 | print(friends.friends[0].receiver_id) 116 | ``` 117 | 118 | ### Async API usage 119 | 120 | ```python 121 | import asyncio 122 | 123 | from hypixelio import AsyncClient, AsyncConverters 124 | 125 | client = AsyncClient(api_key="your-api-key") 126 | 127 | # Async function to fetch info 128 | async def fetch_from_hypixel(): 129 | boosters = await client.get_boosters() # Get the boosters object 130 | 131 | friends = await client.get_friends(uuid="user's-uuid") # Returns the Friends object 132 | # Or, if you don't know the UUID 133 | friends = await client.get_friends(name="user's-username") 134 | 135 | # Safely close the connection 136 | await client.close() 137 | 138 | return boosters, friends 139 | 140 | # Run the coroutine using `asyncio` 141 | boosters, friends = asyncio.run(fetch_from_hypixel()) 142 | 143 | print(boosters[0].id) 144 | print(friends.friends[0].receiver_id) 145 | ``` 146 | 147 | **Find more examples [here](https://github.com/janaSunrise/HypixelIO/tree/main/examples)!** 148 | 149 | ## 📢 Changelog 150 | 151 | If you're interested in seeing the **Changelog**, Go [here!](https://github.com/janaSunrise/HypixelIO/blob/main/CHANGELOG.md) 152 | 153 | ## 🤝 Contributing 154 | 155 | Contributions, issues and feature requests are welcome. After cloning & setting up project locally, you can just submit 156 | a PR to this repo and it will be deployed once it's accepted. 157 | 158 | ⚠️ It’s good to have descriptive commit messages, or PR titles so that other contributors can understand about your 159 | commit or the PR Created. Read [conventional commits](https://www.conventionalcommits.org/en/v1.0.0-beta.3/) before 160 | making the commit message. You can find our contributing guidelines 161 | [here](https://github.com/janaSunrise/HypixelIO/blob/main/CONTRIBUTING.md) 162 | 163 | We have a branch called `dev` containing development code. If you're contributing, Remember to contribute to 164 | `dev` branch, instead of `main`. 165 | 166 | ## 💬 Get in touch 167 | 168 | If you have various suggestions, questions or want to discuss things with our community, Have a look at 169 | [Github discussions](https://github.com/janaSunrise/HypixelIO/discussions) or join our Discord server! 170 | 171 | [![Discord](https://discordapp.com/api/guilds/835940276869791816/widget.png?style=shield)](https://discord.gg/MKC4qna4Gz) 172 | 173 | ## 👋 Show your support 174 | 175 | Be sure to drop a 🌟 if you like the project! 176 | 177 | ## ▶ Links 178 | 179 | - [Official Documentation](http://hypixelio.rtfd.io/) 180 | - [Raise an Issue](https://github.com/janaSunrise/HypixelIO/issues) 181 | - [Discussions](https://github.com/janaSunrise/HypixelIO/discussions) 182 | - [Hypixel API Documentation](https://api.hypixel.net) 183 | 184 |
Made by Sunrit Jana with ❤
185 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | SPHINXOPTS ?= 3 | SPHINXBUILD ?= sphinx-build 4 | SOURCEDIR = . 5 | BUILDDIR = build 6 | 7 | .PHONY: help Makefile 8 | 9 | # Put it first so that "make" without argument is like "make help". 10 | help: 11 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 12 | 13 | clean: 14 | rm -rf $(BUILDDIR)/* 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 20 | -------------------------------------------------------------------------------- /docs/_async/api.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: hypixelio 2 | 3 | API Reference 4 | ============= 5 | 6 | Client 7 | ~~~~~~ 8 | 9 | .. autoclass:: hypixelio._async.AsyncClient 10 | :members: 11 | :undoc-members: 12 | 13 | 14 | Converter 15 | ~~~~~~~~~ 16 | 17 | .. autoclass:: hypixelio._async.AsyncConverters 18 | :members: 19 | :undoc-members: 20 | 21 | 22 | Utils 23 | ~~~~~ 24 | 25 | .. autoclass:: hypixelio._async.Utils 26 | :members: 27 | :undoc-members: 28 | -------------------------------------------------------------------------------- /docs/_static/css/custom.css: -------------------------------------------------------------------------------- 1 | /* CSS files import */ 2 | @import "../basic.css"; 3 | 4 | /* Fonts */ 5 | @import url("https://fonts.googleapis.com/css2?family=Fira+Code&family=Poppins&family=Varela+Round&display=swap"); 6 | 7 | /* Reset */ 8 | * { 9 | box-sizing: border-box; 10 | } 11 | 12 | html { 13 | scroll-behavior: smooth; 14 | } 15 | 16 | body { 17 | font-family: "Poppins", -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, 18 | Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 19 | font-weight: 400; 20 | } 21 | 22 | /* Other styles */ 23 | div.sphinxsidebarwrapper { 24 | margin-right: 30px; 25 | } 26 | 27 | div.sphinxsidebar { 28 | width: 260px; 29 | font-size: 14px; 30 | line-height: 1.5; 31 | } 32 | 33 | .wy-nav-content { 34 | max-width: 1500px; 35 | width: 75%; 36 | } 37 | 38 | .rst-content div[class^="highlight"], 39 | .rst-content pre.literal-block { 40 | border: transparent; 41 | overflow-x: auto; 42 | margin: 1px 0 24px; 43 | border-radius: 10px; 44 | background: #f1f1f1; 45 | } 46 | 47 | .rst-content .linenodiv pre, 48 | .rst-content div[class^="highlight"] pre, 49 | .rst-content pre.literal-block { 50 | font-family: "Fira Code", monospace; 51 | font-size: 12px; 52 | line-height: 1.4; 53 | } 54 | 55 | .rst-content .toctree-wrapper > p.caption, 56 | h1, 57 | h2, 58 | h3, 59 | h4, 60 | h5, 61 | h6, 62 | legend { 63 | margin-top: 0; 64 | font-weight: 700; 65 | font-family: "Varela Round", sans-serif; 66 | } 67 | 68 | .rst-content code.literal, 69 | .rst-content tt.literal { 70 | color: #373737; 71 | white-space: normal; 72 | border-radius: 0.5em; 73 | padding: 0.4% 0.4% 0.3%; 74 | background: #e8e8e8; 75 | border: transparent; 76 | } 77 | 78 | .ethical-rst { 79 | visibility: hidden; 80 | } 81 | 82 | .wy-side-nav-search { 83 | background: #4a94c4; 84 | } 85 | 86 | .version { 87 | color: #d9d9d9; 88 | } 89 | 90 | dt { 91 | padding-left: 8px; 92 | } 93 | 94 | .sig { 95 | border-radius: 0.314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593344612847564823378678316527120190914564856692346034861045432664821339360726024914127372458700660631558817488152092096282925409171536436789259036001133053054882046652138414695194151160943305727036575959195309218611738193261179310511854807446237996274956735188575272489122793818301194912983367336244065664308602139494639522473719070217986094370277053921717629317675238467481846766940513200056812714526356082778577134275778960917363717872146844090122495343014654958537105079227968925892354201995611212902196086403441815981362977477130996051870721134999999837297804995105973173281609631859502445945534690830264252230825334em; 96 | } 97 | 98 | html.writer-html4 .rst-content dl:not(.docutils) > dt, 99 | html.writer-html5 100 | .rst-content 101 | dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) 102 | > dt { 103 | border: 1px solid #72cdf7; 104 | } 105 | 106 | html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list) > dt, 107 | html.writer-html5 108 | .rst-content 109 | dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) 110 | dl:not(.field-list) 111 | > dt { 112 | border: 1px solid #adadad; 113 | } 114 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: hypixelio 2 | 3 | API Reference 4 | ============= 5 | 6 | Version Related Info 7 | --------------------- 8 | 9 | There is a single way to check the version for this library. 10 | 11 | .. data:: __version__ 12 | 13 | A string representation of the version. e.g. ``'0.1.0b1'``. This is based 14 | off of :pep:`440`. 15 | 16 | 17 | Clients 18 | ------- 19 | 20 | .. autoclass:: Client 21 | :members: 22 | :undoc-members: 23 | 24 | 25 | Converters 26 | ---------- 27 | 28 | .. autoclass:: Converters 29 | :members: 30 | :undoc-members: 31 | 32 | 33 | Utility 34 | ------- 35 | 36 | .. autoclass:: Utils 37 | :members: 38 | :undoc-members: 39 | 40 | 41 | Async to sync portal 42 | -------------------- 43 | 44 | .. autoclass:: hypixelio._async.Portal 45 | :members: 46 | :undoc-members: 47 | 48 | .. autofunction:: hypixelio._async.create_portal 49 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from hypixelio import __version__ as hypixelio_version 5 | 6 | # Sphinx path setup 7 | sys.path.insert(0, os.path.abspath("..")) 8 | sys.path.append(os.path.abspath("extensions")) 9 | 10 | # Project info configuration 11 | project = "HypixelIO" 12 | copyright = "Copyright 2021-present, Sunrit Jana" 13 | author = "Sunrit Jana" 14 | 15 | release = hypixelio_version 16 | branch = ( 17 | "main" 18 | if hypixelio_version.endswith("a") or hypixelio_version.endswith("b") or hypixelio_version.endswith("rc") 19 | else "v" + hypixelio_version 20 | ) 21 | 22 | # General configuration 23 | extensions = [ 24 | "myst_parser", 25 | "resourcelinks", 26 | "sphinx.ext.autodoc", 27 | "sphinx.ext.extlinks", 28 | "sphinx.ext.napoleon", 29 | ] 30 | 31 | rst_prolog = """ 32 | .. |coro| replace:: This function is a |coroutine_link|_. 33 | .. |maybecoro| replace:: This function *could be a* |coroutine_link|_. 34 | .. |coroutine_link| replace:: *coroutine* 35 | .. _coroutine_link: https://docs.python.org/3/library/asyncio-task.html#coroutine 36 | """ 37 | 38 | # Warnings 39 | suppress_warnings = ["myst.header"] 40 | 41 | extlinks = { 42 | "issue": ("https://github.com/janaSunrise/HypixelIO/issues/%s", "GH-"), 43 | } 44 | 45 | source_suffix = {".rst": "restructuredtext", ".md": "markdown", ".txt": "markdown"} 46 | 47 | master_doc = "index" 48 | 49 | # Add any paths that contain templates here, relative to this directory. 50 | templates_path = ["_templates"] 51 | 52 | # List of patterns, relative to source directory, that match files and 53 | # directories to ignore when looking for source files. 54 | # This pattern also affects html_static_path and html_extra_path. 55 | exclude_patterns = ["_build", "Thumbs.db", ".DS_STORE"] 56 | 57 | # Links for the RST 58 | resource_links = { 59 | "repo": "https://github.com/janaSunrise/HypixelIO/", 60 | "issues": "https://github.com/janaSunrise/HypixelIO/issues", 61 | "discussions": "https://github.com/janaSunrise/HypixelIO/discussions", 62 | "examples": f"https://github.com/janaSunrise/HypixelIO/tree/{branch}/examples", 63 | } 64 | 65 | # -- Options for HTML output ------------------------------------------------- 66 | html_theme = "sphinx_rtd_theme" 67 | 68 | # Add any paths that contain custom static files (such as style sheets) here, 69 | # relative to this directory. They are copied after the builtin static files, 70 | # so a file named "default.css" will overwrite the builtin "default.css". 71 | html_static_path = ["_static"] 72 | html_css_files = ["css/custom.css"] 73 | -------------------------------------------------------------------------------- /docs/exceptions.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: hypixelio 2 | 3 | API Reference 4 | ============= 5 | 6 | Errors 7 | ------ 8 | 9 | .. autoexception:: hypixelio.exceptions.InvalidArgumentError 10 | 11 | 12 | .. autoexception:: hypixelio.exceptions.RateLimitError 13 | 14 | 15 | .. autoexception:: hypixelio.exceptions.PlayerNotFoundError 16 | 17 | 18 | .. autoexception:: hypixelio.exceptions.GuildNotFoundError 19 | 20 | 21 | .. autoexception:: hypixelio.exceptions.HypixelAPIError 22 | 23 | 24 | .. autoexception:: hypixelio.exceptions.MojangAPIError 25 | 26 | 27 | .. autoexception:: hypixelio.exceptions.CrafatarAPIError 28 | -------------------------------------------------------------------------------- /docs/extensions/resourcelinks.py: -------------------------------------------------------------------------------- 1 | # Credit to sphinx.ext.extlinks for being a good starter 2 | # Copyright 2007-2020 by the Sphinx team 3 | # Licensed under BSD. 4 | 5 | import typing as t 6 | 7 | import sphinx 8 | from docutils import nodes, utils 9 | from docutils.nodes import Node, system_message 10 | from docutils.parsers.rst.states import Inliner 11 | from sphinx.application import Sphinx 12 | from sphinx.util.nodes import split_explicit_title 13 | from sphinx.util.typing import RoleFunction 14 | 15 | 16 | def make_link_role(resource_links: t.Dict[str, str]) -> RoleFunction: 17 | def role( 18 | typ: str, 19 | rawtext: str, 20 | text: str, 21 | lineno: int, 22 | inliner: Inliner, 23 | options: t.Dict = {}, # noqa: B006 24 | content: t.List[str] = [], # noqa: B006 25 | ) -> t.Tuple[t.List[Node], t.List[system_message]]: 26 | text = utils.unescape(text) 27 | 28 | has_explicit_title, title, key = split_explicit_title(text) 29 | 30 | full_url = resource_links[key] 31 | 32 | if not has_explicit_title: 33 | title = full_url 34 | 35 | pnode = nodes.reference(title, title, internal=False, refuri=full_url) 36 | return [pnode], [] 37 | 38 | return role 39 | 40 | 41 | def add_link_role(app: Sphinx) -> None: 42 | app.add_role("resource", make_link_role(app.config.resource_links)) 43 | 44 | 45 | def setup(app: Sphinx) -> t.Dict[str, t.Any]: 46 | app.add_config_value("resource_links", {}, "env") 47 | app.connect("builder-inited", add_link_role) 48 | return {"version": sphinx.__display_version__, "parallel_read_safe": True} 49 | -------------------------------------------------------------------------------- /docs/faq.rst: -------------------------------------------------------------------------------- 1 | FAQs 2 | ==== 3 | 4 | No frequently asked questions are present here! But, we are open to the public. 5 | Feel free to suggest a new question, open an issue or submit one via pull requests. 6 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to HypixelIO! 2 | ====================== 3 | 4 | HypixelIO is a modern, robust, and efficient wrapper for the Hypixel API. With support for both Async 5 | and blocking programming, This has everything for you to work with. 6 | 7 | .. toctree:: 8 | :maxdepth: 2 9 | :titlesonly: 10 | 11 | Welcome 12 | Home 13 | whats_new 14 | faq 15 | 16 | Features 17 | -------- 18 | 19 | - Modern way of handling requests 20 | - Both async and sync support. 21 | - Simple rate handling, and caching. 22 | - Speed optimized 23 | - Easy to use with a modern and simple design 24 | 25 | Getting started 26 | ----------------- 27 | 28 | Is this your first time using the library? This is the place to get started! 29 | 30 | You can get quickly started with the library over here :doc:`quickstart`. 31 | 32 | You can find more examples in the :resource:`repository `. You can also add your own! 33 | Just create a Pull request, and get it added. 34 | 35 | 36 | Getting help 37 | ------------ 38 | 39 | Stuck with library, and need help? These resources might help. 40 | 41 | - Try the :doc:`faq` first, it's got answers to all common questions. 42 | - If you're looking for something specific, try the :ref:`index ` or :ref:`searching `. 43 | - Report bugs in the :resource:`issue tracker `. 44 | - Ask in our :resource:`GitHub discussions page `. 45 | 46 | Modules and manuals 47 | ------------------- 48 | 49 | Here are the modules which constitute the API. All the API documentation, right here to help 50 | you understand and use this library. 51 | 52 | .. toctree:: 53 | :maxdepth: 1 54 | 55 | API reference 56 | Asynchronous API reference <_async/api.rst> 57 | Exceptions API reference 58 | -------------------------------------------------------------------------------- /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 | 11 | set SOURCEDIR=. 12 | set BUILDDIR=build 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs/quickstart.md: -------------------------------------------------------------------------------- 1 | # 2 | 3 | ```{include} ../README.md 4 | 5 | ``` 6 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx-rtd-theme==1.0.0 2 | myst-parser==0.18.0 3 | recommonmark==0.7.1 4 | Sphinx==5.1.1 5 | -------------------------------------------------------------------------------- /docs/whats_new.md: -------------------------------------------------------------------------------- 1 | ```{include} ../CHANGELOG.md 2 | 3 | ``` 4 | -------------------------------------------------------------------------------- /examples/async-player.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | from textwrap import dedent 4 | 5 | from hypixelio import AsyncClient 6 | 7 | 8 | async def fetch() -> None: 9 | # Login to the API 10 | client = AsyncClient(api_key=os.environ["HYPIXEL_KEY"]) 11 | 12 | # Get a player object 13 | player = await client.get_player(name="janaSunrise") 14 | 15 | # Close the session 16 | await client.close() 17 | 18 | # Extract the data from the object 19 | name, uuid, achievements = player.name, player.uuid, player.achievements 20 | 21 | # Print the data 22 | print( 23 | dedent( 24 | f""" 25 | Name: {name} 26 | UUID: {uuid} 27 | Achievements: {achievements} 28 | """ 29 | ) 30 | ) 31 | 32 | 33 | asyncio.run(fetch()) 34 | -------------------------------------------------------------------------------- /examples/get-guild-data.py: -------------------------------------------------------------------------------- 1 | import os 2 | from textwrap import dedent 3 | 4 | import hypixelio as hp 5 | 6 | # Init the Client 7 | client = hp.Client(api_key=os.environ["HYPIXEL_KEY"]) 8 | 9 | # Get the guild object 10 | guild = client.get_guild(name="2k") 11 | 12 | # Get the essential data 13 | name, ranking, achievements = guild.name, guild.legacy_ranking, guild.achievements 14 | 15 | # Print the data 16 | print( 17 | dedent( 18 | f""" 19 | Name: {name} 20 | ranking: {ranking} 21 | achievements: {achievements} 22 | """ 23 | ) 24 | ) 25 | -------------------------------------------------------------------------------- /examples/get-player-data.py: -------------------------------------------------------------------------------- 1 | import os 2 | from textwrap import dedent 3 | 4 | import hypixelio as hp 5 | 6 | # Login to the API 7 | client = hp.Client(api_key=os.environ["HYPIXEL_KEY"]) 8 | 9 | # Get a player object 10 | player = client.get_player(name="janaSunrise") 11 | 12 | # Extract the data from the object 13 | name, uuid, achievements = player.name, player.uuid, player.achievements 14 | 15 | # Print the data 16 | print( 17 | dedent( 18 | f""" 19 | Name: {name} 20 | UUID: {uuid} 21 | Achievements: {achievements} 22 | """ 23 | ) 24 | ) 25 | -------------------------------------------------------------------------------- /examples/get-watchdog-stats.py: -------------------------------------------------------------------------------- 1 | import os 2 | from textwrap import dedent 3 | 4 | import hypixelio as hp 5 | 6 | # Init the Client 7 | client = hp.Client(api_key=os.environ["HYPIXEL_KEY"]) 8 | 9 | # Get the watchdog stats 10 | watchdog = client.get_watchdog_info() 11 | 12 | # Extract the Data 13 | last_minute_ban = watchdog.last_minute_ban 14 | total_bans = watchdog.total_bans 15 | rolling_daily = watchdog.rolling_daily 16 | 17 | # Display the data 18 | print( 19 | dedent( 20 | f""" 21 | Last minute ban: {last_minute_ban} 22 | Total Bans: {total_bans} 23 | Rolling daily: {rolling_daily} 24 | """ 25 | ) 26 | ) 27 | -------------------------------------------------------------------------------- /examples/user-to-uuid.py: -------------------------------------------------------------------------------- 1 | from hypixelio import Converters 2 | 3 | # Convert to UUID 4 | uuid = Converters.username_to_uuid("janaSunrise") 5 | 6 | # Show the UUID 7 | print(f"Your UUID is {uuid}") 8 | -------------------------------------------------------------------------------- /examples/uuid-to-username.py: -------------------------------------------------------------------------------- 1 | from hypixelio import Converters 2 | 3 | # Convert to UUID 4 | username = Converters.uuid_to_username("c8438cdd126043448cca9e28646efbe7") 5 | 6 | # Show the UUID 7 | print(f"Your username is {username}") 8 | -------------------------------------------------------------------------------- /hypixelio/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | 3 | from . import constants 4 | from ._async import AsyncClient, AsyncConverters 5 | from ._async import Utils as AsyncUtils 6 | from .lib import Client, Converters, Utils 7 | 8 | __author__ = "Sunrit Jana" 9 | __email__ = "warriordefenderz@gmail.com" 10 | __version__ = "1.4.1" 11 | __license__ = "MIT license" 12 | __copyright__ = "Copyright 2021-present Sunrit Jana" 13 | 14 | __all__: Tuple[str, ...] = ( 15 | "__author__", 16 | "__email__", 17 | "__version__", 18 | "__license__", 19 | "__copyright__", 20 | "AsyncClient", 21 | "AsyncConverters", 22 | "AsyncUtils", 23 | "Client", 24 | "Converters", 25 | "Utils", 26 | "constants", 27 | ) 28 | -------------------------------------------------------------------------------- /hypixelio/_async/__init__.py: -------------------------------------------------------------------------------- 1 | from .client import AsyncClient 2 | from .converters import AsyncConverters 3 | from .utils import Utils 4 | -------------------------------------------------------------------------------- /hypixelio/_async/client.py: -------------------------------------------------------------------------------- 1 | __all__ = ("AsyncClient",) 2 | 3 | import asyncio 4 | import random 5 | from types import TracebackType 6 | from typing import ( 7 | Any, 8 | Dict, 9 | Optional, 10 | Type, 11 | Union, 12 | cast 13 | ) 14 | 15 | import aiohttp 16 | 17 | from .converters import AsyncConverters 18 | from ..base import BaseClient 19 | from ..constants import HYPIXEL_API, TIMEOUT 20 | from ..exceptions import ( 21 | GuildNotFoundError, 22 | HypixelAPIError, 23 | InvalidArgumentError, 24 | PlayerNotFoundError, 25 | RateLimitError, 26 | ) 27 | from ..models.boosters import Boosters 28 | from ..models.find_guild import FindGuild 29 | from ..models.friends import Friends 30 | from ..models.games import Games 31 | from ..models.guild import Guild 32 | from ..models.key import Key 33 | from ..models.leaderboard import Leaderboard 34 | from ..models.player import Player 35 | from ..models.player_status import PlayerStatus 36 | from ..models.recent_games import RecentGames 37 | from ..models.skyblock import ( 38 | SkyblockActiveAuction, 39 | SkyblockBazaar, 40 | SkyblockNews, 41 | SkyblockProfile, 42 | SkyblockUserAuction, 43 | ) 44 | from ..models.watchdog import Watchdog 45 | from ..utils import form_url 46 | 47 | 48 | class AsyncClient(BaseClient): 49 | """ 50 | The client for this wrapper that handles the requests, authentication, loading and usages of the end user. 51 | 52 | Examples 53 | -------- 54 | Import the async client first. 55 | 56 | >>> from hypixelio._async import AsyncClient 57 | 58 | If you have a single API key, Here's how to authenticate 59 | 60 | >>> client = AsyncClient(api_key="123-456-789") 61 | 62 | You can use multiple API keys to authenticate too. (Better option for load balancing) 63 | 64 | >>> client = AsyncClient(api_key=["123-456", "789-000", "568-908"]) 65 | """ 66 | 67 | def __init__(self, api_key: Union[str, list]) -> None: 68 | """ 69 | Parameters 70 | ---------- 71 | api_key: Union[str, list] 72 | The API key generated in Hypixel server using the `/api new` command. 73 | """ 74 | super().__init__(api_key) 75 | 76 | self._session: Optional[aiohttp.ClientSession] = None 77 | self._lock = asyncio.Lock() 78 | 79 | async def close(self) -> None: 80 | """Close the AIOHTTP sessions to prevent memory leaks.""" 81 | if self._session is not None: 82 | await self._session.close() 83 | 84 | async def _fetch( 85 | self, 86 | url: str, 87 | data: Optional[Dict[str, Any]] = None, 88 | api_key: bool = True, 89 | ) -> dict: 90 | """ 91 | Fetch the JSON response from the API along with the ability to include GET request parameters and support 92 | Authentication using API key too. 93 | 94 | Parameters 95 | ---------- 96 | url: str 97 | The URL to be accessed from the API root URL. 98 | data: Optional[dict] 99 | The GET Request's Key-Value Pair. Example: {"uuid": "abc"} is converted to `?uuid=abc`. Defaults to None. 100 | api_key: bool 101 | If key is needed for the endpoint. 102 | 103 | Returns 104 | ------- 105 | Tuple[dict, bool] 106 | The JSON response obtained after fetching the API, along with success value in the response. 107 | """ 108 | if not self._session: 109 | self._session = aiohttp.ClientSession() 110 | 111 | # Check if ratelimit is hit 112 | if self._is_ratelimit_hit(): 113 | raise RateLimitError(self.retry_after) 114 | 115 | if not data: 116 | data = {} 117 | 118 | # Assign a random key 119 | if api_key: 120 | self.headers["API-Key"] = random.choice(self._api_key) 121 | 122 | url = form_url(HYPIXEL_API, url, data) 123 | 124 | async with self._lock: 125 | async with self._session.get( 126 | url, headers=self.headers, timeout=TIMEOUT 127 | ) as response: 128 | # 404 handling 129 | if response.status == 429: 130 | raise HypixelAPIError("The route specified does not exis") 131 | 132 | # 429 status code handling 133 | if response.status == 429: 134 | self._handle_ratelimit(cast(dict, response.headers)) 135 | 136 | # 403 Status code handling 137 | if response.status == 403: 138 | raise HypixelAPIError("Invalid key specified!") 139 | 140 | if api_key and "RateLimit-Limit" in response.headers: 141 | self._update_ratelimit(cast(dict, response.headers)) 142 | 143 | try: 144 | json = await response.json() 145 | except Exception as exception: 146 | raise HypixelAPIError(f"{exception}") 147 | else: 148 | if not json["success"]: 149 | self._handle_api_failure(json) 150 | 151 | return json 152 | 153 | @staticmethod 154 | async def _filter_name_uuid(name: Optional[str] = None, uuid: Optional[str] = None) -> str: 155 | if not name and not uuid: 156 | raise InvalidArgumentError( 157 | "Named argument for player's either username or UUID not found." 158 | ) 159 | 160 | if name: 161 | uuid = await AsyncConverters.username_to_uuid(name) 162 | 163 | return uuid # type: ignore 164 | 165 | # Context managers 166 | async def __aenter__(self) -> "AsyncClient": 167 | self._session = aiohttp.ClientSession() 168 | 169 | return self 170 | 171 | async def __aexit__( 172 | self, 173 | exc_type: Optional[Type[BaseException]], 174 | exc_val: Optional[BaseException], 175 | exc_tb: Optional[TracebackType], 176 | ) -> None: 177 | if self._session is not None: 178 | await self._session.close() 179 | 180 | # Hypixel API endpoint methods 181 | async def get_key_info(self, api_key: Optional[str] = None) -> Key: 182 | """ 183 | Get info about a specific Hypixel API key. 184 | 185 | Parameters 186 | ---------- 187 | api_key: Optional[str] 188 | The API key generated in Hypixel server using the `/api new` command. Defaults to pre-specified keys. 189 | 190 | Returns 191 | ------- 192 | Key 193 | The Key object created for the API key specified. 194 | """ 195 | if not api_key: 196 | api_key = random.choice(self._api_key) 197 | 198 | json = await self._fetch(self.url["api_key"], {"key": api_key}) 199 | return Key(json["record"]) 200 | 201 | async def get_boosters(self) -> Boosters: 202 | """ 203 | Get the Hypixel coin boosters, and all the info about them. 204 | 205 | Returns 206 | ------- 207 | Boosters 208 | The boosters object, with all the info from the API. 209 | """ 210 | json = await self._fetch(self.url["boosters"]) 211 | 212 | return Boosters(json["boosters"], json) 213 | 214 | async def get_player( 215 | self, name: Optional[str] = None, uuid: Optional[str] = None 216 | ) -> Player: 217 | """ 218 | Get all info about a Hypixel player using his username or his player UUID. 219 | 220 | Parameters 221 | ---------- 222 | name: Optional[str] 223 | The Optional string value for the Username. Defaults to None. 224 | uuid: Optional[str] 225 | The Optional string Value to the UUID. Defaults to None. 226 | 227 | Returns 228 | ------- 229 | Player 230 | The player object with all the info obtained from the API. 231 | """ 232 | uuid = await self._filter_name_uuid(name, uuid) 233 | json = await self._fetch(self.url["player"], {"uuid": uuid}) 234 | 235 | if not json["player"]: 236 | raise PlayerNotFoundError("Null is returned", name) 237 | 238 | return Player(json["player"]) 239 | 240 | async def get_friends( 241 | self, name: Optional[str] = None, uuid: Optional[str] = None 242 | ) -> Friends: 243 | """ 244 | Get the friends, and all their info of specified Hypixel player. 245 | 246 | Parameters 247 | ---------- 248 | name: Optional[str] 249 | The Optional string value for the Username of a hypixel player. Defaults to None. 250 | uuid: Optional[str] 251 | The UUID of a Certain Hypixel Player. Defaults to None. 252 | 253 | Returns 254 | ------- 255 | Friends 256 | The Friend object with all info from the API. 257 | """ 258 | uuid = await self._filter_name_uuid(name, uuid) 259 | json = await self._fetch(self.url["friends"], {"uuid": uuid}) 260 | 261 | return Friends(json["records"]) 262 | 263 | async def get_watchdog_info(self) -> Watchdog: 264 | """ 265 | Get all the stats about the Watchdog (Punishment stats) for the last few days/ 266 | 267 | Returns 268 | ------- 269 | Watchdog 270 | The Watchdog object with all the info. 271 | """ 272 | json = await self._fetch(self.url["watchdog"]) 273 | 274 | return Watchdog(json) 275 | 276 | async def get_guild( 277 | self, name: Optional[str] = None, uuid: Optional[str] = None 278 | ) -> Guild: 279 | """ 280 | Get info about a specific Hypixel guild using the Name, or the Guild's UUID. 281 | 282 | Parameters 283 | ---------- 284 | name: Optional[str] 285 | The Name of the Guild. Defaults to None. 286 | uuid: Optional[str] 287 | The ID Of the guild. Defaults to None. 288 | 289 | Returns 290 | ------- 291 | Guild 292 | The Guild object with the info fetched from the API. 293 | """ 294 | if uuid: 295 | json = await self._fetch(self.url["guild"], {"id": uuid}) 296 | elif name: 297 | json = await self._fetch(self.url["guild"], {"name": name}) 298 | else: 299 | raise InvalidArgumentError( 300 | "Named argument for guild's name or UUID not found." 301 | ) 302 | 303 | if not json["guild"]: 304 | raise GuildNotFoundError("Value returned is null") 305 | 306 | return Guild(json["guild"]) 307 | 308 | async def get_games_info(self) -> Games: 309 | """ 310 | Get the list of all Hypixel games, and their info. 311 | 312 | Returns 313 | ------- 314 | Games 315 | The Games object with all the info. 316 | """ 317 | json = await self._fetch(self.url["game_info"]) 318 | 319 | return Games(json["games"], json["playerCount"]) 320 | 321 | async def get_leaderboards(self) -> Leaderboard: 322 | """ 323 | Get the leaderboard for the Hypixel games with their info. 324 | 325 | Returns 326 | ------- 327 | Leaderboard 328 | The Leaderboard object with all info. 329 | """ 330 | json = await self._fetch(self.url["leaderboards"]) 331 | 332 | return Leaderboard(json["leaderboards"]) 333 | 334 | async def find_guild( 335 | self, guild_name: Optional[str] = None, player_uuid: Optional[str] = None 336 | ) -> FindGuild: 337 | """ 338 | Find a guild using the Guild's name or a Player's UUID. 339 | 340 | Parameters 341 | ---------- 342 | guild_name: Optional[str] 343 | The name of the Guild. Defaults to None. 344 | player_uuid: Optional[str] 345 | The UUID of the Player to find his guild. Defaults to None. 346 | 347 | Returns 348 | ------- 349 | FindGuild 350 | The ID of the guild being find. 351 | """ 352 | if guild_name: 353 | json = await self._fetch(self.url["find_guild"], {"byName": guild_name}) 354 | elif player_uuid: 355 | json = await self._fetch(self.url["find_guild"], {"byUuid": player_uuid}) 356 | else: 357 | raise InvalidArgumentError( 358 | "Named argument for guild's name or UUID not found." 359 | ) 360 | 361 | return FindGuild(json) 362 | 363 | async def get_player_status( 364 | self, name: Optional[str] = None, uuid: Optional[str] = None 365 | ) -> PlayerStatus: 366 | """ 367 | Get the status about a Player using his username or UUID. 368 | 369 | Parameters 370 | ---------- 371 | name: Optional[str] 372 | The Optional string value for the Username. Defaults to None. 373 | uuid: Optional[str] 374 | The Optional string Value to the UUID. Defaults to None. 375 | 376 | Returns 377 | ------- 378 | PlayerStatus 379 | The Player status object consisting of all info from the API. 380 | """ 381 | uuid = await self._filter_name_uuid(name, uuid) 382 | json = await self._fetch(self.url["status"], {"uuid": uuid}) 383 | 384 | return PlayerStatus(json) 385 | 386 | async def get_player_recent_games( 387 | self, name: Optional[str] = None, uuid: Optional[str] = None 388 | ) -> RecentGames: 389 | """ 390 | Get the recent games played by a Hypixel player using his Username or UUID. 391 | 392 | Parameters 393 | ---------- 394 | name: Optional[str] 395 | The Optional string value for the Username. Defaults to None. 396 | uuid: Optional[str] 397 | The Optional string Value to the UUID. Defaults to None. 398 | 399 | Returns 400 | ------- 401 | RecentGames 402 | The recent games for the respective player specified. 403 | """ 404 | uuid = await self._filter_name_uuid(name, uuid) 405 | json = await self._fetch(self.url["recent_games"], {"uuid": uuid}) 406 | 407 | return RecentGames(json) 408 | 409 | async def get_skyblock_news(self) -> SkyblockNews: 410 | json = await self._fetch(self.url["skyblock_news"]) 411 | 412 | return SkyblockNews(json) 413 | 414 | async def get_skyblock_profile( 415 | self, name: Optional[str] = None, uuid: Optional[str] = None 416 | ) -> SkyblockProfile: 417 | """ 418 | Get the skyblock information and profile about a specific user as passed in the requirements. 419 | 420 | Parameters 421 | ---------- 422 | name: Optional[str] 423 | The player's name in Hypixel 424 | uuid: Optional[str] 425 | The player's global UUID 426 | 427 | Returns 428 | ------- 429 | SkyblockProfile 430 | The skyblock profile model for the specified user. 431 | """ 432 | uuid = await self._filter_name_uuid(name, uuid) 433 | json = await self._fetch(self.url["skyblock_profile"], {"profile": uuid}) 434 | 435 | if not json["profile"]: 436 | raise PlayerNotFoundError("The skyblock player does not exist", uuid) 437 | 438 | return SkyblockProfile(json) 439 | 440 | async def get_skyblock_user_auctions( 441 | self, name: Optional[str] = None, uuid: Optional[str] = None 442 | ) -> SkyblockUserAuction: 443 | """ 444 | Get the skyblock auction info about a specific user. 445 | 446 | Parameters 447 | ---------- 448 | name: Optional[str] 449 | The player's name in Hypixel 450 | uuid: Optional[str] 451 | The player's global UUID 452 | 453 | Returns 454 | ------- 455 | SkyblockUserAuction 456 | The skyblock auction model for the user. 457 | """ 458 | uuid = await self._filter_name_uuid(name, uuid) 459 | json = await self._fetch(self.url["skyblock_auctions"], {"profile": uuid}) 460 | 461 | if not json["auctions"]: 462 | raise PlayerNotFoundError("The skyblock player does not exist!", uuid) 463 | 464 | return SkyblockUserAuction(json) 465 | 466 | async def get_skyblock_active_auctions( 467 | self, page: int = 0 468 | ) -> SkyblockActiveAuction: 469 | """ 470 | Get the list of active auctions in skyblock and use the data. 471 | 472 | Parameters 473 | ---------- 474 | page: int 475 | The skyblock auction page to lookup. 476 | 477 | Returns 478 | ------- 479 | SkyblockActiveAuction 480 | The active auction model. 481 | """ 482 | json = await self._fetch(self.url["skyblock_active_auctions"], {"page": page}) 483 | return SkyblockActiveAuction(json) 484 | 485 | async def get_skyblock_bazaar(self) -> SkyblockBazaar: 486 | """ 487 | Get the skyblock bazaar items 488 | 489 | Returns 490 | ------- 491 | SkyblockBazaar 492 | The bazaar model object representing each produc 493 | """ 494 | json = await self._fetch(self.url["skyblock_bazaar"]) 495 | return SkyblockBazaar(json) 496 | 497 | async def get_resources_achievements(self) -> dict: 498 | data = await self._fetch(self.url["achievements"], api_key=False) 499 | return data["achievements"] 500 | 501 | async def get_resources_challenges(self) -> dict: 502 | data = await self._fetch(self.url["challenges"], api_key=False) 503 | return data["challenges"] 504 | 505 | async def get_resources_quests(self) -> dict: 506 | data = await self._fetch(self.url["quests"], api_key=False) 507 | return data["quests"] 508 | 509 | async def get_resources_guild_achievements(self) -> dict: 510 | data = await self._fetch(self.url["guild_achievements"], api_key=False) 511 | return {"one_time": data["one_time"], "tiered": data["tiered"]} 512 | 513 | async def get_skyblock_skills(self) -> dict: 514 | data = await self._fetch(self.url["skyblock_skills"], api_key=False) 515 | return { 516 | "skills": data["skills"], 517 | "collections": data["collections"], 518 | } 519 | 520 | async def get_skyblock_collections(self) -> dict: 521 | data = await self._fetch(self.url["skyblock_collections"], api_key=False) 522 | return data["collections"] 523 | -------------------------------------------------------------------------------- /hypixelio/_async/converters.py: -------------------------------------------------------------------------------- 1 | __all__ = ("AsyncConverters",) 2 | 3 | from typing import Any, Dict, Union, cast 4 | 5 | import aiohttp 6 | 7 | from ..constants import MOJANG_API, TIMEOUT 8 | from ..endpoints import API_PATH 9 | from ..exceptions import MojangAPIError, PlayerNotFoundError 10 | 11 | 12 | class AsyncConverters: 13 | url = API_PATH["MOJANG"] 14 | 15 | @classmethod 16 | async def _fetch(cls, url: str) -> Union[dict, list]: 17 | """ 18 | The internal function for fetching info from the Mojang API. 19 | 20 | Parameters 21 | ---------- 22 | url: str 23 | The Mojang URL, whose JSON is supposed to be fetched. 24 | 25 | Returns 26 | ------- 27 | Union[dict, list] 28 | The JSON response from the Mojang API. 29 | """ 30 | session = aiohttp.ClientSession() 31 | 32 | async with session.get(f"{MOJANG_API}{url}", timeout=TIMEOUT) as response: 33 | if response.status == 204: 34 | raise PlayerNotFoundError( 35 | "Error code 204 returned during conversion to UUID.", None 36 | ) 37 | 38 | if response.status == 400: 39 | raise PlayerNotFoundError("Badly formed UUID error.", None) 40 | 41 | try: 42 | json = await response.json() 43 | except Exception: 44 | raise MojangAPIError() 45 | else: 46 | if "error" in json: 47 | raise MojangAPIError(f"An error occurred! {json['errorMessage']}") 48 | 49 | return json 50 | 51 | @classmethod 52 | async def username_to_uuid(cls, username: str) -> str: 53 | """ 54 | This is a method, to convert username in minecraft, for its respective UUID. 55 | 56 | Parameters 57 | ---------- 58 | username: str 59 | This is the minecraft user, which is passed to this function for the UUID Conversion. 60 | 61 | Returns 62 | ------- 63 | str 64 | returns the converted UUID for the respective username. 65 | """ 66 | json = cast( 67 | Dict[str, Any], 68 | await AsyncConverters._fetch( 69 | AsyncConverters.url["username_to_uuid"].format(username) 70 | ), 71 | ) 72 | 73 | return json["id"] 74 | 75 | @classmethod 76 | async def uuid_to_username(cls, uuid: str) -> str: 77 | """ 78 | Method to convert the UUID for your profile to the username for your Minecraft account. 79 | 80 | Parameters 81 | ---------- 82 | uuid: str 83 | This is the minecraft UUID, which is passed to this function for the UUID to username Conversion. 84 | 85 | Returns 86 | ------- 87 | str 88 | The username for the respective minecraft UUID is returned. 89 | """ 90 | json = await AsyncConverters._fetch( 91 | AsyncConverters.url["uuid_to_username"].format(uuid) 92 | ) 93 | 94 | return json[-1]["name"] 95 | -------------------------------------------------------------------------------- /hypixelio/_async/utils.py: -------------------------------------------------------------------------------- 1 | __all__ = ("Utils",) 2 | 3 | from typing import Optional, Union 4 | 5 | import aiohttp 6 | 7 | from .converters import AsyncConverters as Converters 8 | from ..constants import TIMEOUT 9 | from ..endpoints import API_PATH 10 | from ..exceptions import CrafatarAPIError, InvalidArgumentError 11 | 12 | 13 | class Utils: 14 | mojang_url = API_PATH["MOJANG"] 15 | url = API_PATH["CRAFATAR"] 16 | 17 | @classmethod 18 | async def _crafatar_fetch(cls, url: str) -> str: 19 | """ 20 | Method to fetch the JSON from the Crafatar API. 21 | 22 | Parameters 23 | ---------- 24 | url: str 25 | The Crafatar URL, whose JSON is supposed to be fetched. 26 | 27 | Returns 28 | ------- 29 | ClientResponse 30 | The JSON response from the Crafatar API. 31 | """ 32 | session = aiohttp.ClientSession() 33 | 34 | async with session.get( 35 | f"https://crafatar.com/{url}", timeout=TIMEOUT 36 | ) as response: 37 | if response.status == 422: 38 | raise InvalidArgumentError( 39 | "Invalid URL passed. Either user does not exist, or URL is malformed." 40 | ) 41 | 42 | try: 43 | return await response.text() 44 | except Exception: 45 | raise CrafatarAPIError() 46 | 47 | @staticmethod 48 | async def _filter_name_uuid( 49 | name: Optional[str] = None, uuid: Optional[str] = None 50 | ) -> str: 51 | if not name and not uuid: 52 | raise InvalidArgumentError( 53 | "Please provide a named argument of the player's username or player's UUID." 54 | ) 55 | 56 | if name: 57 | uuid = await Converters.username_to_uuid(name) 58 | 59 | return uuid # type: ignore 60 | 61 | @classmethod 62 | def _form_crafatar_url(cls, route: str) -> str: 63 | """ 64 | This function forms the crafatar API URL for fetching skins of users. 65 | 66 | Parameters 67 | ---------- 68 | route: str 69 | The URL path to form for crafatar API. 70 | 71 | Returns 72 | ------- 73 | str 74 | The API URL formed to fetch. 75 | """ 76 | return f"https://crafatar.com{route}" 77 | 78 | @classmethod 79 | async def get_name_history( 80 | cls, 81 | name: Optional[str] = None, 82 | uuid: Optional[str] = None, 83 | changed_at: bool = False, 84 | ) -> Union[list, dict]: 85 | """ 86 | Get the name history with records for a player. 87 | 88 | Parameters 89 | ---------- 90 | name: Optional[str] 91 | The username of the player. Defaults to None. 92 | uuid: Optional[str] 93 | The UUID of the player. Defaults to None. 94 | changed_at: bool 95 | Toggle to true, if you need when the player changed name. Defaults to False. 96 | 97 | Returns 98 | ------- 99 | Union[list, dict] 100 | The list or dictionary with the name history and records. 101 | """ 102 | uuid = await cls._filter_name_uuid(name, uuid) 103 | json = await Converters._fetch(Utils.mojang_url["name_history"].format(uuid)) 104 | 105 | if changed_at: 106 | return json 107 | 108 | usernames = [] 109 | for data in json: 110 | usernames.append(data["name"]) 111 | 112 | return usernames 113 | 114 | @classmethod 115 | async def get_avatar( 116 | cls, name: Optional[str] = None, uuid: Optional[str] = None 117 | ) -> str: 118 | """ 119 | Get the avatar of the specified player. 120 | 121 | Parameters 122 | ---------- 123 | name: Optional[str] 124 | The username of the player. Defaults to None. 125 | uuid: Optional[str] 126 | The UUID of the player. Defaults to None. 127 | 128 | Returns 129 | ------- 130 | str 131 | The URL containing the image of the avatar. 132 | """ 133 | uuid = await cls._filter_name_uuid(name, uuid) 134 | await Utils._crafatar_fetch(Utils.url["avatar"].format(uuid)) 135 | 136 | return Utils._form_crafatar_url(Utils.url["avatar"].format(uuid)) 137 | 138 | @classmethod 139 | async def get_head( 140 | cls, name: Optional[str] = None, uuid: Optional[str] = None 141 | ) -> str: 142 | """ 143 | Get the head skin of the specified player. 144 | 145 | Parameters 146 | ---------- 147 | name: Optional[str] 148 | The username of the player. Defaults to None. 149 | uuid: Optional[str] 150 | The UUID of the player. Defaults to None. 151 | 152 | Returns 153 | ------- 154 | str 155 | The URL containing the image of the head. 156 | """ 157 | uuid = await cls._filter_name_uuid(name, uuid) 158 | await Utils._crafatar_fetch(Utils.url["head"].format(uuid)) 159 | 160 | return Utils._form_crafatar_url(Utils.url["head"].format(uuid)) 161 | 162 | @classmethod 163 | async def get_body( 164 | cls, name: Optional[str] = None, uuid: Optional[str] = None 165 | ) -> str: 166 | """ 167 | Get the whole body's skin of the specified player 168 | 169 | Parameters 170 | ---------- 171 | name: Optional[str] 172 | The username of the player. Defaults to None. 173 | uuid: Optional[str] 174 | The UUID of the player. Defaults to None. 175 | 176 | Returns 177 | ------- 178 | str 179 | The URL containing the image of the whole body. 180 | """ 181 | uuid = await cls._filter_name_uuid(name, uuid) 182 | await Utils._crafatar_fetch(Utils.url["body"].format(uuid)) 183 | 184 | return Utils._form_crafatar_url(Utils.url["body"].format(uuid)) 185 | -------------------------------------------------------------------------------- /hypixelio/base.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from abc import ABC, abstractmethod 3 | from datetime import datetime, timedelta 4 | from typing import Any, Dict, Optional, Union 5 | 6 | from .endpoints import API_PATH 7 | from .exceptions import HypixelAPIError, RateLimitError 8 | 9 | 10 | # TODO: Move to `requests.session` for better performance and avoid creating a new session for every request. 11 | class BaseClient(ABC): 12 | def __init__(self, api_key: Union[str, list]): 13 | self.url = API_PATH["HYPIXEL"] 14 | 15 | if not isinstance(api_key, list): 16 | self._api_key = [api_key] 17 | 18 | # Ratelimiting config 19 | self.requests_remaining = -1 20 | self.total_requests = 0 21 | self._ratelimit_reset = datetime(1998, 1, 1) 22 | self.retry_after = datetime(1998, 1, 1) 23 | 24 | # Headers 25 | from hypixelio import __version__ as hypixelio_version 26 | 27 | python_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" 28 | self.headers = { 29 | "User-Agent": f"HypixelIO v{hypixelio_version} Client (https://github.com/janaSunrise/HypixelIO) " 30 | f"Python/{python_version}" 31 | } 32 | 33 | # Define the dunder methods 34 | def __repr__(self): 35 | return ( 36 | f"<{self.__class__.__qualname__} requests_remaining={self.requests_remaining} total_requests=" 37 | f"{self.total_requests} retry_after={self.retry_after}>" 38 | ) 39 | 40 | def _update_ratelimit(self, resp_headers: Dict[str, Any]) -> None: 41 | """Utility to update ratelimiting variables""" 42 | 43 | if "RateLimit-Limit" in resp_headers: 44 | if self.total_requests == 0: 45 | self.total_requests = int(resp_headers["RateLimit-Limit"]) 46 | 47 | self.requests_remaining = int(resp_headers["RateLimit-Remaining"]) 48 | self._ratelimit_reset = datetime.now() + timedelta( 49 | seconds=int(resp_headers["RateLimit-Reset"]) 50 | ) 51 | 52 | def _is_ratelimit_hit(self) -> bool: 53 | """Utility to check if ratelimit has been hit""" 54 | 55 | is_ratelimit_hit = self.requests_remaining != -1 \ 56 | and (self.requests_remaining == 0 and self._ratelimit_reset > datetime.now()) \ 57 | or self.retry_after \ 58 | and (self.retry_after > datetime.now()) 59 | 60 | return is_ratelimit_hit 61 | 62 | def _handle_ratelimit(self, resp_headers: Dict[str, Any]) -> None: 63 | """Raise error if ratelimit has been hit""" 64 | 65 | self.requests_remaining = 0 66 | self.retry_after = datetime.now() + timedelta( 67 | seconds=int(resp_headers["Retry-After"]) 68 | ) 69 | 70 | raise RateLimitError(self.retry_after) 71 | 72 | @staticmethod 73 | def _handle_api_failure(json: Dict[str, Any]) -> None: 74 | """Handle raising error if API response is not successful.""" 75 | 76 | raise HypixelAPIError(reason=json["cause"]) 77 | 78 | @staticmethod 79 | @abstractmethod 80 | def _filter_name_uuid(name: Optional[str] = None, uuid: Optional[str] = None) -> str: 81 | ... 82 | 83 | # Utility for keys 84 | def add_key(self, api_key: Union[str, list]) -> None: 85 | """ 86 | Add a Hypixel API Key to the list of the API keys. 87 | 88 | Parameters 89 | ---------- 90 | api_key: Union[str, list] 91 | The API key(s) to be added to the lis 92 | 93 | Returns 94 | ------- 95 | None 96 | """ 97 | if isinstance(api_key, str): 98 | api_key = [api_key] 99 | 100 | for key in api_key: 101 | if key in self._api_key: 102 | continue 103 | 104 | self._api_key.append(key) 105 | 106 | def remove_key(self, api_key: Union[str, list]) -> None: 107 | """ 108 | Remove a Hypixel API Key from the list of the API keys. 109 | 110 | Parameters 111 | ---------- 112 | api_key: Union[str, list] 113 | The API key(s) to be removed from the lis 114 | 115 | Returns 116 | ------- 117 | None 118 | """ 119 | if isinstance(api_key, str): 120 | api_key = [api_key] 121 | 122 | for key in api_key: 123 | if key not in self._api_key: 124 | continue 125 | 126 | self._api_key.remove(key) 127 | -------------------------------------------------------------------------------- /hypixelio/constants.py: -------------------------------------------------------------------------------- 1 | """All the constant variables used in the Hypixel library.""" 2 | __all__ = ( 3 | "HYPIXEL_API", 4 | "MOJANG_API", 5 | "RANKS", 6 | "RANK_COLORS", 7 | "BEDWARS_PRESTIGE_COLOR", 8 | "BEDWARS_PRESTIGE_RANKS", 9 | "GUILD_COLORS__TAGS", 10 | "SKYWARS_PRESTIGES_RANKS", 11 | "SKYWARS_PRESTIGE_COLOR", 12 | "TIMEOUT", 13 | ) 14 | 15 | HYPIXEL_API = "https://api.hypixel.net" 16 | MOJANG_API = "https://api.mojang.com" 17 | 18 | TIMEOUT = 10 19 | DEFAULT_HEADERS = { 20 | "Accept-Encoding": "gzip, deflate" # To enable gzip compression and reduce bandwidth 21 | } 22 | 23 | RANKS = { 24 | "NONE": None, 25 | "VIP": "VIP", 26 | "VIP_PLUS": "VIP+", 27 | "MVP": "MVP", 28 | "MVP_PLUS": "MVP+", 29 | "SUPERSTAR": "MVP++", 30 | "YOUTUBER": "YOUTUBE", 31 | "PIG+++": "PIG+++", 32 | "BUILD TEAM": "BUILD TEAM", 33 | "HELPER": "HELPER", 34 | "MODERATOR": "MOD", 35 | "ADMIN": "ADMIN", 36 | "SLOTH": "SLOTH", 37 | "OWNER": "OWNER", 38 | } 39 | 40 | RANK_COLORS = { 41 | "VIP": int("55FF55", 16), 42 | "VIP+": int("55FF55", 16), 43 | "MVP": int("55FFFF", 16), 44 | "MVP+": int("55FFFF", 16), 45 | "MVP++": int("FFAA00", 16), 46 | "YOUTUBE": int("FF5555", 16), 47 | "PIG+++": int("FF69DC", 16), 48 | "BUILD TEAM": int("00AAAA", 16), 49 | "EVENTS": int("FFAA00", 16), 50 | "HELPER": int("5555FF", 16), 51 | "MOD": int("00AA00", 16), 52 | "ADMIN": int("AA0000", 16), 53 | "SLOTH": int("AA0000", 16), 54 | "OWNER": int("AA0000", 16), 55 | None: int("607D8B", 16), 56 | } 57 | 58 | BEDWARS_PRESTIGE_RANKS = ( 59 | "Stone", 60 | "Iron", 61 | "Gold", 62 | "Diamond", 63 | "Emerald", 64 | "Sapphire", 65 | "Ruby", 66 | "Crystal", 67 | "Opal", 68 | "Amethyst", 69 | "Rainbow", 70 | "Iron Prime", 71 | "Gold Prime", 72 | "Diamond Prime", 73 | "Emerald Prime", 74 | "Sapphire Prime", 75 | "Ruby Prime", 76 | "Crystal Prime", 77 | "Opal Prime", 78 | "Amethyst Prime", 79 | "Mirror", 80 | "Light", 81 | "Dawn", 82 | "Dusk", 83 | "Air", 84 | "Wind", 85 | "Nebula", 86 | "Thunder", 87 | "Earth", 88 | "Water", 89 | "Fire", 90 | ) 91 | 92 | SKYWARS_PRESTIGES_RANKS = ( 93 | "Stone", 94 | "Iron", 95 | "Gold", 96 | "Diamond", 97 | "Emerald", 98 | "Sapphire", 99 | "Ruby", 100 | "Crystal", 101 | "Opal", 102 | "Amethyst", 103 | "Rainbow", 104 | "Mystic", 105 | ) 106 | 107 | BEDWARS_PRESTIGE_COLOR = ( 108 | int("607D8B", 16), 109 | int("95A5A6", 16), 110 | int("FFAC0F", 16), 111 | int("55FFFF", 16), 112 | int("00AA00", 16), 113 | int("00AAAA", 16), 114 | int("AA0000", 16), 115 | int("FF69DC", 16), 116 | int("2562E9", 16), 117 | int("AA00AA", 16), 118 | int("1ABC9C", 16), 119 | int("607D8B", 16), 120 | int("95A5A6", 16), 121 | int("FFAC0F", 16), 122 | int("55FFFF", 16), 123 | int("00AA00", 16), 124 | int("00AAAA", 16), 125 | int("AA0000", 16), 126 | int("FF69DC", 16), 127 | int("2562E9", 16), 128 | int("AA00AA", 16), 129 | ) 130 | 131 | SKYWARS_PRESTIGE_COLOR = ( 132 | int("607D8B", 16), 133 | int("95A5A6", 16), 134 | int("FFAC0F", 16), 135 | int("55FFFF", 16), 136 | int("00AA00", 16), 137 | int("00AAAA", 16), 138 | int("AA0000", 16), 139 | int("FF69DC", 16), 140 | int("2562E9", 16), 141 | int("AA00AA", 16), 142 | int("AA00AA", 16), 143 | ) 144 | 145 | GUILD_COLORS__TAGS = { 146 | "GRAY": int("607D8B", 16), 147 | "GOLD": int("FFAC0F", 16), 148 | "DARK_AQUA": int("00AAAA", 16), 149 | "DARK_GREEN": int("00AA00", 16), 150 | "YELLOW": int("FFFF55", 16), 151 | } 152 | -------------------------------------------------------------------------------- /hypixelio/endpoints.py: -------------------------------------------------------------------------------- 1 | """List of all the various endpoint paths for all the APIs used.""" 2 | 3 | API_PATH = { 4 | "HYPIXEL": { 5 | "api_key": "/key", 6 | "boosters": "/boosters", 7 | "player": "/player", 8 | "friends": "/friends", 9 | "watchdog": "/watchdogstats", 10 | "guild": "/guild", 11 | "game_info": "/gameCounts", 12 | "leaderboards": "/leaderboards", 13 | "find_guild": "/findGuild", 14 | "status": "/status", 15 | "recent_games": "/recentgames", 16 | "skyblock_auctions": "/skyblock/auction", 17 | "skyblock_active_auctions": "/skyblock/auctions", 18 | "skyblock_bazaar": "/skyblock/bazaar", 19 | "skyblock_profile": "/skyblock/profile", 20 | "skyblock_news": "/skyblock/news", 21 | "skyblock_skills": "/resources/skyblock/skills", 22 | "skyblock_collections": "/resources/skyblock/collections", 23 | "achievements": "/resources/achievements", 24 | "challenges": "/resources/challenges", 25 | "quests": "/resources/quests", 26 | "guild_achievements": "/resources/guilds/achievements", 27 | }, 28 | "MOJANG": { 29 | "username_to_uuid": "/users/profiles/minecraft/{}", 30 | "uuid_to_username": "/user/profiles/{}/names", 31 | "name_history": "/user/profiles/{}/names", 32 | }, 33 | "CRAFATAR": { 34 | "avatar": "/avatars/{}", 35 | "head": "/renders/head/{}", 36 | "body": "/renders/body/{}", 37 | "skins": "/skins/{}", 38 | }, 39 | } 40 | -------------------------------------------------------------------------------- /hypixelio/exceptions.py: -------------------------------------------------------------------------------- 1 | """Custom exceptions used for the librar.""" 2 | __all__ = ( 3 | "InvalidArgumentError", 4 | "PlayerNotFoundError", 5 | "HypixelAPIError", 6 | "RateLimitError", 7 | "GuildNotFoundError", 8 | "CrafatarAPIError", 9 | "MojangAPIError", 10 | ) 11 | 12 | import typing as t 13 | from datetime import datetime 14 | 15 | 16 | class InvalidArgumentError(Exception): 17 | """Raised when invalid argument is present, or no argument is specified.""" 18 | 19 | ... 20 | 21 | 22 | class APIError(Exception): 23 | """Base class for all API exceptions.""" 24 | 25 | def __init__(self, service: str, reason: t.Optional[str] = None) -> None: 26 | error = f"There was an issue with the {service} API." 27 | if reason: 28 | error += f" Reason: {reason}." 29 | 30 | super().__init__(error) 31 | self.error = error 32 | 33 | def __str__(self) -> str: 34 | return self.error 35 | 36 | 37 | class HypixelAPIError(APIError): 38 | """Raised when there is an issue with the Hypixel API or during fetch.""" 39 | 40 | def __init__(self, reason: t.Optional[str] = None) -> None: 41 | super().__init__("Hypixel", reason) 42 | 43 | 44 | class CrafatarAPIError(APIError): 45 | """Raised during issues faced by Crafatar API.""" 46 | 47 | def __init__(self, reason: t.Optional[str] = None) -> None: 48 | super().__init__("Crafatar", reason) 49 | 50 | 51 | class MojangAPIError(APIError): 52 | """Raised when the Mojang API is facing some problems.""" 53 | 54 | def __init__(self, reason: t.Optional[str] = None) -> None: 55 | super().__init__("Mojang", reason) 56 | 57 | 58 | # Rate-limit exception 59 | class RateLimitError(Exception): 60 | """Raised when the ratelimit for the hypixel API is hit.""" 61 | 62 | def __init__(self, retry_after: datetime) -> None: 63 | error = ( 64 | "The ratelimit for the hypixel API was hit. Try again after" 65 | f"{retry_after.strftime('%Y-%m-%d %H:%M:%S')}." 66 | ) 67 | 68 | super().__init__(error) 69 | self.error = error 70 | 71 | def __str__(self) -> str: 72 | return self.error 73 | 74 | 75 | # Hypixel-related exceptions 76 | class PlayerNotFoundError(Exception): 77 | """Raised when the specified player is not found.""" 78 | 79 | def __init__( 80 | self, reason: t.Optional[str] = None, user: t.Optional[str] = None 81 | ) -> None: 82 | error = "Player not found." 83 | if reason: 84 | error += f" {reason}." 85 | 86 | super().__init__(error) 87 | 88 | self.error = error 89 | self.user = user 90 | 91 | def __str__(self) -> str: 92 | return self.error 93 | 94 | 95 | class GuildNotFoundError(Exception): 96 | """Raised when the specified guild is not found.""" 97 | 98 | def __init__(self, reason: t.Optional[str] = None) -> None: 99 | """ 100 | Parameters 101 | ---------- 102 | reason: str 103 | The reason for the Error. Defaults to None. 104 | """ 105 | error = "Guild not found." 106 | if reason: 107 | error += f" {reason}." 108 | 109 | super().__init__(error) 110 | self.error = error 111 | 112 | def __str__(self) -> str: 113 | return self.error 114 | -------------------------------------------------------------------------------- /hypixelio/lib/__init__.py: -------------------------------------------------------------------------------- 1 | from .client import Client 2 | from .converters import Converters 3 | from .utils import Utils 4 | -------------------------------------------------------------------------------- /hypixelio/lib/client.py: -------------------------------------------------------------------------------- 1 | __all__ = ("Client",) 2 | 3 | import random 4 | from types import TracebackType 5 | from typing import Any, Dict, Optional, Type, Union, cast 6 | 7 | import requests 8 | 9 | from .converters import Converters 10 | from ..base import BaseClient 11 | from ..constants import DEFAULT_HEADERS, HYPIXEL_API, TIMEOUT 12 | from ..exceptions import ( 13 | GuildNotFoundError, 14 | HypixelAPIError, 15 | InvalidArgumentError, 16 | PlayerNotFoundError, 17 | RateLimitError, 18 | ) 19 | from ..models.boosters import Boosters 20 | from ..models.find_guild import FindGuild 21 | from ..models.friends import Friends 22 | from ..models.games import Games 23 | from ..models.guild import Guild 24 | from ..models.key import Key 25 | from ..models.leaderboard import Leaderboard 26 | from ..models.player import Player 27 | from ..models.player_status import PlayerStatus 28 | from ..models.recent_games import RecentGames 29 | from ..models.skyblock import ( 30 | SkyblockActiveAuction, 31 | SkyblockBazaar, 32 | SkyblockNews, 33 | SkyblockProfile, 34 | SkyblockUserAuction, 35 | ) 36 | from ..models.watchdog import Watchdog 37 | from ..utils import form_url 38 | 39 | 40 | class Client(BaseClient): 41 | """ 42 | Client for handling requests, authentication, and usage of the Hypixel API for the end user. 43 | 44 | Examples 45 | -------- 46 | If you have a single API key, Here's how to authenticate 47 | 48 | >>> import hypixelio 49 | >>> client = hypixelio.Client(api_key="123-456-789") 50 | 51 | You can use multiple API keys to authenticate too. (Better option for load balancing) 52 | 53 | >>> client = hypixelio.Client(api_key=["123-456", "789-000", "568-908"]) 54 | """ 55 | 56 | def __init__(self, api_key: Union[str, list]) -> None: 57 | """ 58 | Parameters 59 | ---------- 60 | api_key: Union[str, list] 61 | The API key generated in Hypixel server using the `/api new` command. 62 | """ 63 | super().__init__(api_key) 64 | 65 | self._session = requests.Session() 66 | self._session.headers.update(DEFAULT_HEADERS) 67 | 68 | def _fetch( 69 | self, 70 | url: str, 71 | data: Optional[Dict[str, Any]] = None, 72 | *, 73 | api_key: bool = True, 74 | ) -> Dict[str, Any]: 75 | """ 76 | Fetch the JSON response from the API along with the ability to include GET request parameters and support 77 | Authentication using API key too. 78 | 79 | Parameters 80 | ---------- 81 | url: str 82 | The URL to be accessed from the API root URL. 83 | data: Optional[dict] 84 | The GET request's key-value pair. eg: `{"uuid": "abc"}` is converted to `?uuid=abc`. Defaults to None. 85 | api_key: bool 86 | If key is needed for the endpoin 87 | 88 | Returns 89 | ------- 90 | Dict[str, Any] 91 | The JSON response obtained after fetching the API, along with success value in the response. 92 | """ 93 | # Check if ratelimit is hit 94 | if self._is_ratelimit_hit(): 95 | raise RateLimitError(self.retry_after) 96 | 97 | # If no data for JSON 98 | if not data: 99 | data = {} 100 | 101 | # Assign a random key if the Key parameter exists. 102 | if api_key: 103 | self.headers["API-Key"] = random.choice(self._api_key) 104 | 105 | # Form the URL to fetch 106 | url = form_url(HYPIXEL_API, url, data) 107 | 108 | # Core fetch logic 109 | with self._session.get(url, timeout=TIMEOUT, headers=self.headers) as response: 110 | # 404 handling 111 | if response.status_code == 404: 112 | raise HypixelAPIError("The route specified does not exis") 113 | 114 | # 429 Code handle 115 | if response.status_code == 429: 116 | self._handle_ratelimit(cast(dict, response.headers)) 117 | 118 | # 403 Code handle 119 | if response.status_code == 403: 120 | raise HypixelAPIError("Invalid key specified!") 121 | 122 | # Ratelimit handling 123 | if api_key and "RateLimit-Limit" in response.headers: 124 | self._update_ratelimit(cast(dict, response.headers)) 125 | 126 | try: 127 | json = response.json() 128 | except Exception as exc: 129 | raise HypixelAPIError(f"{exc}") 130 | else: 131 | if not json["success"]: 132 | self._handle_api_failure(json) 133 | 134 | return json 135 | 136 | def __enter__(self) -> "Client": 137 | return self 138 | 139 | def __exit__( 140 | self, 141 | exc_type: Optional[Type[BaseException]], 142 | exc_val: Optional[BaseException], 143 | exc_tb: Optional[TracebackType], 144 | ) -> None: 145 | pass 146 | 147 | # Purge the session when the object is deleted 148 | def __del__(self) -> None: 149 | self._session.close() 150 | 151 | @staticmethod 152 | def _filter_name_uuid(name: Optional[str] = None, uuid: Optional[str] = None) -> str: 153 | if not name and not uuid: 154 | raise InvalidArgumentError( 155 | "Named argument for player's either username or UUID not found." 156 | ) 157 | 158 | if name: 159 | uuid = Converters.username_to_uuid(name) 160 | 161 | return uuid # type: ignore 162 | 163 | # Hypixel API endpoint methods. 164 | def get_key_info(self, api_key: Optional[str] = None) -> Key: 165 | """ 166 | Get info about a specific Hypixel API key. 167 | 168 | Parameters 169 | ---------- 170 | api_key: Optional[str] 171 | The API key generated in Hypixel server using the `/api new` command. Defaults to pre-specified keys. 172 | 173 | Returns 174 | ------- 175 | Key 176 | The Key object created for the API key specified. 177 | """ 178 | api_key = api_key or random.choice(self._api_key) 179 | 180 | json = self._fetch(self.url["api_key"], {"key": api_key}) 181 | return Key(json["record"]) 182 | 183 | def get_boosters(self) -> Boosters: 184 | """ 185 | Get the Hypixel coin boosters, and all the info about them. 186 | 187 | Returns 188 | ------- 189 | Boosters 190 | The boosters object, with all the info from the API. 191 | """ 192 | json = self._fetch(self.url["boosters"]) 193 | 194 | return Boosters(json["boosters"], json) 195 | 196 | def get_player( 197 | self, name: Optional[str] = None, uuid: Optional[str] = None 198 | ) -> Player: 199 | """ 200 | Get all info about a Hypixel player using his username or his player UUID. 201 | 202 | Parameters 203 | ---------- 204 | name: Optional[str] 205 | The Optional string value for the Username. Defaults to None. 206 | uuid: Optional[str] 207 | The Optional string value to the UUID. Defaults to None. 208 | 209 | Returns 210 | ------- 211 | Player 212 | The player object with all the info obtained from the API. 213 | """ 214 | uuid = self._filter_name_uuid(name, uuid) 215 | json = self._fetch(self.url["player"], {"uuid": uuid}) 216 | 217 | if not json["player"]: 218 | raise PlayerNotFoundError("Null is returned", name) 219 | 220 | return Player(json["player"]) 221 | 222 | def get_friends( 223 | self, name: Optional[str] = None, uuid: Optional[str] = None 224 | ) -> Friends: 225 | """ 226 | Get the friends, and all their info of specified Hypixel player. 227 | 228 | Parameters 229 | ---------- 230 | name: Optional[str] 231 | The Optional string value for the Username of a hypixel player. Defaults to None. 232 | uuid: Optional[str] 233 | The UUID of a Certain Hypixel Player. Defaults to None. 234 | 235 | Returns 236 | ------- 237 | Friends 238 | The Friend object with all info from the API. 239 | """ 240 | uuid = self._filter_name_uuid(name, uuid) 241 | json = self._fetch(self.url["friends"], {"uuid": uuid}) 242 | 243 | return Friends(json["records"]) 244 | 245 | def get_watchdog_info(self) -> Watchdog: 246 | """ 247 | Get all the stats about the Watchdog (Punishment stats) for the last few days/ 248 | 249 | Returns 250 | ------- 251 | Watchdog 252 | The Watchdog object with all the info. 253 | """ 254 | json = self._fetch(self.url["watchdog"]) 255 | 256 | return Watchdog(json) 257 | 258 | def get_guild( 259 | self, 260 | name: Optional[str] = None, 261 | uuid: Optional[str] = None, 262 | player_uuid: Optional[str] = None, 263 | ) -> Guild: 264 | """ 265 | Get info about a specific Hypixel guild using the Name, or the Guild's UUID. 266 | 267 | Parameters 268 | ---------- 269 | name: Optional[str] 270 | The Name of the Guild. Defaults to None. 271 | uuid: Optional[str] 272 | The ID Of the guild. Defaults to None. 273 | player_uuid: Optional[str] 274 | The UUID of the player to get guild using. Defaults to None. 275 | 276 | Returns 277 | ------- 278 | Guild 279 | The Guild object with the info fetched from the API. 280 | """ 281 | if uuid: 282 | json = self._fetch(self.url["guild"], {"id": uuid}) 283 | elif name: 284 | json = self._fetch(self.url["guild"], {"name": name}) 285 | elif player_uuid: 286 | json = self._fetch(self.url["guild"], {"player": player_uuid}) 287 | else: 288 | raise InvalidArgumentError( 289 | "Named argument for guild's name or UUID not found." 290 | ) 291 | 292 | if not json["guild"]: 293 | raise GuildNotFoundError("Value returned is null") 294 | 295 | return Guild(json["guild"]) 296 | 297 | def get_games_info(self) -> Games: 298 | """ 299 | Get the list of all Hypixel games, and their info. 300 | 301 | Returns 302 | ------- 303 | Games 304 | The Games object with all the info. 305 | """ 306 | json = self._fetch(self.url["game_info"]) 307 | 308 | return Games(json["games"], json["playerCount"]) 309 | 310 | def get_leaderboards(self) -> Leaderboard: 311 | """ 312 | Get the leaderboard for the Hypixel games with their info. 313 | 314 | Returns 315 | ------- 316 | Leaderboard 317 | The Leaderboard object with all info. 318 | """ 319 | json = self._fetch(self.url["leaderboards"]) 320 | 321 | return Leaderboard(json["leaderboards"]) 322 | 323 | def find_guild( 324 | self, guild_name: Optional[str] = None, player_uuid: Optional[str] = None 325 | ) -> FindGuild: 326 | """ 327 | Find a guild using the Guild's name or a Player's UUID. 328 | 329 | Parameters 330 | ---------- 331 | guild_name: Optional[str] 332 | The name of the Guild. Defaults to None. 333 | player_uuid: Optional[str] 334 | The UUID of the Player to find his guild. Defaults to None. 335 | 336 | Returns 337 | ------- 338 | FindGuild 339 | The ID of the guild being find. 340 | """ 341 | if guild_name: 342 | json = self._fetch(self.url["find_guild"], {"byName": guild_name}) 343 | elif player_uuid: 344 | json = self._fetch(self.url["find_guild"], {"byUuid": player_uuid}) 345 | else: 346 | raise InvalidArgumentError( 347 | "Named argument for guild's name or UUID not found." 348 | ) 349 | 350 | return FindGuild(json) 351 | 352 | def get_player_status( 353 | self, name: Optional[str] = None, uuid: Optional[str] = None 354 | ) -> PlayerStatus: 355 | """ 356 | Get the status about a Player using his username or UUID. 357 | 358 | Parameters 359 | ---------- 360 | name: Optional[str] 361 | The Optional string value for the Username. Defaults to None. 362 | uuid: Optional[str] 363 | The Optional string Value to the UUID. Defaults to None. 364 | 365 | Returns 366 | ------- 367 | PlayerStatus 368 | The Player status object consisting of all info from the API. 369 | """ 370 | uuid = self._filter_name_uuid(name, uuid) 371 | json = self._fetch(self.url["status"], {"uuid": uuid}) 372 | 373 | return PlayerStatus(json) 374 | 375 | def get_player_recent_games( 376 | self, name: Optional[str] = None, uuid: Optional[str] = None 377 | ) -> RecentGames: 378 | """ 379 | Get the recent games played by a Hypixel player using his Username or UUID. 380 | 381 | Parameters 382 | ---------- 383 | name: Optional[str] 384 | The Optional string value for the Username. Defaults to None. 385 | uuid: Optional[str] 386 | The Optional string Value to the UUID. Defaults to None. 387 | 388 | Returns 389 | ------- 390 | RecentGames 391 | The recent games for the respective player specified. 392 | """ 393 | uuid = self._filter_name_uuid(name, uuid) 394 | json = self._fetch(self.url["recent_games"], {"uuid": uuid}) 395 | 396 | return RecentGames(json) 397 | 398 | def get_skyblock_news(self) -> SkyblockNews: 399 | json = self._fetch(self.url["skyblock_news"]) 400 | 401 | return SkyblockNews(json) 402 | 403 | def get_skyblock_profile( 404 | self, name: Optional[str] = None, uuid: Optional[str] = None 405 | ) -> SkyblockProfile: 406 | """ 407 | Get the skyblock information and profile about a specific user as passed in the requirements. 408 | 409 | Parameters 410 | ---------- 411 | name: Optional[str] 412 | The player's name in Hypixel 413 | uuid: Optional[str] 414 | The player's global UUID 415 | 416 | Returns 417 | ------- 418 | SkyblockProfile 419 | The skyblock profile model for the specified user. 420 | """ 421 | uuid = self._filter_name_uuid(name, uuid) 422 | json = self._fetch(self.url["skyblock_profile"], {"profile": uuid}) 423 | 424 | if not json["profile"]: 425 | raise PlayerNotFoundError("The skyblock player does not exist!", uuid) 426 | 427 | return SkyblockProfile(json) 428 | 429 | def get_skyblock_user_auctions( 430 | self, name: Optional[str] = None, uuid: Optional[str] = None 431 | ) -> SkyblockUserAuction: 432 | """ 433 | Get the skyblock auction info about a specific user. 434 | 435 | Parameters 436 | ---------- 437 | name: Optional[str] 438 | The player's name in Hypixel 439 | uuid: Optional[str] 440 | The player's global UUID 441 | 442 | Returns 443 | ------- 444 | SkyblockUserAuction 445 | The skyblock auction model for the user. 446 | """ 447 | uuid = self._filter_name_uuid(name, uuid) 448 | json = self._fetch(self.url["skyblock_auctions"], {"profile": uuid}) 449 | 450 | if not json["auctions"]: 451 | raise PlayerNotFoundError("The skyblock player does not exist!", uuid) 452 | 453 | return SkyblockUserAuction(json) 454 | 455 | def get_skyblock_active_auctions(self, page: int = 0) -> SkyblockActiveAuction: 456 | """ 457 | Get the list of active auctions in skyblock and use the data. 458 | 459 | Parameters 460 | ---------- 461 | page: int 462 | The skyblock auction page to lookup. 463 | 464 | Returns 465 | ------- 466 | SkyblockActiveAuction 467 | The active auction model. 468 | """ 469 | json = self._fetch(self.url["skyblock_active_auctions"], {"page": page}) 470 | return SkyblockActiveAuction(json) 471 | 472 | def get_skyblock_bazaar(self) -> SkyblockBazaar: 473 | """ 474 | Get the skyblock bazaar items 475 | 476 | Returns 477 | ------- 478 | SkyblockBazaar 479 | The bazaar model object representing each produc 480 | """ 481 | json = self._fetch(self.url["skyblock_bazaar"]) 482 | return SkyblockBazaar(json) 483 | 484 | def get_resources_achievements(self) -> dict: 485 | data = self._fetch(self.url["achievements"], api_key=False) 486 | return data["achievements"] 487 | 488 | def get_resources_challenges(self) -> dict: 489 | data = self._fetch(self.url["challenges"], api_key=False) 490 | return data["challenges"] 491 | 492 | def get_resources_quests(self) -> dict: 493 | data = self._fetch(self.url["quests"], api_key=False) 494 | return data["quests"] 495 | 496 | def get_resources_guild_achievements(self) -> dict: 497 | data = self._fetch(self.url["guild_achievements"], api_key=False) 498 | return {"one_time": data["one_time"], "tiered": data["tiered"]} 499 | 500 | def get_skyblock_skills(self) -> dict: 501 | data = self._fetch(self.url["skyblock_skills"], api_key=False) 502 | return { 503 | "skills": data["skills"], 504 | "collections": data["collections"], 505 | } 506 | 507 | def get_skyblock_collections(self) -> dict: 508 | data = self._fetch(self.url["skyblock_collections"], api_key=False) 509 | return data["collections"] 510 | -------------------------------------------------------------------------------- /hypixelio/lib/converters.py: -------------------------------------------------------------------------------- 1 | __all__ = ("Converters",) 2 | 3 | from typing import Any, Dict, Union, cast 4 | 5 | import requests 6 | 7 | from ..constants import MOJANG_API, TIMEOUT 8 | from ..endpoints import API_PATH 9 | from ..exceptions import MojangAPIError, PlayerNotFoundError 10 | 11 | 12 | class Converters: 13 | url = API_PATH["MOJANG"] 14 | 15 | @classmethod 16 | def _fetch(cls, url: str) -> Union[dict, list]: 17 | """ 18 | The internal function for fetching info from the Mojang API. 19 | 20 | Parameters 21 | ---------- 22 | url: str 23 | The Mojang URL, whose JSON is supposed to be fetched. 24 | 25 | Returns 26 | ------- 27 | Union[dict, list] 28 | The JSON response from the Mojang API. 29 | """ 30 | with requests.Session() as session: 31 | with session.get(MOJANG_API + url, timeout=TIMEOUT) as response: 32 | if response.status_code == 204: 33 | raise PlayerNotFoundError( 34 | "Error code 204 returned during conversion to UUID", None 35 | ) 36 | 37 | if response.status_code == 400: 38 | raise PlayerNotFoundError("Badly formed UUID error", None) 39 | 40 | try: 41 | json = response.json() 42 | except Exception: 43 | raise MojangAPIError() 44 | else: 45 | if "error" in json: 46 | raise MojangAPIError(f"An error occurred! {json['errorMessage']}") 47 | 48 | return json 49 | 50 | @classmethod 51 | def username_to_uuid(cls, username: str) -> str: 52 | """ 53 | This is a method, to convert username in minecraft, for its respective UUID. 54 | 55 | Parameters 56 | ---------- 57 | username: str 58 | This is the minecraft user, which is passed to this function for the UUID Conversion. 59 | 60 | Returns 61 | ------- 62 | str 63 | returns the converted UUID for the respective username. 64 | """ 65 | json = cast( 66 | Dict[str, Any], 67 | Converters._fetch(Converters.url["username_to_uuid"].format(username)), 68 | ) 69 | print(json) 70 | 71 | return json["id"] 72 | 73 | @classmethod 74 | def uuid_to_username(cls, uuid: str) -> str: 75 | """ 76 | Method to convert the UUID for your profile to the username for your Minecraft accoun 77 | 78 | Parameters 79 | ---------- 80 | uuid: str 81 | This is the minecraft UUID, which is passed to this function for the UUID to username Conversion. 82 | 83 | Returns 84 | ------- 85 | str 86 | The username for the respective minecraft UUID is returned. 87 | """ 88 | json = Converters._fetch(Converters.url["uuid_to_username"].format(uuid)) 89 | 90 | return json[-1]["name"] 91 | -------------------------------------------------------------------------------- /hypixelio/lib/utils.py: -------------------------------------------------------------------------------- 1 | __all__ = ("Utils",) 2 | 3 | 4 | import typing as t 5 | 6 | import requests 7 | from requests.models import Response 8 | 9 | from ..constants import TIMEOUT 10 | from ..endpoints import API_PATH 11 | from ..exceptions import CrafatarAPIError, InvalidArgumentError 12 | from ..lib.converters import Converters 13 | 14 | 15 | class Utils: 16 | mojang_url = API_PATH["MOJANG"] 17 | url = API_PATH["CRAFATAR"] 18 | 19 | @classmethod 20 | def _crafatar_fetch(cls, url: str) -> Response: 21 | """ 22 | Method to fetch the JSON from the Crafatar API. 23 | 24 | Parameters 25 | ---------- 26 | url: str 27 | The Crafatar URL, whose JSON is supposed to be fetched. 28 | 29 | Returns 30 | ------- 31 | Response 32 | The JSON response from the Crafatar API. 33 | """ 34 | with requests.get(f"https://crafatar.com/{url}", timeout=TIMEOUT) as response: 35 | if response.status_code == 422: 36 | raise InvalidArgumentError( 37 | "Invalid URL passed. Either user does not exist, or URL is malformed." 38 | ) 39 | 40 | try: 41 | return response 42 | except Exception: 43 | raise CrafatarAPIError() 44 | 45 | @staticmethod 46 | def _filter_name_uuid( 47 | name: t.Optional[str] = None, uuid: t.Optional[str] = None 48 | ) -> str: 49 | if name is None and uuid is None: 50 | raise InvalidArgumentError( 51 | "Named argument for player's either username or UUID not found." 52 | ) 53 | 54 | if name: 55 | uuid = Converters.username_to_uuid(name) 56 | 57 | return uuid # type: ignore 58 | 59 | @classmethod 60 | def _form_crafatar_url(cls, route: str) -> str: 61 | """ 62 | This function forms the crafatar API URL for fetching USER skins. 63 | 64 | Parameters 65 | ---------- 66 | route: str 67 | The URL path to visit. 68 | 69 | Returns 70 | ------- 71 | str 72 | The well formed API URL for fetching. 73 | """ 74 | return f"https://crafatar.com{route}" 75 | 76 | @classmethod 77 | def get_name_history( 78 | cls, 79 | name: t.Optional[str] = None, 80 | uuid: t.Optional[str] = None, 81 | changed_at: bool = False, 82 | ) -> t.Union[list, dict]: 83 | """ 84 | Get the name history with records of a player. 85 | 86 | Parameters 87 | ---------- 88 | name: t.Optional[str] 89 | The username of the player. Defaults to None. 90 | uuid: t.Optional[str] 91 | The UUID of the player. Defaults to None. 92 | changed_at: bool 93 | Toggle to true, if you need when the player changed name. Defaults to False. 94 | 95 | Returns 96 | ------- 97 | t.Union[list, dict] 98 | The list or dictionary with the name history and records. 99 | """ 100 | uuid = cls._filter_name_uuid(name, uuid) 101 | json = Converters._fetch(Utils.mojang_url["name_history"].format(uuid)) 102 | 103 | # Return JSON if time is specified. 104 | if changed_at: 105 | return json 106 | 107 | # Return all usernames. 108 | usernames = [] 109 | for data in json: 110 | usernames.append(data["name"]) 111 | 112 | return usernames 113 | 114 | @classmethod 115 | def get_avatar( 116 | cls, name: t.Optional[str] = None, uuid: t.Optional[str] = None 117 | ) -> str: 118 | """ 119 | Get the avatar of the specified player. 120 | 121 | Parameters 122 | ---------- 123 | name: t.Optional[str] 124 | The username of the player. Defaults to None. 125 | uuid: t.Optional[str] 126 | The UUID of the player. Defaults to None. 127 | 128 | Returns 129 | ------- 130 | str 131 | The URL containing the image of the avatar. 132 | """ 133 | uuid = cls._filter_name_uuid(name, uuid) 134 | Utils._crafatar_fetch(Utils.url["avatar"].format(uuid)) 135 | 136 | return Utils._form_crafatar_url(Utils.url["avatar"].format(uuid)) 137 | 138 | @classmethod 139 | def get_head( 140 | cls, name: t.Optional[str] = None, uuid: t.Optional[str] = None 141 | ) -> str: 142 | """ 143 | Get the head skin of the specified player. 144 | 145 | Parameters 146 | ---------- 147 | name: t.Optional[str] 148 | The username of the player. Defaults to None. 149 | uuid: t.Optional[str] 150 | The UUID of the player. Defaults to None. 151 | 152 | Returns 153 | ------- 154 | str 155 | The URL containing the image of the head. 156 | """ 157 | uuid = cls._filter_name_uuid(name, uuid) 158 | Utils._crafatar_fetch(Utils.url["head"].format(uuid)) 159 | 160 | return Utils._form_crafatar_url(Utils.url["head"].format(uuid)) 161 | 162 | @classmethod 163 | def get_body( 164 | cls, name: t.Optional[str] = None, uuid: t.Optional[str] = None 165 | ) -> str: 166 | """ 167 | Get the whole body's skin of the specified player 168 | 169 | Parameters 170 | ---------- 171 | name: t.Optional[str] 172 | The username of the player. Defaults to None. 173 | uuid: t.Optional[str] 174 | The UUID of the player. Defaults to None. 175 | 176 | Returns 177 | ------- 178 | str 179 | The URL containing the image of the whole body. 180 | """ 181 | uuid = cls._filter_name_uuid(name, uuid) 182 | Utils._crafatar_fetch(Utils.url["body"].format(uuid)) 183 | 184 | return Utils._form_crafatar_url(Utils.url["body"].format(uuid)) 185 | -------------------------------------------------------------------------------- /hypixelio/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janaSunrise/HypixelIO/388ebcc145e754639daafb0811d709c87b2ef3e6/hypixelio/models/__init__.py -------------------------------------------------------------------------------- /hypixelio/models/boosters.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict, Iterator, List 2 | 3 | from hypixelio.utils import unix_time_to_datetime 4 | 5 | 6 | class BoosterInfo: 7 | def __init__(self, info: Dict[str, Any]) -> None: 8 | """ 9 | Parameters 10 | ---------- 11 | info: dict 12 | This contains the JSON Response from the API for the Info about a specific booster. 13 | """ 14 | self.id = info["_id"] 15 | self.purchaser_uuid = info["purchaserUuid"] 16 | 17 | self.amount = info["amount"] 18 | self.original_length = info["originalLength"] 19 | self.length = info["length"] 20 | 21 | self.game_type_code = info["gameType"] 22 | self.date_activated = unix_time_to_datetime(info["dateActivated"]) 23 | 24 | self.stacked = "stacked" in info 25 | 26 | def __eq__(self, other: "BoosterInfo") -> bool: 27 | return self.id == other.id and self.purchaser_uuid == other.purchaser_uuid 28 | 29 | def __hash__(self) -> int: 30 | return hash((self.id, self.purchaser_uuid)) 31 | 32 | def __str__(self) -> str: 33 | return self.purchaser_uuid 34 | 35 | def __repr__(self) -> str: 36 | return f'<{self.__class__.__name__} id="{self.id}" purchaser="{self.purchaser_uuid}" stacked={self.stacked}>' 37 | 38 | 39 | class Boosters: 40 | def __init__(self, boosters: List[Dict[str, Any]], json: Dict[str, Any]) -> None: 41 | """ 42 | Parameters 43 | ---------- 44 | boosters: list 45 | The list of the Coin Boosters in the Hypixel server. 46 | """ 47 | # All the boosters 48 | self.boosters = [BoosterInfo(booster) for booster in boosters] 49 | 50 | # State of the boosters 51 | self.state = json["boosterState"] 52 | 53 | # Decrementing 54 | self.decrementing = self.state["decrementing"] 55 | 56 | def __len__(self) -> int: 57 | return len(self.boosters) 58 | 59 | def __getitem__(self, key: int) -> BoosterInfo: 60 | return self.boosters[key] 61 | 62 | def __setitem__(self, key: int, value: BoosterInfo) -> None: 63 | self.boosters[key] = value 64 | 65 | def __iter__(self) -> Iterator: 66 | return iter(self.boosters) 67 | 68 | def __eq__(self, other: "Boosters") -> bool: 69 | if len(self.boosters) != len(other.boosters): 70 | return False 71 | 72 | for booster in list(zip(self.boosters, other.boosters)): 73 | if booster[0].id != booster[1].id: 74 | return False 75 | 76 | return True 77 | 78 | def __str__(self) -> str: 79 | return str(len(self.boosters)) 80 | 81 | def __repr__(self) -> str: 82 | return f"<{self.__class__.__name__} booster_count={len(self.boosters)}>" 83 | -------------------------------------------------------------------------------- /hypixelio/models/find_guild.py: -------------------------------------------------------------------------------- 1 | class FindGuild: 2 | def __init__(self, data: dict) -> None: 3 | """ 4 | Parameters 5 | ---------- 6 | data: dict 7 | The JSON data received from the Hypixel API. 8 | """ 9 | self.id = data["guild"] 10 | 11 | def __str__(self) -> str: 12 | return self.id 13 | 14 | def __repr__(self) -> str: 15 | return f'<{self.__class__.__name__} id="{self.id}">' 16 | 17 | def __hash__(self) -> int: 18 | return hash(self.id) 19 | 20 | def __eq__(self, other: "FindGuild") -> bool: 21 | return self.id == other.id 22 | -------------------------------------------------------------------------------- /hypixelio/models/friends.py: -------------------------------------------------------------------------------- 1 | import typing as t 2 | 3 | from hypixelio.utils import unix_time_to_datetime 4 | 5 | 6 | class FriendData: 7 | def __init__(self, friend: dict) -> None: 8 | """ 9 | Parameters 10 | ---------- 11 | friend: dict 12 | This contains the JSON Response for the Friend's list element API Request. 13 | """ 14 | self.request_id = friend["_id"] 15 | 16 | self.sender_id = friend["uuidSender"] 17 | self.receiver_id = friend["uuidReceiver"] 18 | 19 | self.sent_at = unix_time_to_datetime(friend["started"]) 20 | 21 | def __repr__(self) -> str: 22 | return ( 23 | f'<{self.__class__.__name__} id="{self.request_id}" sent="{self.sent_at}">' 24 | ) 25 | 26 | def __str__(self) -> str: 27 | return self.request_id 28 | 29 | def __hash__(self) -> int: 30 | return hash((self.sender_id, self.receiver_id)) 31 | 32 | def __eq__(self, other: "FriendData") -> bool: 33 | return ( 34 | self.receiver_id == other.receiver_id and self.sender_id == other.sender_id 35 | ) 36 | 37 | 38 | class Friends: 39 | def __init__(self, friends: list) -> None: 40 | """ 41 | Parameters 42 | ---------- 43 | friends: list 44 | This contains the Returned JSON Response List for the List of the friends of an user. 45 | """ 46 | self.friends = [FriendData(friend) for friend in friends] 47 | 48 | def __len__(self) -> int: 49 | return len(self.friends) 50 | 51 | def __getitem__(self, key: int) -> FriendData: 52 | return self.friends[key] 53 | 54 | def __setitem__(self, key: int, value: FriendData) -> None: 55 | self.friends[key] = value 56 | 57 | def __iter__(self) -> t.Iterator: 58 | return iter(self.friends) 59 | 60 | def __repr__(self) -> str: 61 | return f"<{self.__class__.__name__} friends_count={len(self.friends)}>" 62 | 63 | def __hash__(self) -> int: 64 | return hash(tuple(self.friends)) 65 | 66 | def __eq__(self, other: "Friends") -> bool: 67 | if len(self.friends) != len(other.friends): 68 | return False 69 | 70 | for friend in list(zip(self.friends, other.friends)): 71 | if friend[0].request_id != friend[1].request_id: 72 | return False 73 | return True 74 | -------------------------------------------------------------------------------- /hypixelio/models/games.py: -------------------------------------------------------------------------------- 1 | class GameCount: 2 | def __init__(self, game: dict) -> None: 3 | """ 4 | Parameters 5 | ---------- 6 | game: dict 7 | The Game JSON data response received from the Hypixel API. 8 | """ 9 | self.players = game["players"] 10 | self.modes = game.get("modes") 11 | 12 | def __repr__(self) -> str: 13 | return ( 14 | f'<{self.__class__.__name__} players="{self.players}" modes={self.modes}>' 15 | ) 16 | 17 | 18 | class Games: 19 | def __init__(self, games: dict, player_count: int) -> None: 20 | """ 21 | Parameters 22 | ---------- 23 | games: dict 24 | The Games JSON list data response received from the Hypixel API. 25 | player_count: int 26 | The player count in the whole Hypixel Server. 27 | """ 28 | self.player_count = player_count 29 | 30 | self.main_lobby = GameCount(games["MAIN_LOBBY"]) 31 | self.tournament_lobby = GameCount(games["TOURNAMENT_LOBBY"]) 32 | 33 | self.uhc = GameCount(games["UHC"]) 34 | self.super_smash = GameCount(games["SUPER_SMASH"]) 35 | self.legacy = GameCount(games["LEGACY"]) 36 | self.build_battle = GameCount(games["BUILD_BATTLE"]) 37 | self.battleground = GameCount(games["BATTLEGROUND"]) 38 | self.walls3 = GameCount(games["WALLS3"]) 39 | self.housing = GameCount(games["HOUSING"]) 40 | self.speed_uhc = GameCount(games["SPEED_UHC"]) 41 | self.duels = GameCount(games["DUELS"]) 42 | self.replay = GameCount(games["REPLAY"]) 43 | self.survival_games = GameCount(games["SURVIVAL_GAMES"]) 44 | self.prototype = GameCount(games["PROTOTYPE"]) 45 | self.murder_mystery = GameCount(games["MURDER_MYSTERY"]) 46 | self.mcgo = GameCount(games["MCGO"]) 47 | self.bedwars = GameCount(games["BEDWARS"]) 48 | self.skyblock = GameCount(games["SKYBLOCK"]) 49 | self.arcade = GameCount(games["ARCADE"]) 50 | self.pit = GameCount(games["PIT"]) 51 | self.tnt_games = GameCount(games["TNTGAMES"]) 52 | self.skywars = GameCount(games["SKYWARS"]) 53 | self.limbo = GameCount(games["LIMBO"]) 54 | 55 | self.idle = GameCount(games["IDLE"]) 56 | self.queue = GameCount(games["QUEUE"]) 57 | 58 | def __repr__(self) -> str: 59 | return ( 60 | f"<{self.__class__.__name__} lobby={self.main_lobby} idle={self.idle} queue={self.queue} " 61 | f"players={self.player_count}>" 62 | ) 63 | -------------------------------------------------------------------------------- /hypixelio/models/guild.py: -------------------------------------------------------------------------------- 1 | from hypixelio.utils import unix_time_to_datetime 2 | 3 | 4 | class Guild: 5 | def __init__(self, data: dict) -> None: 6 | """ 7 | Parameters 8 | ---------- 9 | data: dict 10 | The JSON data received from the Hypixel API. 11 | """ 12 | self.hypixel_id = data["_id"] 13 | self.name = data["name"] 14 | 15 | # Coins the guild holds 16 | self.coins = data["coins"] 17 | 18 | # When the guild was created 19 | self.created = unix_time_to_datetime(data["created"]) 20 | 21 | # Member count of the guild 22 | self.members = data["members"] 23 | 24 | # Old ranking data, and experience 25 | self.legacy_ranking = data["legacyRanking"] 26 | self.experience = data["exp"] 27 | 28 | self.achievements = data["achievements"] 29 | self.experience_by_game = data["guildExpByGameType"] 30 | 31 | def __str__(self) -> str: 32 | return self.name 33 | 34 | def __repr__(self) -> str: 35 | return f'<{self.__class__.__name__} id="{self.hypixel_id}" name="{self.name}" experience="{self.experience}">' 36 | 37 | def __hash__(self) -> int: 38 | return hash(self.hypixel_id) 39 | 40 | def __eq__(self, other: "Guild") -> bool: 41 | return self.hypixel_id == other.name 42 | -------------------------------------------------------------------------------- /hypixelio/models/key.py: -------------------------------------------------------------------------------- 1 | class Key: 2 | def __init__(self, data: dict) -> None: 3 | """ 4 | Parameters 5 | ---------- 6 | data: dict 7 | The JSON data received from the Hypixel API. 8 | """ 9 | self.key = data["key"] 10 | self.owner_uuid = data["owner"] 11 | 12 | # Queries info 13 | self.query_limit = data["limit"] 14 | self.queries_in_past_minute = data["queriesInPastMin"] 15 | self.total_queries = data["totalQueries"] 16 | 17 | def __str__(self) -> str: 18 | return self.key 19 | 20 | def __repr__(self) -> str: 21 | return f'<{self.__class__.__name__} key="{self.key}" owner="{self.owner_uuid}">' 22 | 23 | def __hash__(self) -> int: 24 | return hash(self.key) 25 | 26 | def __eq__(self, other: "Key") -> bool: 27 | return self.key == other.key 28 | -------------------------------------------------------------------------------- /hypixelio/models/leaderboard.py: -------------------------------------------------------------------------------- 1 | class LeaderboardData: 2 | def __init__(self, data: dict) -> None: 3 | """ 4 | Parameters 5 | ---------- 6 | data: dict 7 | The Leaderboard JSON data per game response received from the Hypixel API. 8 | """ 9 | self.path = data["path"] 10 | self.prefix = data["prefix"] 11 | 12 | self.title = data["title"] 13 | 14 | self.location = data["location"] 15 | self.count = data["count"] 16 | 17 | self.leaders_uuid = data["leaders"] 18 | 19 | def __repr__(self) -> str: 20 | return f'<{self.__class__.__name__} title="{self.title}" location="{self.location}">' 21 | 22 | def __str__(self) -> str: 23 | return self.title 24 | 25 | 26 | class Leaderboard: 27 | def __init__(self, board: dict) -> None: 28 | """ 29 | Parameters 30 | ---------- 31 | board: dict 32 | The Leaderboard JSON data response received from the Hypixel API. 33 | """ 34 | self.arena = [LeaderboardData(arena) for arena in board["ARENA"]] 35 | self.mcgo = [LeaderboardData(arena) for arena in board["MCGO"]] 36 | self.battleground = [LeaderboardData(arena) for arena in board["BATTLEGROUND"]] 37 | self.survival_games = [ 38 | LeaderboardData(arena) for arena in board["SURVIVAL_GAMES"] 39 | ] 40 | self.uhc = [LeaderboardData(arena) for arena in board["UHC"]] 41 | self.walls = [LeaderboardData(arena) for arena in board["WALLS"]] 42 | self.paintball = [LeaderboardData(arena) for arena in board["PAINTBALL"]] 43 | self.skywars = [LeaderboardData(arena) for arena in board["SKYWARS"]] 44 | self.murder_mystery = [ 45 | LeaderboardData(arena) for arena in board["MURDER_MYSTERY"] 46 | ] 47 | self.super_smash = [LeaderboardData(arena) for arena in board["SUPER_SMASH"]] 48 | self.duels = [LeaderboardData(arena) for arena in board["DUELS"]] 49 | self.speed_uhc = [LeaderboardData(arena) for arena in board["SPEED_UHC"]] 50 | self.tnt_games = [LeaderboardData(arena) for arena in board["TNTGAMES"]] 51 | self.bedwars = [LeaderboardData(arena) for arena in board["BEDWARS"]] 52 | self.gingerbread = [LeaderboardData(arena) for arena in board["GINGERBREAD"]] 53 | self.build_battle = [LeaderboardData(arena) for arena in board["BUILD_BATTLE"]] 54 | self.arcade = [LeaderboardData(arena) for arena in board["ARCADE"]] 55 | self.skyclash = [LeaderboardData(arena) for arena in board["SKYCLASH"]] 56 | self.quakecraft = [LeaderboardData(arena) for arena in board["QUAKECRAFT"]] 57 | self.true_combat = [LeaderboardData(arena) for arena in board["TRUE_COMBAT"]] 58 | self.walls3 = [LeaderboardData(arena) for arena in board["WALLS3"]] 59 | self.vampirez = [LeaderboardData(arena) for arena in board["VAMPIREZ"]] 60 | -------------------------------------------------------------------------------- /hypixelio/models/player.py: -------------------------------------------------------------------------------- 1 | import typing as t 2 | from dataclasses import dataclass 3 | 4 | from hypixelio.utils import get_rank, unix_time_to_datetime 5 | 6 | 7 | @dataclass 8 | class PlayerSocialMedia: 9 | youtube: t.Optional[str] 10 | twitter: t.Optional[str] 11 | instagram: t.Optional[str] 12 | twitch: t.Optional[str] 13 | discord: t.Optional[str] 14 | hypixel_forums: t.Optional[str] 15 | 16 | # Convert JSON data to a PlayerSocialMedia object 17 | @classmethod 18 | def from_json(cls, data: t.Optional[dict]) -> t.Optional["PlayerSocialMedia"]: 19 | if not data: 20 | return None 21 | 22 | return cls( 23 | youtube=data.get("YOUTUBE"), 24 | twitter=data.get("TWITTER"), 25 | instagram=data.get("INSTAGRAM"), 26 | twitch=data.get("TWITCH"), 27 | discord=data.get("DISCORD"), 28 | hypixel_forums=data.get("HYPIXEL_FORUMS"), 29 | ) 30 | 31 | 32 | class Player: 33 | def __init__(self, data: dict) -> None: 34 | """ 35 | Parameters 36 | ---------- 37 | data: dict 38 | The JSON data received from the Hypixel API. 39 | """ 40 | self.hypixel_id = data["_id"] 41 | self.uuid = data["uuid"] 42 | 43 | self.name = data["displayname"] 44 | self.known_aliases = data["knownAliases"] 45 | 46 | self.first_login = unix_time_to_datetime(data["firstLogin"]) 47 | self.last_login = unix_time_to_datetime(data["lastLogin"]) 48 | self.last_logout = unix_time_to_datetime(data["lastLogout"]) 49 | 50 | self.one_time_achievements = data["achievementsOneTime"] 51 | self.achievement_points = data["achievementPoints"] 52 | self.achievements = data["achievements"] 53 | 54 | self.experience = data["networkExp"] 55 | self.level = self._calc_player_level(self.experience) 56 | 57 | self.karma = data["karma"] 58 | self.mc_version_rp = data.get("mcVersionRp") 59 | 60 | self.challenges = data["challenges"]["all_time"] 61 | self.most_recent_game = data["mostRecentGameType"] 62 | 63 | self.total_rewards = data.get("totalRewards") 64 | self.total_daily_rewards = data.get("totalDailyRewards") 65 | self.reward_streak = data.get("rewardStreak") 66 | self.reward_score = data.get("rewardScore") 67 | self.reward_high_score = data.get("rewardHighScore") 68 | 69 | self.pet_stats = data.get("petStats") 70 | self.current_gadget = data.get("currentGadget") 71 | 72 | self.social_media = PlayerSocialMedia.from_json( 73 | data.get("socialMedia", {}).get("links") 74 | ) 75 | 76 | self.rank = self._get_rank(data) 77 | 78 | @staticmethod 79 | def _calc_player_level(xp: t.Union[float, int]) -> float: 80 | return 1 + (-8750.0 + (8750**2 + 5000 * xp) ** 0.5) / 2500 81 | 82 | @staticmethod 83 | def _get_rank(data: dict) -> t.Optional[str]: 84 | return get_rank( 85 | data.get("rank"), 86 | data.get("prefix"), 87 | data.get("monthlyPackageRank"), 88 | data.get("newPackageRank"), 89 | data.get("packageRank"), 90 | ) 91 | 92 | def __str__(self) -> str: 93 | return self.name 94 | 95 | def __repr__(self) -> str: 96 | return f'<{self.__class__.__name__} id="{self.hypixel_id}" name="{self.name}" experience="{self.experience}">' 97 | 98 | def __hash__(self) -> int: 99 | return hash(self.uuid) 100 | 101 | def __eq__(self, other: "Player") -> bool: 102 | return self.uuid == other.uuid 103 | -------------------------------------------------------------------------------- /hypixelio/models/player_status.py: -------------------------------------------------------------------------------- 1 | class PlayerStatus: 2 | def __init__(self, data: dict) -> None: 3 | """ 4 | Parameters 5 | ---------- 6 | data: dict 7 | The JSON data received from the Hypixel API. 8 | """ 9 | self.uuid = data["uuid"] 10 | 11 | self.online = data["session"]["online"] 12 | self.game_type = data["session"].get("gameType") 13 | self.mode = data["session"].get("mode") 14 | self.map = data["session"].get("map") 15 | 16 | def __str__(self) -> str: 17 | return self.uuid 18 | 19 | def __repr__(self) -> str: 20 | return f'<{self.__class__.__name__} uuid="{self.uuid}" online="{self.online}">' 21 | 22 | def __hash__(self) -> int: 23 | return hash(self.uuid) 24 | 25 | def __eq__(self, other: "PlayerStatus") -> bool: 26 | return self.uuid == other.uuid 27 | -------------------------------------------------------------------------------- /hypixelio/models/recent_games.py: -------------------------------------------------------------------------------- 1 | import typing as t 2 | 3 | from hypixelio.utils import unix_time_to_datetime 4 | 5 | 6 | class RecentGameInfo: 7 | def __init__(self, game: dict) -> None: 8 | """ 9 | Parameters 10 | ---------- 11 | game: dict 12 | The game's JSON data received from the Hypixel API. 13 | """ 14 | self.date = unix_time_to_datetime(game["date"]) 15 | self.end_datetime = unix_time_to_datetime(game["ended"]) 16 | 17 | self.game_type = game["gameType"] 18 | self.mode = game.get("mode") 19 | self.map = game["map"] 20 | 21 | def __hash__(self) -> int: 22 | return hash((self.date, self.map)) 23 | 24 | def __str__(self) -> str: 25 | return self.game_type 26 | 27 | def __repr__(self) -> str: 28 | return f'<{self.__class__.__name__} game_type="{self.game_type}" mode="{self.mode}" map="{self.map}">' 29 | 30 | 31 | class RecentGames: 32 | def __init__(self, data: dict) -> None: 33 | """ 34 | Parameters 35 | ---------- 36 | data: dict 37 | The JSON data received from the Hypixel API. 38 | """ 39 | self.uuid = data["uuid"] 40 | self.games = [RecentGameInfo(game) for game in data["games"]] 41 | 42 | # Length of the list 43 | def __len__(self) -> int: 44 | return len(self.games) 45 | 46 | # Get items 47 | def __getitem__(self, key: int) -> RecentGameInfo: 48 | return self.games[key] 49 | 50 | def __setitem__(self, key: int, value: RecentGameInfo) -> None: 51 | self.games[key] = value 52 | 53 | # Looping 54 | def __iter__(self) -> t.Iterator: 55 | return iter(self.games) 56 | 57 | # Other dunder methods 58 | def __str__(self) -> str: 59 | return self.uuid 60 | 61 | def __repr__(self) -> str: 62 | return f'<{self.__class__.__name__} uuid="{self.uuid}">' 63 | 64 | def __hash__(self) -> int: 65 | return hash(self.uuid) 66 | 67 | def __eq__(self, other: "RecentGames") -> bool: 68 | return self.uuid == other.uuid 69 | -------------------------------------------------------------------------------- /hypixelio/models/skyblock/__init__.py: -------------------------------------------------------------------------------- 1 | from .active_auctions import SkyblockActiveAuction 2 | from .bazaar import SkyblockBazaar 3 | from .news import SkyblockNews 4 | from .profile import SkyblockProfile 5 | from .user_auction import SkyblockUserAuction 6 | -------------------------------------------------------------------------------- /hypixelio/models/skyblock/active_auctions/__init__.py: -------------------------------------------------------------------------------- 1 | from .active_auctions import SkyblockActiveAuction 2 | -------------------------------------------------------------------------------- /hypixelio/models/skyblock/active_auctions/active_auctions.py: -------------------------------------------------------------------------------- 1 | import typing as t 2 | 3 | from hypixelio.models.skyblock.auction import SkyblockAuction 4 | 5 | 6 | class SkyblockActiveAuction: 7 | def __init__(self, data: dict) -> None: 8 | """ 9 | Parameters 10 | ---------- 11 | data: dict 12 | The data from the Hypixel API endpoint. 13 | """ 14 | self.page_number = data["page"] 15 | self.total_pages = data["totalPages"] 16 | self.total_auctions = data["totalAuctions"] 17 | 18 | self.auctions = [SkyblockAuction(auction) for auction in data["auctions"]] 19 | 20 | def __len__(self) -> int: 21 | return len(self.auctions) 22 | 23 | def __getitem__(self, key: int) -> SkyblockAuction: 24 | return self.auctions[key] 25 | 26 | def __setitem__(self, key: int, value: SkyblockAuction) -> None: 27 | self.auctions[key] = value 28 | 29 | def __iter__(self) -> t.Iterator: 30 | return iter(self.auctions) 31 | -------------------------------------------------------------------------------- /hypixelio/models/skyblock/auction.py: -------------------------------------------------------------------------------- 1 | class SkyblockAuction: 2 | def __init__(self, auction_data: dict) -> None: 3 | """ 4 | Parameters 5 | ---------- 6 | auction_data: dict 7 | The auction JSON model to be parsed. 8 | """ 9 | self.id = auction_data["_id"] 10 | self.uuid = auction_data["uuid"] 11 | 12 | self.auctioneer = auction_data["auctioneer"] 13 | 14 | self.item_name = auction_data["item_name"] 15 | self.item_lore = auction_data["item_lore"] 16 | 17 | self.category = auction_data["category"] 18 | self.tier = auction_data["tier"] 19 | self.starting_big = auction_data["starting_bid"] 20 | 21 | self.claimed_bidders = auction_data.get("claimed_bidders") 22 | self.bids = auction_data.get("bids") 23 | self.highest_bid = auction_data.get("highest_bid_amount") 24 | 25 | def __str__(self) -> str: 26 | return self.id 27 | 28 | def __repr__(self) -> str: 29 | return f'<{self.__class__.__name__} id="{self.id}" uuid="{self.uuid}">' 30 | 31 | def __hash__(self) -> int: 32 | return hash(self.id) 33 | 34 | def __eq__(self, other: "SkyblockAuction") -> bool: 35 | return self.id == other.id 36 | -------------------------------------------------------------------------------- /hypixelio/models/skyblock/bazaar/__init__.py: -------------------------------------------------------------------------------- 1 | from .skyblock_bazaar import SkyblockBazaar 2 | -------------------------------------------------------------------------------- /hypixelio/models/skyblock/bazaar/bazaar_item.py: -------------------------------------------------------------------------------- 1 | class SkyblockBazaarItem: 2 | def __init__(self, data: dict) -> None: 3 | """ 4 | Parameters 5 | ---------- 6 | data: dict 7 | The product data from the Hypixel bazaar API endpoint. 8 | """ 9 | self.product_id = data["productId"] 10 | 11 | self.sell_price = data["sellPrice"] 12 | self.sell_volume = data["sellVolume"] 13 | self.sell_moving_week = data["sellMovingWeek"] 14 | self.sell_orders = data["sellOrders"] 15 | 16 | self.buy_price = data["buyPrice"] 17 | self.buy_volume = data["buyVolume"] 18 | self.buy_moving_week = data["buyMovingWeek"] 19 | self.buy_orders = data["buyOrders"] 20 | 21 | def __str__(self) -> str: 22 | return self.product_id 23 | 24 | def __repr__(self) -> str: 25 | return ( 26 | f'<{self.__class__.__name__} id="{self.product_id}" sell_price="{self.sell_price}" ' 27 | f'buy_price="{self.buy_price}">' 28 | ) 29 | 30 | def __hash__(self) -> int: 31 | return hash(self.product_id) 32 | 33 | def __eq__(self, other: "SkyblockBazaarItem") -> bool: 34 | return self.product_id == other.product_id 35 | -------------------------------------------------------------------------------- /hypixelio/models/skyblock/bazaar/skyblock_bazaar.py: -------------------------------------------------------------------------------- 1 | from .bazaar_item import SkyblockBazaarItem 2 | 3 | 4 | class SkyblockBazaar: 5 | def __init__(self, data: dict) -> None: 6 | """ 7 | Parameters 8 | ---------- 9 | data: dict 10 | The data from the Hypixel API endpoint. 11 | """ 12 | self.last_updated = data["lastUpdated"] 13 | self.products = {} 14 | 15 | for key, value in data["products"].items(): 16 | bazaar_item_object = SkyblockBazaarItem(value["quick_status"]) 17 | self.products[key] = bazaar_item_object 18 | -------------------------------------------------------------------------------- /hypixelio/models/skyblock/news.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class NewsItem: 6 | title: str 7 | text: str 8 | link: str 9 | item: dict 10 | 11 | 12 | class SkyblockNews: 13 | def __init__(self, data: dict) -> None: 14 | """ 15 | Parameters 16 | ---------- 17 | data: dict 18 | The JSON data to be parsed. 19 | """ 20 | news = [] 21 | 22 | for news_data in data["items"]: 23 | news.append( 24 | NewsItem( 25 | news_data["title"], 26 | news_data["text"], 27 | news_data["link"], 28 | news_data["item"], 29 | ) 30 | ) 31 | 32 | self.news = news 33 | 34 | def __repr__(self) -> str: 35 | return f"<{self.__class__.__qualname__} news_amount={len(self.news)}>" 36 | 37 | def __len__(self) -> int: 38 | return len(self.news) 39 | 40 | def __iter__(self): 41 | return iter(self.news) 42 | -------------------------------------------------------------------------------- /hypixelio/models/skyblock/profile/__init__.py: -------------------------------------------------------------------------------- 1 | from .profile import SkyblockProfile 2 | -------------------------------------------------------------------------------- /hypixelio/models/skyblock/profile/profile.py: -------------------------------------------------------------------------------- 1 | import typing as t 2 | 3 | from .profile_member import SkyblockProfileMember 4 | 5 | 6 | class SkyblockProfile: 7 | def __init__(self, data: dict) -> None: 8 | """ 9 | Parameters 10 | ---------- 11 | data: dict 12 | The JSON data received from the Hypixel API. 13 | """ 14 | self.__profile_json = data["profile"] 15 | 16 | self.profile_id = self.__profile_json["profile_id"] 17 | self.members = [ 18 | SkyblockProfileMember(self.__profile_json["members"][member]) 19 | for member in self.__profile_json["members"] 20 | ] 21 | self.community_upgrades = self.__profile_json["community_upgrades"] 22 | 23 | def __str__(self) -> str: 24 | return self.profile_id 25 | 26 | def __repr__(self) -> str: 27 | return f'<{self.__class__.__name__} id="{self.profile_id}" member_count="{len(self.members)}">' 28 | 29 | def __hash__(self) -> int: 30 | return hash(self.profile_id) 31 | 32 | def __eq__(self, other: "SkyblockProfile") -> bool: 33 | return self.profile_id == other.profile_id 34 | 35 | def __len__(self) -> int: 36 | return len(self.members) 37 | 38 | def __getitem__(self, key: int) -> SkyblockProfileMember: 39 | return self.members[key] 40 | 41 | def __setitem__(self, key: int, value: SkyblockProfileMember) -> None: 42 | self.members[key] = value 43 | 44 | def __iter__(self) -> t.Iterator: 45 | return iter(self.members) 46 | -------------------------------------------------------------------------------- /hypixelio/models/skyblock/profile/profile_member.py: -------------------------------------------------------------------------------- 1 | class SkyblockProfileMember: 2 | def __init__(self, member_data: dict) -> None: 3 | """ 4 | Parameters 5 | ---------- 6 | data: dict 7 | The JSON data received from the Hypixel API. 8 | """ 9 | self.coin_purse = member_data["coin_purse"] 10 | self.death_count = member_data["death_count"] 11 | self.fairy_souls_collected = member_data.get("fairy_souls_collected") 12 | self.fishing_treasure_count = member_data.get("fishing_treasure_caught") 13 | 14 | self.stats = member_data["stats"] 15 | self.objectives = member_data["objectives"] 16 | 17 | self.crafted_generators = member_data.get("crafted_generators") 18 | self.visited_zones = member_data.get("visited_zones") 19 | self.achievement_spawned_island_types = member_data.get( 20 | "achievement_spawned_island_types" 21 | ) 22 | 23 | self.slayer_quest = member_data.get("slayer_quest") 24 | self.slayer_bosses = member_data.get("slayer_bosses") 25 | 26 | self.pets = member_data["pets"] 27 | self.griffin = member_data["griffin"] 28 | 29 | self.unlocked_collection_tiers = member_data.get("unlocked_coll_tiers") 30 | 31 | self.skills = { 32 | "alchemy": member_data.get("experience_skill_alchemy"), 33 | "farming": member_data.get("experience_skill_farming"), 34 | "taming": member_data.get("experience_skill_taming"), 35 | "enchanting": member_data.get("experience_skill_enchanting"), 36 | "fishing": member_data.get("experience_skill_fishing"), 37 | "foraging": member_data.get("experience_skill_foraging"), 38 | "carpentry": member_data.get("experience_skill_carpentry"), 39 | "runecrafting": member_data.get("experience_skill_runecrafting"), 40 | "combat": member_data.get("experience_skill_combat"), 41 | "mining": member_data.get("experience_skill_mining"), 42 | } 43 | 44 | self.collection = member_data.get("collection") 45 | -------------------------------------------------------------------------------- /hypixelio/models/skyblock/user_auction/__init__.py: -------------------------------------------------------------------------------- 1 | from .user_auction import SkyblockUserAuction 2 | -------------------------------------------------------------------------------- /hypixelio/models/skyblock/user_auction/user_auction.py: -------------------------------------------------------------------------------- 1 | import typing as t 2 | 3 | from hypixelio.models.skyblock.auction import SkyblockAuction 4 | 5 | 6 | class SkyblockUserAuction: 7 | def __init__(self, data: dict) -> None: 8 | """ 9 | Parameters 10 | ---------- 11 | data: dict 12 | The data from the Hypixel API endpoint. 13 | """ 14 | self.auctions = [SkyblockAuction(auction) for auction in data["auctions"]] 15 | 16 | def __len__(self) -> int: 17 | return len(self.auctions) 18 | 19 | def __getitem__(self, key: int) -> SkyblockAuction: 20 | return self.auctions[key] 21 | 22 | def __setitem__(self, key: int, value: SkyblockAuction) -> None: 23 | self.auctions[key] = value 24 | 25 | def __iter__(self) -> t.Iterator: 26 | return iter(self.auctions) 27 | -------------------------------------------------------------------------------- /hypixelio/models/watchdog.py: -------------------------------------------------------------------------------- 1 | class Watchdog: 2 | def __init__(self, data: dict) -> None: 3 | """ 4 | Parameters 5 | ---------- 6 | data: dict 7 | The JSON data received from the Hypixel API. 8 | """ 9 | self.last_minute_ban = data["watchdog_lastMinute"] 10 | 11 | self.staff_rolling_daily = data["staff_rollingDaily"] 12 | self.rolling_daily = data["watchdog_rollingDaily"] 13 | 14 | self.staff_total_bans = data["staff_total"] 15 | self.total_bans = data["watchdog_total"] 16 | 17 | def __str__(self) -> str: 18 | return self.last_minute_ban 19 | 20 | def __repr__(self) -> str: 21 | return f"<{self.__class__.__name__} last_minute_ban={self.last_minute_ban} rolling_daily={self.rolling_daily}>" 22 | -------------------------------------------------------------------------------- /hypixelio/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janaSunrise/HypixelIO/388ebcc145e754639daafb0811d709c87b2ef3e6/hypixelio/py.typed -------------------------------------------------------------------------------- /hypixelio/utils.py: -------------------------------------------------------------------------------- 1 | import math 2 | import re 3 | from datetime import datetime 4 | from typing import Any, Dict, Optional, Union 5 | 6 | from .constants import RANKS, RANK_COLORS 7 | 8 | 9 | def form_url(main_url: str, url: str, data: Optional[Dict[str, Any]] = None) -> str: 10 | if not data: 11 | data = {} 12 | 13 | url = main_url + url if url.startswith("/") else url 14 | url += "?" + "&".join( 15 | [f"{dict_key}={dict_value}" for dict_key, dict_value in data.items()] 16 | ) 17 | 18 | return url 19 | 20 | 21 | # Convert unix time to datetime 22 | def unix_time_to_datetime(unix_time: int) -> datetime: 23 | return datetime.fromtimestamp(float(unix_time) / 1000) 24 | 25 | 26 | # Other useful functions 27 | def get_ratio( 28 | positive_stat: Union[int, float], negative_stat: Union[int, float] 29 | ) -> Union[int, float]: 30 | try: 31 | ratio = positive_stat / negative_stat 32 | return round(ratio, 2) 33 | except ZeroDivisionError: 34 | return float("inf") if positive_stat > 0 else 0 35 | 36 | 37 | def get_ratio_next(ratio: Union[int, float]) -> Union[int, float]: 38 | if ratio == float("inf"): 39 | return ratio 40 | return math.trunc(ratio) + 1 41 | 42 | 43 | def get_level_percentage(level: float) -> Union[int, float]: 44 | return round((level - math.trunc(level)) * 100, 2) 45 | 46 | 47 | def get_network_level(experience: Union[int, float]) -> Union[int, float]: 48 | return math.trunc(get_network_level_exact(experience)) 49 | 50 | 51 | def get_network_level_exact(experience: Union[int, float]) -> Union[int, float]: 52 | return (math.sqrt(experience + 15312.5) - 88.38834764831843) / 35.35533905932738 53 | 54 | 55 | def get_rank( 56 | rank: Optional[str] = None, 57 | prefix_raw: Optional[str] = None, 58 | monthly_package_rank: Optional[str] = None, 59 | new_package_rank: Optional[str] = None, 60 | package_rank: Optional[str] = None, 61 | ) -> Optional[str]: 62 | real_rank = None 63 | 64 | if prefix_raw: 65 | prefix = re.sub(r"§.", "", prefix_raw)[1:-1] 66 | real_rank = RANKS.get(prefix, prefix) 67 | elif rank and rank != "NORMAL" and not real_rank: 68 | real_rank = RANKS.get(rank, rank) 69 | elif new_package_rank and not real_rank: 70 | real_rank = RANKS.get(new_package_rank, new_package_rank) 71 | elif package_rank and not real_rank: 72 | real_rank = RANKS.get(package_rank, package_rank) 73 | elif (monthly_package_rank and monthly_package_rank != "NONE") and not real_rank: 74 | real_rank = RANKS.get(monthly_package_rank, monthly_package_rank) 75 | 76 | return real_rank 77 | 78 | 79 | def get_rank_color(rank: str) -> int: 80 | return RANK_COLORS[rank] 81 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=42", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | -------------------------------------------------------------------------------- /pyrightconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": ["build", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import re 2 | from itertools import chain 3 | from pathlib import Path 4 | 5 | import setuptools 6 | 7 | # Constant variables 8 | BASE_DIR = Path(__file__).resolve().parent 9 | 10 | README = Path(BASE_DIR / "README.md").read_text() 11 | 12 | URL = "https://github.com/janaSunrise/HypixelIO" 13 | 14 | # Version locating and assignment 15 | VERSION = re.search( 16 | r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', 17 | Path(BASE_DIR / "hypixelio/__init__.py").read_text(), 18 | re.MULTILINE, 19 | ).group( # type: ignore 20 | 1 21 | ) 22 | 23 | # Version error 24 | if not VERSION: 25 | raise RuntimeError("VERSION is not set.") 26 | 27 | # Dependencies configuration 28 | extras_require = {"speedups": ["aiodns==3.0.0", "Brotli==1.0.9", "cchardet==2.1.7"]} 29 | extras_require["all"] = list(chain.from_iterable(extras_require.values())) 30 | 31 | # Main setup 32 | setuptools.setup( 33 | name="HypixelIO", 34 | version=VERSION, 35 | author="Sunrit Jana", 36 | author_email="warriordefenderz@gmail.com", 37 | description="A modern, efficient and better way of interacting with the Hypixel API!", 38 | long_description=README, 39 | long_description_content_type="text/markdown", 40 | license="MIT", 41 | url=URL, 42 | project_urls={"Documentation": URL, "Issue tracker": f"{URL}/issues"}, 43 | packages=setuptools.find_packages(exclude=["tests", "tests.*", "tools", "tools.*"]), 44 | package_data={"hypixelio": ["py.typed"]}, 45 | install_requires=[ 46 | "requests==2.28.2", 47 | "aiohttp==3.8.3", 48 | ], 49 | extras_require=extras_require, 50 | classifiers=[ 51 | "Programming Language :: Python :: 3", 52 | "Programming Language :: Python :: 3.7", 53 | "Programming Language :: Python :: 3.8", 54 | "Programming Language :: Python :: 3.9", 55 | "Programming Language :: Python :: Implementation :: CPython", 56 | "License :: OSI Approved :: MIT License", 57 | "Operating System :: OS Independent", 58 | "Intended Audience :: Developers", 59 | "Natural Language :: English", 60 | "Typing :: Typed", 61 | ], 62 | python_requires=">=3.7", 63 | ) 64 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janaSunrise/HypixelIO/388ebcc145e754639daafb0811d709c87b2ef3e6/tests/__init__.py -------------------------------------------------------------------------------- /tests/mock_data/player_data.py: -------------------------------------------------------------------------------- 1 | PLAYER_MOCK = { 2 | "_id": 1234, 3 | "uuid": "2ad3kfei9ikmd", 4 | "displayname": "TestPlayer123", 5 | "knownAliases": ["1234", "test", "TestPlayer123"], 6 | "firstLogin": 123456, 7 | "lastLogin": 150996, 8 | "lastLogout": 178901, 9 | "achievementsOneTime": ["MVP", "MVP2", "BedwarsMVP"], 10 | "achievementPoints": 300, 11 | "achievements": {"bedwars_level": 5, "general_challenger": 7, "bedwars_wins": 18}, 12 | "networkExp": 3400, 13 | "challenges": {"all_time": {"DUELS_challenge": 1, "BEDWARS_challenge": 6}}, 14 | "mostRecentGameType": "BEDWARS", 15 | "socialMedia": {"links": {"DISCORD": "test#1234"}}, 16 | "karma": 8888, 17 | "mcVersionRp": "1.8.9", 18 | "petStats": {}, 19 | "currentGadget": "Dummy thingy", 20 | "rank": None, 21 | } 22 | -------------------------------------------------------------------------------- /tests/test_converters.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from hypixelio import Converters as Conv 4 | 5 | 6 | class TestUsernameToUUID(unittest.TestCase): 7 | """Test for the Username to UUID conversion.""" 8 | 9 | def test_conversion(self) -> None: 10 | test_cases = ( 11 | ("janaSunrise", "c8438cdd126043448cca9e28646efbe7"), 12 | ("JanaSunrise123", "b4ead04f4e2d484ba70257d5729aa773"), 13 | ("VSCode_", "2a13b3a34bf343fa9d8db0f87187da39"), 14 | ) 15 | 16 | for username, uuid in test_cases: 17 | self.assertEqual(Conv.username_to_uuid(username), uuid) 18 | 19 | 20 | class TestUUIDToUsername(unittest.TestCase): 21 | """Test for the UUID to Username conversion.""" 22 | 23 | def test_conversion(self) -> None: 24 | test_cases = ( 25 | ("janaSunrise", "c8438cdd126043448cca9e28646efbe7"), 26 | ("JanaSunrise123", "b4ead04f4e2d484ba70257d5729aa773"), 27 | ("VSCode_", "2a13b3a34bf343fa9d8db0f87187da39"), 28 | ) 29 | 30 | for username, uuid in test_cases: 31 | self.assertEqual(username, Conv.uuid_to_username(uuid)) 32 | -------------------------------------------------------------------------------- /tests/test_player.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | from typing import cast 4 | 5 | from hypixelio import Client 6 | from hypixelio.exceptions import HypixelAPIError, PlayerNotFoundError 7 | from hypixelio.models.player import Player 8 | from tests.mock_data.player_data import PLAYER_MOCK 9 | 10 | API_KEY = cast(str, os.getenv("HYPIXEL_KEY")) 11 | 12 | if not API_KEY: 13 | raise Exception("Please set the HYPIXEL_KEY environment variable.") 14 | 15 | 16 | class TestPlayer(unittest.TestCase): 17 | """Tests for testing the player data.""" 18 | 19 | def test_invalid_player_name(self) -> None: 20 | test_cases = ("ewdijenwmim", "de3in7euw9s38h23782iwksjnhuwiks") 21 | 22 | client = Client(api_key=API_KEY) 23 | 24 | for test in test_cases: 25 | with self.assertRaises(PlayerNotFoundError): 26 | client.get_player(name=test) 27 | 28 | def test_invalid_player_uuid(self) -> None: 29 | test_cases = ("ewdijenwmim", "de3in7euw9s38h23782iwksjnhuwiks") 30 | 31 | client = Client(api_key=API_KEY) 32 | 33 | for test in test_cases: 34 | with self.assertRaises(HypixelAPIError): 35 | client.get_player(uuid=test) 36 | 37 | def test_player_data(self) -> None: 38 | client = Client(api_key=API_KEY) 39 | player = client.get_player(name="VSCode_") 40 | 41 | self.assertIsInstance(player.hypixel_id, str) 42 | self.assertIsInstance(player.achievement_points, int) 43 | self.assertIsInstance(player.one_time_achievements, list) 44 | 45 | def test_player_achievements(self) -> None: 46 | data = { 47 | "achievements": { 48 | "bedwars_level": 5, 49 | "general_challenger": 7, 50 | "bedwars_wins": 18, 51 | } 52 | } 53 | 54 | player = Player(PLAYER_MOCK) 55 | for key, value in player.achievements.items(): 56 | self.assertEqual(value, data["achievements"][key]) 57 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length=120 3 | import-order-style=pycharm 4 | application-import-names=hypixelio,tests,examples 5 | exclude= 6 | .venv/**, 7 | .git/** 8 | ignore= 9 | # Ignore missing return type annotations for special methods 10 | ANN204, 11 | # Ignore missing type annotations 12 | ANN101, # Init 13 | ANN102, # cls 14 | ANN002, # *args 15 | ANN003, # **kwargs 16 | E731, # Allow lambdas 17 | MD033, # Allow markdown inline HTML 18 | F401, # Allow `__init__` imports 19 | --------------------------------------------------------------------------------