PK!dΠaiohypixel/__init__.py#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright © Tmpod 2019 # # This file is part of aiohypixel. # # aiohypixel is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # aiohypixel is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with aiohypixel. If not, see . """ This is an asynchronous Hypixel API wrapper written in Python. Compatible with Python3.6 and up NOTE: It's highly recommended that you setup logging for this module! A lot of info that you may not want to miss is reported through there. Doing it for aiohttp might also be a good idea (INFO level is generaly enough). """ # TODO: Add documentation to all models' fields. from . import shared, session, player, guild, keys, boosters, friends, watchdogstats, stats from .session import * from .shared import * from .player import * from .stats import * from .guild import * from .boosters import * from .keys import * from .friends import * from .watchdogstats import * from .leaderboards import * from .resources import * from .skyblock import * __author__ = "Tmpod" __url__ = f"https://gitlab.com/{__author__}/aiohypixel/" __version__ = "0.2.0" __licence__ = "LGPL-3.0" __copyright__ = f"Copyright (c) 2018-2019 {__author__}" PK!9-aiohypixel/__main__.py#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright © Tmpod 2019 # # This file is part of aiohypixel. # # aiohypixel is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # aiohypixel is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with aiohypixel. If not, see . """ This is just a little *crude* interface for the wrapper. """ from .session import HypixelSession import logging, asyncio, sys from shutil import get_terminal_size WIN_SIZE = get_terminal_size() def fmtp(s: str) -> str: """Just prints the string with a little delimiter around it""" ndash = ("-" * (len(s) - 2)) if len(s) <= WIN_SIZE.columns else "-" * (WIN_SIZE.columns - 2) print(f"\n<{ndash}>\n{s}\n<{ndash}>\n") try: API_KEY = sys.argv[1] except IndexError: fmtp("You must provide an API key!") else: try: REQUEST_TYPE = sys.argv[2] except IndexError: fmtp("You must provide a get method!") else: logging.basicConfig(level="DEBUG") try: QUERY = sys.argv[3] except IndexError: QUERY = None async def main(request_type: str, query: str = None) -> None: session = await HypixelSession(API_KEY) try: if query is None: fmtp(str(await (getattr(session, f"get_{request_type}"))())) return fmtp(str(await (getattr(session, f"get_{request_type}"))(query))) except AttributeError: fmtp("Invalid!") asyncio.get_event_loop().run_until_complete(main(REQUEST_TYPE, QUERY)) PK!aiohypixel/boosters.py#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright © Tmpod 2019 # # This file is part of aiohypixel. # # aiohypixel is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # aiohypixel is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with aiohypixel. If not, see . """ Stuff for the boosters endpoint """ __all__ = ("Booster",) from dataclasses import dataclass from datetime import datetime from typing import Union, Tuple from .shared import APIResponse, GAME_TYPES_TABLE, HypixelModel @dataclass(frozen=True) class Booster(HypixelModel): """ Represents a """ purchaser_uuid: str amount: int original_length: int current_length: int game_type: int game: str activated_at: datetime stacked: Union[bool, Tuple[str], None] # not sure what this is... I'll get back here soon @classmethod def from_api_response(cls, resp: APIResponse): """ Processes the raw API response into a :class:`Booster` object. Args: resp: The API response to process. Returns: The processed :class:`Booster` object. """ return cls( purchaser_uuid=resp["purchaserUuid"], amount=resp["amount"], original_length=resp["originalLength"], current_length=resp["length"], game_type=resp["gameType"], game=GAME_TYPES_TABLE.get(resp["gameType"]), activated_at=datetime.utcfromtimestamp(resp["dateActivated"] / 1000), stacked=resp.get("stacked"), ) PK!f<aiohypixel/friends.py#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright © Tmpod 2019 # # This file is part of aiohypixel. # # aiohypixel is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # aiohypixel is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with aiohypixel. If not, see . """ Stuff for the friends endpoint """ __all__ = ("Friend",) from dataclasses import dataclass from datetime import datetime from .shared import APIResponse, HypixelModel @dataclass(frozen=True) class Friend(HypixelModel): sender_uuid: str receiver_uuid: str started_at: datetime @classmethod def from_api_response(cls, resp: APIResponse): """ Processes the raw API response into a :class:`Friend` object. Args: resp: The API response to process. Returns: The processed :class:`Friend` object. """ return cls( sender_uuid=resp["uuidSender"], receiver_uuid=resp["uuidReceiver"], started_at=datetime.utcfromtimestamp(resp["started"] / 1000), ) PK![aiohypixel/guild.py#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright © Tmpod 2019 # # This file is part of aiohypixel. # # aiohypixel is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # aiohypixel is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with aiohypixel. If not, see . """ Custom classes that are related to guild lookups on the Hypixel API """ __all__ = ("Guild", "GuildRank", "GuildMember", "GuildTag") from dataclasses import dataclass from datetime import datetime from typing import Union, Dict, Tuple, List from .shared import APIResponse, HypixelModel LVL_EXP_NEEDED = [ 100000, 150000, 250000, 500000, 750000, 1000000, 1250000, 1500000, 2000000, 2500000, 2500000, 2500000, 2500000, 2500000, 3000000, ] def get_guild_level(exp: int) -> int: level = 0 i = 0 while True: need = LVL_EXP_NEEDED[i] exp -= need if exp < 0: return level level += 1 if i < len(LVL_EXP_NEEDED) - 1: i += 1 @dataclass(frozen=True) class GuildRank(HypixelModel): name: str default: bool tag: Union[str, None] created_at: datetime priority: 1 @classmethod def from_api_response(cls, resp: APIResponse): """ Processes the raw API response into a :class:`GuildRank` object. Args: resp: The API response to process. Returns: The processed :class:`GuildRank` object. """ return cls( name=resp["name"], default=resp["default"], tag=resp["tag"], created_at=datetime.utcfromtimestamp(resp["created"] / 1000), priority=resp["priority"], ) @dataclass(frozen=True) class GuildMember(HypixelModel): uuid: str rank: str joined_at: datetime quest_participation: int @classmethod def from_api_response(cls, resp: APIResponse): """ Processes the raw API response into a :class:`GuildMember` object. Args: resp: The API response to process. Returns: The processed :class:`GuildMember` object. """ return cls( uuid=resp["uuid"], rank=resp["rank"], joined_at=datetime.utcfromtimestamp(resp["joined"] / 1000), quest_participation=resp.get("questParticipation", 0), ) @dataclass(frozen=True) class GuildTag(HypixelModel): """Represents a guild tag""" text: str colour: str color: str @classmethod def from_api_response(cls, resp: APIResponse): """ Processes the raw API response into a :class:`GuildTag` object. Args: resp: The API response to process. Returns: The processed :class:`GuildTag` object. """ return cls(text=resp["tag"], colour=resp["tagColor"], color=resp["tagColor"]) @dataclass(frozen=True) class Guild(HypixelModel): """Describes a Hypixel guild""" raw_data: APIResponse id: str name: str coins: int total_coins: int created_at: datetime joinable: bool tag: Dict[str, str] exp: int level: int preferred_games: List[str] ranks: Tuple[GuildRank] members: Tuple[GuildMember] banner: dict # The API is kinda messy on this one achievements: Dict[str, int] exp_by_game: Dict[str, int] @classmethod def from_api_response(cls, resp: APIResponse): """ Processes the raw API response into a :class:`Guild` object. Args: resp: The API response to process. Returns: The processed :class:`Guild` object. """ return cls( raw_data=resp, id=resp["_id"], name=resp["name"], coins=resp["coins"], total_coins=resp["coinsEver"], created_at=datetime.utcfromtimestamp(resp["created"] / 1000), tag=GuildTag.from_api_response(resp), exp=resp["exp"], level=get_guild_level(resp["exp"]), joinable=resp.get("joinable", False), preferred_games=resp.get("preferredGames"), ranks=tuple(GuildRank.from_api_response(r) for r in resp["ranks"]), members=tuple(GuildMember.from_api_response(m) for m in resp["members"]), banner=resp.get("banner"), achievements=resp["achievements"], exp_by_game=resp["guildExpByGameType"], ) PK!%%aiohypixel/keys.py#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright © Tmpod 2019 # # This file is part of aiohypixel. # # aiohypixel is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # aiohypixel is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with aiohypixel. If not, see . """ Stuff for the keys endpoint """ __all__ = ("Key",) from dataclasses import dataclass from datetime import datetime from .shared import APIResponse @dataclass(frozen=True) class Key: owner_uuid: str value: str # The actual key total_queries: int queries_in_past_min: int @classmethod def from_api_response(cls, resp: APIResponse): """ Processes the raw API response into a :class:`Key` object. Args: resp: The API response to process. Returns: The processed :class:`Key` object. """ return cls( owner_uuid=resp["ownerUuid"], value=resp["key"], total_queries=resp["totalQueries"], queries_in_past_min=resp.get("queriesInPastMin"), ) PK! :aiohypixel/leaderboards.py#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright © Tmpod 2019 # # This file is part of aiohypixel. # # aiohypixel is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # aiohypixel is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with aiohypixel. If not, see . """ Leaderboards dataclasses """ __all__ = ("Leaderboards",) from dataclasses import dataclass from .shared import HypixelModel, APIResponse @dataclass(frozen=True) class Leaderboards(HypixelModel): """""" @classmethod def from_api_response(cls, resp: APIResponse): """ Processes the raw API response into a :class:`Leaderboards` object. Args: resp: The API response to process. Returns: The processed :class:`Leaderboards` object. """ PK!"%!%!aiohypixel/player.py#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright © Tmpod 2019 # # This file is part of aiohypixel. # # aiohypixel is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # aiohypixel is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with aiohypixel. If not, see . """ Custom classes that are related to player lookups on the Hypixel API """ # TODO: Actually sort out this clusterfuck of stuff from dataclasses import dataclass from datetime import datetime from math import sqrt, floor from typing import Union, List, Dict, Tuple from .shared import APIResponse, ImmutableProxy from .stats import * #: Locations where the player rank might be stored POSSIBLE_RANK_LOC = ("packageRank", "newPackageRank", "monthlyPackageRank", "rank") #: Level calculation stuff EXP_FIELD = 0 LVL_FIELD = 0 BASE = 10000 GROWTH = 2500 HALF_GROWTH = 0.5 * GROWTH REVERSE_PQ_PREFIX = -(BASE - 0.5 * GROWTH) / GROWTH REVERSE_CONST = REVERSE_PQ_PREFIX * REVERSE_PQ_PREFIX GROWTH_DIVIDES_2 = 2 / GROWTH BEDWARS_EXP_PER_PRESTIGE = 489000 BEDWARS_LEVELS_PER_PRESTIGE = 100 def get_level(exp): return floor(1 + REVERSE_PQ_PREFIX + sqrt(REVERSE_CONST + GROWTH_DIVIDES_2 * exp)) def get_exact_level(exp): return get_level(exp) + get_percentage_to_next_lvl(exp) def get_exp_from_lvl_to_next(level): return GROWTH * (level - 1) + BASE def get_total_exp_to_lvl(level): lv = floor(level) x0 = get_total_exp_to_full_lvl(lv) if level == lv: return x0 else: return (get_total_exp_to_full_lvl(lv + 1) - x0) * (level % 1) + x0 def get_total_exp_to_full_lvl(level): return (HALF_GROWTH * (level - 2) + BASE) * (level - 1) def get_percentage_to_next_lvl(exp): lv = get_level(exp) x0 = get_total_exp_to_lvl(lv) return (exp - x0) / (get_total_exp_to_lvl(lv + 1) - x0) def get_exp(EXP_FIELD, LVL_FIELD): exp = int(EXP_FIELD) exp += get_total_exp_to_full_lvl(LVL_FIELD + 1) return exp @dataclass(frozen=True) class Player: """ This will describe a player """ raw_data: APIResponse hypixel_id: int uuid: int username: str aliases: List[str] # in chronological order one_time_achievements: List[str] # also in chronological order achievments: ImmutableProxy mc_version: str rank: str was_staff: bool rank_colour: str rank_color: str # for 'muricans outfit: ImmutableProxy voting: ImmutableProxy parkours: ImmutableProxy #: If it is `None`, it means the full player info wasn't requested stats: Tuple[ImmutableProxy] = None @classmethod def from_api_response(cls, resp: APIResponse): """ Processes the raw API response into a :class:`Player` object. Args: resp: The API response to process. Returns: The processed :class:`Player` object. """ class PlayerStats: """""" @classmethod def from_api_response(cls, resp: APIResponse): """ Processes the raw API response into a :class:`PlayerStats` object. Args: resp: The API response to process. Returns: The processed :class:`PlayerStats` object. """ class PlayerInfo: """""" @classmethod def from_api_response(cls, resp: APIResponse): """ Processes the raw API response into a :class:`PlayerStats` object. Args: resp: The API response to process. Returns: The processed :class:`PlayerStats` object. """ def process_raw_player(json_data: APIResponse, partial: bool = False) -> Player: """ This handles the raw json returned by the API and creates a Player object Pass the `partial` param as True to get a PartialPlayer object instead. """ # Basic player info processed_data = { "hypixel_id": json_data["_id"], "uuid": json_data["uuid"], "username": json_data["displayname"], "aliases": [*json_data["knownAliases"].values()], "raw_data": json_data, } # Last used MC version processed_data.update({"mc_version": json_data.get("mcVersionRp")}) # Rank for l in POSSIBLE_RANK_LOC: if l in json_data: if l == "rank" and json_data[l] == "NORMAL": was_staff = True else: was_staff = False if json_data[l].upper() == "NONE": continue rank = ( json_data[l] .title() .replace("_", " ") .replace("Mvp", "MVP") .replace("Vip", "VIP") .replace("Superstar", "MVP++") .replace("Youtuber", "YouTube") .replace(" Plus", "+") ) processed_data.update({"rank": rank, "was_staff": was_staff}) # Calculating player's Network EXP processed_data.update({"level": get_level(json_data["networkExp"])}) # Voting stats # Just processing the time snowflakes # Doing a cheeky workaround :D json_data["voting"] = ImmutableProxy( { k: datetime.utcfromtimestamp(v / 1000) if k.startswith("last") else v for k, v in json_data.get("voting", {}) } ) # That can be translated to: # for k, v in json_data.get("voting", {}): # if k.startswith("last"): # json_data.get("voting", {})[k] = datetime.utcfromtimestamp(v / 1000) # Parkours # More cheeky trickery p_actions = { "timeStart": lambda t: datetime.utcfromtimestamp(t / 1000), "timeTook": lambda t: t / 1000, } json_data["parkours"] = ImmutableProxy( [ImmutableProxy({k: p_actions.get(k, lambda x: x)(v) for k, v in l}) for a in l] for l in json_data.get("parkourCompletions", {}) ) # That can be translated to: # p_tmp = {} # for lobby in json_data.get("parkourCompletions", {}): # new_attemps = [] # for attempt in lobby: # new_attempt = {} # for key, value in attempt: # new_attempt[key] = p_actions.get(key, lambda x: x)(value) # new_attempts.append(new_attempt) # p_tmp[lobby] = new_attemps # json_data["parkours"] = ImmutableProxy(p_tmp) # Current outfit processed_data.update( { "outfit": { k.lower(): v.title().replace("_", " ") for k, v in json_data.get("outfit", {}) } or None } ) if partial: return Player(**processed_data) processed_data.update({"stats": process_raw_player_stats(json_data.get("stats", {}))}) return Player(**processed_data) ### Stats ### # This is extracted from Plancke's php stuff BW_XP_PER_PRESTIGE = 489000 BW_LVLS_PER_PRESTIGE = 100 def get_bw_lvl(exp: int) -> int: prestige = exp // BW_XP_PER_PRESTIGE exp = exp % BW_XP_PER_PRESTIGE if prestige > 5: over = prestige % 5 exp += over * BW_XP_PER_PRESTIGE prestige -= over if exp < 500: return 0 + (prestige * BW_LVLS_PER_PRESTIGE) if exp < 1500: return 1 + (prestige * BW_LVLS_PER_PRESTIGE) if exp < 3500: return 2 + (prestige * BW_LVLS_PER_PRESTIGE) if exp < 5500: return 3 + (prestige * BW_LVLS_PER_PRESTIGE) if exp < 9000: return 4 + (prestige * BW_LVLS_PER_PRESTIGE) exp -= 9000 return (exp / 5000 + 4) + (prestige * BW_LVLS_PER_PRESTIGE) def process_raw_player_stats( json_data: APIResponse, game: Union[str, None] = None ) -> Tuple[ImmutableProxy]: """This will process the JSON data into a tuple of game stats""" # Processing bedwars exp json_data.get("Bedwars", {})["level"] = get_bw_lvl(json_data.get("Bedwars", {})["Experience"]) ... # Process more stuff in the future ig return tuple(ImmutableProxy(d) for _, d in json_data.items()) PK!taiohypixel/resources.py#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright © Tmpod 2019 # # This file is part of aiohypixel. # # aiohypixel is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # aiohypixel is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with aiohypixel. If not, see . """ Resources dataclasses """ __all__ = ( "Achievements", "Quests", "Challenges", "GuildAchievements", "GuildPermissions", "SkyblockCollections", "SkyblockSkills", ) from dataclasses import dataclass from .shared import HypixelModel, APIResponse @dataclass(frozen=True) class Achievements(HypixelModel): """""" @classmethod def from_api_response(cls, resp: APIResponse): """ Processes the raw API response into a :class:`Achievements` object. Args: resp: The API response to process. Returns: The processed :class:`Achievements` object. """ @dataclass(frozen=True) class Quests(HypixelModel): """""" @classmethod def from_api_response(cls, resp: APIResponse): """ Processes the raw API response into a :class:`Quests` object. Args: resp: The API response to process. Returns: The processed :class:`Quests` object. """ @dataclass(frozen=True) class Challenges(HypixelModel): """""" @classmethod def from_api_response(cls, resp: APIResponse): """ Processes the raw API response into a :class:`Challenges` object. Args: resp: The API response to process. Returns: The processed :class:`Challenges` object. """ @dataclass(frozen=True) class GuildAchievements(HypixelModel): """""" @classmethod def from_api_response(cls, resp: APIResponse): """ Processes the raw API response into a :class:`GuildAchievements` object. Args: resp: The API response to process. Returns: The processed :class:`GuildAchievements` object. """ @dataclass(frozen=True) class GuildPermissions(HypixelModel): """""" @classmethod def from_api_response(cls, resp: APIResponse): """ Processes the raw API response into a :class:`GuildPermissions` object. Args: resp: The API response to process. Returns: The processed :class:`GuildPermissions` object. """ @dataclass(frozen=True) class SkyblockCollections(HypixelModel): """""" @classmethod def from_api_response(cls, resp: APIResponse): """ Processes the raw API response into a :class:`SkyblockCollections` object. Args: resp: The API response to process. Returns: The processed :class:`SkyblockCollections` object. """ @dataclass(frozen=True) class SkyblockSkills(HypixelModel): """""" @classmethod def from_api_response(cls, resp: APIResponse): """ Processes the raw API response into a :class:`SkyblockSkills` object. Args: resp: The API response to process. Returns: The processed :class:`SkyblockSkills` object. """ PK!JvPPaiohypixel/session.py#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright © Tmpod 2019 # # This file is part of aiohypixel. # # aiohypixel is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # aiohypixel is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with aiohypixel. If not, see . """ This defines a connection to the Hypixel API. All requests will pass through here and this will handle everything for you """ # TODO: Clean up logging messages. __all__ = ("HypixelSession",) import aiohttp import asyncio import logging import typing from random import choice import re from uuid import UUID # NOTE: I should probably ditch the wildcard import from .shared import * from .player import * from .guild import * from .boosters import * from .keys import * from .friends import * from .watchdogstats import * from .resources import * from .skyblock import * from .leaderboards import * #: This is the base URL for all the requests regarding the Hypixel API. BASE_API_URL = "https://api.hypixel.net/" #: This is the message sent when you try to do a request with an invalid key. INVALID_KEY_MESSAGE = "Invalid API key!" #: Mojang name lookup endpoint MOJANG_PROFILE_API_URL = "https://api.mojang.com/users/profiles/minecraft/" #: This is th length of a UUID. 32 hex digits + 4 dashes. UUID_LENGTH = (32, 36) #: This is the boundaries of char lenghts for Minecraft usernames. MC_NAME_LENGTH = (3, 16) #: MongoDB _id string pattern. The API uses Mongo's IDs for guilds. MONGO_ID_RE = re.compile(r"^[a-f\d]{24}$", re.I) class HypixelSession: """ Represents a connection to the Hypixel API. You can perform a check on your API keys with the :meth:`check_keys` method. """ #: This is the base URL for all the requests regarding the Hypixel API. Also see :const:`BASIC_API_URL`. DEFAULT_BASE_API_URL: str = BASE_API_URL __slots__ = ("logger", "_api_keys", "api_url", "max_key_wait_time", "loop", "http_client") def __init__( self, api_keys: typing.Iterable[str], *, api_url: str = None, max_key_wait_time: typing.Union[int, float] = 10, loop: asyncio.AbstractEventLoop = None, ) -> None: """ Args: api_keys: The collection of API keys to use for making requests. api_url: The base API URL to use. Defaults to :attr:`DEFAULT_BASE_API_URL`. max_key_wait_time: The maximum time in seconds the requester will wait for an available key. After that, :exc:asyncio.TimeoutError is raised. Defaults to 10. loop: The event loop to use for any asynchronous task. """ #: Client logger. self.logger = logging.getLogger(__name__) self.logger.debug("Starting a new session...") #: Pool of API keys to use when doing requests. self._api_keys = asyncio.Queue() # Filling the queue for k in api_keys: self._api_keys.put_nowait(k) #: Base API URL to use self.api_url = api_url or self.DEFAULT_BASE_API_URL #: Maximum time (in seconds) allowed for the requester to wait for an available key before raising an error. self.max_key_wait_time = max_key_wait_time #: Event loop to use for any asynchronous process. self.loop = loop or asyncio.get_event_loop() #: Client to use for making requests to the API. self.http_client = aiohttp.ClientSession(loop=self.loop) self.logger.debug("Session fully initialized!") @property def api_keys(self): """Set of all available keys in the pool.""" # Ik this isn't that great... return set(self._api_keys._queue) async def _request(self, endpoint: str, params: dict = {}): """ Fetches the JSON response from the passed endpoint with the given params. Args: endpoint: The endpoint to make the request to. params: The query-string parameters to pass. Returns: The JSON response received from the API. Raises: asyncio.TimeoutError: In case an available key cannot be found in less time than :attr:`max_key_wait_time` seconds. InvalidAPIKey: If the API key used was rejected. When this happens, the invalid API key will be removed from the internal key pool. HypixelAPIError: In case the API didn't give an OK response. UnsuccessfulRequest: If the API rejected the request for some unkonwn reason. """ self.logger.debug( "Starting to fetch JSON from the **%s** endpoint with these params: %s", endpoint, params, ) self.logger.debug("Chosing key...") key = await asyncio.wait_for(self._api_keys.get(), timeout=self.max_key_wait_time) if "key" not in params: params["key"] = key self.logger.debug("Chose: `%s`\nNow doing the actual fetching...", params["key"]) trash_key = False try: async with self.http_client.get(f"{BASE_API_URL}{endpoint}", params=params) as resp: resp.raise_for_status() result = await resp.json() if not result["success"]: # NOTE: Maybe keep trying until a key is valid? if result["cause"] == INVALID_KEY_MESSAGE: trash_key = True self.logger.error("Request failed due to invalid API key! Removing it...") raise InvalidAPIKey self.logger.error("Request failed! Cause: %s", result["cause"]) raise UnsuccessfulRequest(result["cause"]) self.logger.debug("Request was successful! Returning result...") return result except KeyError: raise HypixelAPIError("Something went wrong with the Hypixel API! Try again later.") finally: if not trash_key: self._api_keys.put_nowait(key) async def check_keys( self, keys: typing.Union[str, typing.Iterable[str]] ) -> typing.Iterable[str]: """ Validates the given keys by using the '/key' endpoint, wrapped by :meth:`get_key`. Args: keys: Collection of keys to check. Returns: Collection of invalid keys. Raises: HypixelAPIError: In case the API didn't give an OK response. UnsuccessfulRequest: If the API rejected the request for some unkonwn reason. """ self.logger.debug("Checking API keys: %s", keys) rejected_keys = set() for k in keys: try: # Not even caring about the return value await self.get_key(k) except InvalidAPIKey: rejected_keys.add(k) self.logger.debug( "Finished checking API keys. Encoutered %s error%s!", len(rejected_keys) or "no", "s" if rejected_keys else "", ) return rejected_keys ## Deprecated this since it doesn't make sense with the queue aproach I'm going with ## I might add separate methods for adding or removing keys later. # async def edit_keys( # self, new_keys: typing.Iterable[str], validate_keys: bool = True # ) -> typing.Union[typing.Set[str], None]: # """ # Allows you to edit your registered keys for this session. # You can optionally perform a check on the keys, and if so, # any invaid key will not be added and instead be returned a collection of all invalid keys. # Args: # new_keys: # The collection of keys to check. # validate_keys: # Wheather to perform validation checks on the keys or not. # Returns: # Collection of invalid keys. # Raises: # HypixelAPIError: # In case the API didn't give an OK response. # UnsuccessfulRequest: # If the API rejected the request for some unkonwn reason. # """ # self.logger.debug("Attempting to change the API key pool...") # invalid_keys = None # if validate_keys: # invalid_keys = await self.check_keys(new_keys) # self.api_keys = self.api_keys - invalid_keys # self.logger.info("Changed the API key pool to: %s", self.api_keys) # return invalid_keys async def name_to_uuid(self, query: str) -> str: """ Gets the UUID for the player with the given name. Uses the Mojang API. Args: query: The username you want to lookup the UUID of. Returns: The player's UUID. Raises: aiohttp.ClientResponseError: In case something goes wrong with the request. """ self.logger.debug("Fetching UUID from name: %s", query) async with self.http_client.get(f"{MOJANG_PROFILE_API_URL}{query}") as resp: resp.raise_for_status() return (await resp.json())["id"] async def uuid_to_name(self, query: str) -> str: """ Gets the name for the player with the given UUID. Uses the Mojang API for that. Args: query: The player UUID to get the name of. Returns: The player's name. Raises: aiohttp.ClientResponseError: In case something goes wrong with the request. """ self.logger.debug("Fetching name from UUID: %s", query) async with self.http_client.get(f"{MOJANG_PROFILE_API_URL}{query}") as resp: resp.raise_for_status() return (await resp.json())["name"] async def get_player_by_name(self, name: str) -> typing.Union[Player, None]: """""" async def get_player_by_uuid(self, uuid: typing.Union[UUID, str]) -> typing.Union[Player, None]: """""" async def get_player(self, query: typing.Union[str, UUID]) -> typing.Union[Player, None]: """ Gets a full player object (general info + game stats) with the given query. Args: query: Either the UUID or username of the player you want to get. Returns: The requested player data or `None` if the player wasn't found. Raises: InvalidAPIKey: If the used API key isn't valid. HypixelAPIError: In case the API didn't give an OK response. UnsuccessfulRequest: If the API rejected the request for some unkonwn reason. """ if is_uuid(query): return await self.get_player_by_uuid(query) return await self.get_player_by_name(query) async def get_player_stats(self, query: str) -> typing.Union[PlayerStats, None]: """ Gets a player's stats with the given query. Args: query: Either the UUID or username of the player you want to get the stats for. Returns: The requested player stats or `None` if the player wasn't found. Raises: InvalidAPIKey: If the used API key isn't valid. HypixelAPIError: In case the API didn't give an OK response. UnsuccessfulRequest: If the API rejected the request for some unkonwn reason. """ if is_uuid(query): query_type = "uuid" else: query_type = "name" result = await self._request("player", params={query_type: query}) if result["player"] is None: return return PlayerStats.from_api_response(result["player"]["stats"]) async def get_player_info(self, query: str) -> typing.Union[PlayerInfo, None]: """ Gets a player object with just the network info that does not relate to any stats. Args: query: Either the UUID or username of the player you want to get info for. Returns: The requested player info or `None` if the player wasn't found. Raises: InvalidAPIKey: If the used API key isn't valid. HypixelAPIError: In case the API didn't give an OK response. UnsuccessfulRequest: If the API rejected the request for some unkonwn reason. """ if is_uuid(query): query_type = "uuid" else: query_type = "name" result = await self._request("player", params={query_type: query}) if result["player"] is None: return return PlayerInfo.from_api_response(result["player"]) # NOTE: maybe split this into two separate methods? async def get_guild_id(self, query: str) -> typing.Union[str, None]: """""" async def get_guild_by_id(self, guild_id: str) -> typing.Union[Guild, None]: """""" async def get_guild_by_name(self, name: str) -> typing.Union[Guild, None]: """""" async def get_guild(self, query: str) -> typing.Union[Guild, None]: """ Gets a guild object from the given query. Args: query: Either the ID or name of the guild you want to get. Returns: The requested guild or `None` if not found. Raises: InvalidAPIKey: If the used API key isn't valid. HypixelAPIError: In case the API didn't give an OK response. UnsuccessfulRequest: If the API rejected the request for some unkonwn reason. """ if is_uuid(query): return await self.get_guild_by_id(query) return await self.get_guild_by_name(query) # NOTE: Maybe rename to 'get_player_guild'? async def get_guild_by_player_uuid( self, uuid: typing.Union[UUID, str] ) -> typing.Union[Guild, None]: """""" async def get_guild_by_player_name(self, name: str) -> typing.Union[Guild, None]: """""" async def get_guild_by_player( self, query: typing.Union[str, UUID] ) -> typing.Union[Guild, None]: """ Gets a guild object to which the player with the given name/UUID belongs to. Args: query: Either the ID or name of the player you want to get guild for. Returns: The requested guild or `None` if not found. Raises: InvalidAPIKey: If the used API key isn't valid. HypixelAPIError: In case the API didn't give an OK response. UnsuccessfulRequest: If the API rejected the request for some unkonwn reason. """ if is_uuid(query): return await self.get_guild_by_player_uuid(query) return await self.get_guild_by_player_name(query) async def get_key(self, key: typing.Union[UUID, str]) -> Key: """ Gets a Key object from the passed query. Args: key: The key to get. Returns: The requested key. Raises: InvalidAPIKey: If the requested key does not exist. HypixelAPIError: In case the API didn't give an OK response. UnsuccessfulRequest: If the API rejected the request for some unkonwn reason. """ if not is_uuid(key): raise InvalidAPIKey(f"{key} isn't a valid UUID and so it isn't a valid key!") result = await self._request("key", params={"key": key}) return Key.from_api_response(result["record"]) async def get_friends_by_name(self, name: str) -> typing.List[Friend]: """""" async def get_friends_by_uuid(self, uuid: typing.Union[UUID, str]) -> typing.List[Friend]: """""" # NOTE: Maybe implement an async iterator for this? async def get_friends(self, query: typing.Union[str, UUID]) -> typing.List[Friend]: """ Gets a tuple of Friend objects that the player with the given name/UUID has. Args: query: Either the UUID or the username of the player you want to get the friends for. Returns: A list of friends the given player has. Raises: InvalidAPIKey: If the used API key isn't valid. HypixelAPIError: In case the API didn't give an OK response. UnsuccessfulRequest: If the API rejected the request for some unkonwn reason. """ if is_uuid(uuid): return await self.get_friends_by_uuid(query) return await self.get_friends_by_name(query) async def get_boosters(self) -> typing.Tuple[typing.List[Booster], dict]: """ Gets a tuple of Booster objects denoting the currently active boosters on the Hypixel Network. Returns: A list containing the currently active boosters as well as the boosters state. Raises: InvalidAPIKey: If the used API key isn't valid. HypixelAPIError: In case the API didn't give an OK response. UnsuccessfulRequest: If the API rejected the request for some unkonwn reason. """ result = await self._request("boosters") boosters = [Booster.from_api_response(b) for b in result.get("boosters", ())] # NOTE: Maybe tidy up this with an enum or smth? return boosters, result.get("boosterState") async def get_watchdog_stats(self) -> WatchdogStats: """ Gets the current WatchDog stats. Returns: The current Watchdog stats. Raises: InvalidAPIKey: If the used API key isn't valid. HypixelAPIError: In case the API didn't give an OK response. UnsuccessfulRequest: If the API rejected the request for some unkonwn reason. """ result = await self._request("watchdogstats") return WatchdogStats.from_api_response(result) async def get_leaderboards(self) -> Leaderboards: """""" async def get_player_count(self) -> int: """""" async def get_achievements_resource(self) -> Achievements: """""" async def get_quests_resource(self) -> Quests: """""" async def get_challenges_resource(self) -> Challenges: """""" async def get_guild_achievements_resource(self) -> GuildAchievements: """""" async def get_guild_permissions_resource(self) -> GuildPermissions: """""" async def get_skyblock_collections_resource(self) -> SkyblockCollections: """""" async def get_skyblock_skills_resource(self) -> SkyblockSkills: """""" async def get_skyblock_player_auctions_by_uuid( self, uuid: typing.Union[UUID, str] ) -> typing.List[SkyblockAuction]: """""" async def get_skyblock_profile_auctions( self, uuid: typing.Union[UUID, str] ) -> typing.List[SkyblockAuction]: """""" async def get_skyblock_auction(self, uuid: typing.Union[UUID, str]) -> SkyblockAuction: """""" async def get_all_skyblock_auctions(self) -> SkyblockAuctionsIterator: """""" async def get_all_skyblock_auctions_page(self, index: int) -> typing.List[SkyblockAuction]: """""" async def get_skyblock_news(self) -> typing.List[SkyblockNews]: """""" async def get_skyblock_profile(self) -> SkyblockProfile: """""" PK!MQ##aiohypixel/shared.py#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright © Tmpod 2019 # # This file is part of aiohypixel. # # aiohypixel is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # aiohypixel is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with aiohypixel. If not, see . """ Custom exceptions related to the Hypixel API and other misc stuff """ __all__ = ( "GAME_TYPES_TABLE", "APIResponse", "AiohypixelException", "UnsuccessfulRequest", "PlayerNotFound", "GuildIDNotValid", "HypixelAPIError", "InvalidAPIKey", "ImmutableProxy", "is_uuid", ) import abc import inspect import typing import uuid #: Mapping for decoding game types (https://github.com/HypixelDev/PublicAPI/blob/master/Documentation/misc/GameType.md) GAME_TYPES_TABLE = { 2: {"type_name": "QUAKECRAFT", "db_name": "Quake", "pretty_name": "Quake"}, 3: {"type_name": "WALLS", "db_name": "Walls", "pretty_name": "Walls"}, 4: {"type_name": "PAINTBALL", "db_name": "Paintball", "pretty_name": "Paintball"}, 5: { "type_name": "SURVIVAL_GAMES", "db_name": "HungerGames", "pretty_name": "Blitz Survival Games", }, 6: {"type_name": "TNTGAMES", "db_name": "TNTGames", "pretty_name": "TNT Games"}, 7: {"type_name": "VAMPIREZ", "db_name": "VampireZ", "pretty_name": "VampireZ"}, 13: {"type_name": "WALLS3", "db_name": "Walls3", "pretty_name": "Mega Walls"}, 14: {"type_name": "ARCADE", "db_name": "Arcade", "pretty_name": "Arcade"}, 17: {"type_name": "ARENA", "db_name": "Arena", "pretty_name": "Arena"}, 20: {"type_name": "UHC", "db_name": "UHC", "pretty_name": "UHC Champions"}, 21: {"type_name": "MCGO", "db_name": "MCGO", "pretty_name": "Cops and Crims"}, 23: {"type_name": "BATTLEGROUND", "db_name": "Battleground", "pretty_name": "Warlords"}, 24: {"type_name": "SUPER_SMASH", "db_name": "SuperSmash", "pretty_name": "Smash Heroes"}, 25: {"type_name": "GINGERBREAD", "db_name": "GingerBread", "pretty_name": "Turbo Kart Racers"}, 26: {"type_name": "HOUSING", "db_name": "Housing", "pretty_name": "Housing"}, 51: {"type_name": "SKYWARS", "db_name": "SkyWars", "pretty_name": "SkyWars"}, 52: {"type_name": "TRUE_COMBAT", "db_name": "TrueCombat", "pretty_name": "Crazy Walls"}, 54: {"type_name": "SPEED_UHC", "db_name": "SpeedUHC", "pretty_name": "Speed UHC"}, 55: {"type_name": "SKYCLASH", "db_name": "SkyClash", "pretty_name": "SkyClash"}, 56: {"type_name": "LEGACY", "db_name": "Legacy", "pretty_name": "Classic Games"}, 57: {"type_name": "PROTOTYPE", "db_name": "Prototype", "pretty_name": "Prototype"}, 58: {"type_name": "BEDWARS", "db_name": "Bedwars", "pretty_name": "Bed Wars"}, 59: { "type_name": "MURDER_MYSTERY", "db_name": "MurderMystery", "pretty_name": "Murder Mystery", }, 60: {"type_name": "BUILD_BATTLE", "db_name": "BuildBattle", "pretty_name": "Build Battle"}, 61: {"type_name": "DUELS", "db_name": "Duels", "pretty_name": "Duels"}, } #: Dummy type to represent an response from the Hypiel API. APIResponse = typing.NewType("APIResponse", typing.Dict[str, typing.Any]) class AiohypixelException(Exception): """ Base exception class from which all other exceptions by this library are subclassed. """ class UnsuccessfulRequest(AiohypixelException): """ Raised when the "success" key from a request is False """ class PlayerNotFound(UnsuccessfulRequest): """ Raised if a player/UUID is not found. This exception can usually be ignored. You can catch this exception with ``except aiohypixel.PlayerNotFoundException:`` """ class GuildIDNotValid(UnsuccessfulRequest): """ Raised if a Guild is not found using a Guild ID. This exception can usually be ignored. You can catch this exception with ``except aiohypixel.GuildIDNotValid:`` """ class HypixelAPIError(UnsuccessfulRequest): """ Raised if something's gone very wrong with the API. """ class InvalidAPIKey(UnsuccessfulRequest): """ Raised if the given API Key is invalid """ def find(func, iterable): for i in iterable: if func(i): return i return None def get(iterable, **attrs): def predicate(elem): for attr, val in attrs.items(): nested = attr.split("__") obj = elem for attribute in nested: obj = getattr(obj, attribute) if obj != val: return False return True return find(predicate, iterable) def is_uuid(item: typing.Union[str, uuid.UUID]) -> bool: """ Checks if the given string is a valid UUID. Args: item: The hexadecimal string to check. """ if isinstance(item, str): try: uuid.UUID(item) return True except ValueError: # in case of a malformed hex UUID string return False return isinstance(item, uuid.UUID) class ImmutableProxy: """""" class HypixelModel(abc.ABC): """ Base for all models. """ @abc.abstractclassmethod def from_api_response(cls, resp: APIResponse): ... PK!.!eaiohypixel/skyblock.py#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright © Tmpod 2019 # # This file is part of aiohypixel. # # aiohypixel is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # aiohypixel is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with aiohypixel. If not, see . """ Skyblock related stuff """ __all__ = ("SkyblockAuction", "SkyblockNews", "SkyblockProfile", "SkyblockAuctionsIterator") import collections from dataclasses import dataclass from .shared import HypixelModel, APIResponse @dataclass(frozen=True) class SkyblockAuction(HypixelModel): """""" @classmethod def from_api_response(cls, resp: APIResponse): """ Processes the raw API response into a :class:`SkyblockAuction` object. Args: resp: The API response to process. Returns: The processed :class:`SkyblockAuction` object. """ @dataclass(frozen=True) class SkyblockNews(HypixelModel): """""" @classmethod def from_api_response(cls, resp: APIResponse): """ Processes the raw API response into a :class:`SkyblockNews` object. Args: resp: The API response to process. Returns: The processed :class:`SkyblockNews` object. """ @dataclass(frozen=True) class SkyblockProfile(HypixelModel): """""" @classmethod def from_api_response(cls, resp: APIResponse): """ Processes the raw API response into a :class:`SkyblockProfile` object. Args: resp: The API response to process. Returns: The processed :class:`SkyblockProfile` object. """ class SkyblockAuctionsIterator(collections.abc.AsyncIterator): """""" PK!c!$aiohypixel/stats.py#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright © Tmpod 2019 # # This file is part of aiohypixel. # # aiohypixel is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # aiohypixel is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with aiohypixel. If not, see . """ Stats dataclasses and other related functions """ __all__ = ("BaseGameStats", "BedWarsStats") from dataclasses import dataclass from .shared import HypixelModel, APIResponse class BaseGameStats(HypixelModel): """""" @classmethod def from_api_response(cls, resp: APIResponse): ... @dataclass(frozen=True) class BedWarsStats(BaseGameStats): """""" @classmethod def from_api_response(cls, resp: APIResponse): """ Processes the raw API response into a :class:`BedWarsStats` object. Args: resp: The API response to process. Returns: The processed :class:`BedWarsStats` object. """ PK!g#aiohypixel/watchdogstats.py#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright © Tmpod 2019 # # This file is part of aiohypixel. # # aiohypixel is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # aiohypixel is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with aiohypixel. If not, see . """ Stuff for the Watchdog Stats endpoint """ __all__ = ("WatchdogStats",) from dataclasses import dataclass from .shared import APIResponse @dataclass(frozen=True) class WatchdogStats: total: int rolling_daily: int last_minute: int staff_total: int staff_rolling_daily: int @classmethod def from_api_response(cls, resp: APIResponse): """ Processes the raw API response into a :class:`WatchdogStats` object. Args: resp: The API response to process. Returns: The processed :class:`WatchdogStats` object. """ return cls( total=resp["watchdog_total"], rolling_daily=resp["watchdog_rollingDaily"], last_minute=resp["watchdog_lastMinute"], staff_total=resp["staff_total"], staff_rolling_daily=resp["staff_rollingDaily"], ) PK!S%aiohypixel-0.2.0.dist-info/LICENSE.md GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. PK!HڽTU aiohypixel-0.2.0.dist-info/WHEEL A н#Z;/"d&F[xzw@Zpy3Fv]\fi4WZ^EgM_-]#0(q7PK!HX˅~#aiohypixel-0.2.0.dist-info/METADATAT]oF|_jFi !l5,$N /&!/%-KNy۝wd!+3x. 2ec+(}ćX5L4HtFd1ONZvNEJŵq`z||Q{0Zc1u=C?Jdqz!T{VH:epa kb_? 'JW qUv ;=)pfd]+ԛHԹbDžO_ŧ|QKCOY1m΍3n^Qsl go&නB}wi!Nb)zJXǪ!)8AVd>BLoB^նgw".P;P*_\{254&N-BB/ &I^VݑGKXbC[#!RS גƦ#Ӥ+G '?I򼗜"BJ݀>"37@~Ua[I1mb<Ȑz5])#ZsNl,- S'_6 Bl]H鱬rB≐ =6ZS'SSugL 2N˳LvqiŲ#%N>(yl)m-VrjIytt h7PBH]ItE,oCL Uw8PK!H /N!aiohypixel-0.2.0.dist-info/RECORD}Dzh}_ $.fA l(r~T <~1(@5-FyoА(N]ovC1؋/ꝱ C(LG.`~8pmb;'Ƴ&0.og}[zqZK"i6Sq/Rd}6+6[ZGBQ˦|OVU;?y(;!2Eɰ.}a*َH)Ok!؝Yxs]wu={BO,+Vq҇ *UQgo1QϽbUNHVj :ܔ/VIop;8CЯPK!dΠaiohypixel/__init__.pyPK!9-aiohypixel/__main__.pyPK!#aiohypixel/boosters.pyPK!f<eaiohypixel/friends.pyPK![aiohypixel/guild.pyPK!%%1aiohypixel/keys.pyPK! :7aiohypixel/leaderboards.pyPK!"%!%!A=aiohypixel/player.pyPK!t^aiohypixel/resources.pyPK!JvPPmaiohypixel/session.pyPK!MQ##aiohypixel/shared.pyPK!.!eSaiohypixel/skyblock.pyPK!c!$Xaiohypixel/stats.pyPK!g#Aaiohypixel/watchdogstats.pyPK!S%aiohypixel-0.2.0.dist-info/LICENSE.mdPK!HڽTU (aiohypixel-0.2.0.dist-info/WHEELPK!HX˅~#aiohypixel-0.2.0.dist-info/METADATAPK!H /N!y aiohypixel-0.2.0.dist-info/RECORDPK