""" Simple Hypixel-API in Python, by Snuggle | 2017-09-30 to 2018-06-14 """
__version__ = '0.8.0'
# pylint: disable=C0103
# TODO: Add more comments. Explain what's happening!
# TODO: Add API-usage stat-tracking. Like a counter of the number of requests and how many per minute etc.
from random import choice
from time import time
import grequests
import leveling
HYPIXEL_API_URL = 'https://api.hypixel.net/'
UUIDResolverAPI = "https://sessionserver.mojang.com/session/minecraft/profile/"
HYPIXEL_API_KEY_LENGTH = 36 # This is the length of a Hypixel-API key. Don't change from 36.
verified_api_keys = []
requestCache = {}
cacheTime = 60
[docs]class PlayerNotFoundException(Exception):
""" Simple exception if a player/UUID is not found. This exception can usually be ignored.
You can catch this exception with ``except hypixel.PlayerNotFoundException:`` """
pass
class SkyblockUUIDRequired(Exception):
"""Simple exception to tell the user that in the Skyblock API, UUID's are required and names cannot be used.
Catch this exception with ``except hypixel.SkyblockUUIDRequired:``"""
pass
class GuildIDNotValid(Exception):
""" Simple exception if a Guild is not found using a GuildID. This exception can usually be ignored.
You can catch this exception with ``except hypixel.GuildIDNotValid:`` """
pass
[docs]class HypixelAPIError(Exception):
""" Simple exception if something's gone very wrong and the program can't continue. """
pass
def getJSON(typeOfRequest, **kwargs):
""" This private function is used for getting JSON from Hypixel's Public API. """
requestEnd = ''
if typeOfRequest == 'key':
api_key = kwargs['key']
else:
api_key = choice(verified_api_keys) # Select a random API key from the list available.
if typeOfRequest == 'player':
UUIDType = 'uuid'
uuid = kwargs['uuid']
if len(uuid) <= 16:
UUIDType = 'name' # TODO: I could probably clean this up somehow.
if typeOfRequest == 'skyblockplayer':
typeOfRequest = "/skyblock/profiles"
for name, value in kwargs.items():
if typeOfRequest == "player" and name == "uuid":
name = UUIDType
requestEnd += '&{}={}'.format(name, value)
cacheURL = HYPIXEL_API_URL + '{}?key={}{}'.format(typeOfRequest, "None", requestEnd) # TODO: Lowercase
allURLS = [HYPIXEL_API_URL + '{}?key={}{}'.format(typeOfRequest, api_key, requestEnd)] # Create request URL.
# If url exists in request cache, and time hasn't expired...
if cacheURL in requestCache and requestCache[cacheURL]['cacheTime'] > time():
response = requestCache[cacheURL]['data'] # TODO: Extend cache time
else:
requests = (grequests.get(u) for u in allURLS)
responses = grequests.imap(requests)
for r in responses:
response = r.json()
if not response['success']:
raise HypixelAPIError(response)
if typeOfRequest == 'player':
if response['player'] is None:
raise PlayerNotFoundException(uuid)
if typeOfRequest != 'key': # Don't cache key requests.
requestCache[cacheURL] = {}
requestCache[cacheURL]['data'] = response
requestCache[cacheURL]['cacheTime'] = time() + cacheTime # Cache request and clean current cache.
cleanCache()
try:
return response[typeOfRequest]
except KeyError:
return response
def cleanCache():
""" This function is occasionally called to clean the cache of any expired objects. """
itemsToRemove = []
for item in requestCache:
try:
if requestCache[item]['cacheTime'] < time():
itemsToRemove.append(item)
except:
pass
for item in itemsToRemove:
requestCache.pop(item)
[docs]def setCacheTime(seconds):
""" This function sets how long the request cache should last, in seconds.
Parameters
-----------
seconds : float
How long you would like Hypixel-API requests to be cached for.
"""
try:
global cacheTime
cacheTime = float(seconds)
return "Cache time has been successfully set to {} seconds.".format(cacheTime)
except ValueError as chainedException:
raise HypixelAPIError("Invalid cache time \"{}\"".format(seconds)) from chainedException
[docs]def setKeys(api_keys):
""" This function is used to set your Hypixel API keys.
It also checks that they are valid/working.
Raises
------
HypixelAPIError
If any of the keys are invalid or don't work, this will be raised.
Parameters
-----------
api_keys : list
A list of the API keys that you would like to use.
Example: ``['740b8cf8-8aba-f2ed-f7b10119d28']``.
"""
for api_key in api_keys:
if len(api_key) == HYPIXEL_API_KEY_LENGTH:
response = getJSON('key', key=api_key)
if response['success']:
verified_api_keys.append(api_key)
else:
raise HypixelAPIError("hypixel/setKeys: Error with key XXXXXXXX-XXXX-XXXX-XXXX{} | {}".format(api_key[23:], response))
else:
raise HypixelAPIError("hypixel/setKeys: The key '{}' is not 36 characters.".format(api_key))
[docs]class Player:
""" This class represents a player on Hypixel as a single object.
A player has a UUID, a username, statistics etc.
Raises
------
PlayerNotFoundException
If the player cannot be found, this will be raised.
Parameters
-----------
Username/UUID : string
Either the UUID or the username (Deprecated) for a Minecraft player.
Attributes
-----------
JSON : string
The raw JSON receieved from the Hypixel API.
UUID : string
The player's UUID.
"""
JSON = None
UUID = None
def __init__(self, UUID):
""" This is called whenever someone uses hypixel.Player('Snuggle').
Get player's UUID, if it's a username. Get Hypixel-API data. """
self.UUID = UUID
if len(UUID) <= 16: # If the UUID isn't actually a UUID... *rolls eyes* Lazy people.
self.JSON = getJSON('player', uuid=UUID) # Get player's Hypixel-API JSON information.
JSON = self.JSON
self.UUID = JSON['uuid'] # Pretend that nothing happened and get the UUID from the API.
elif len(UUID) in (32, 36): # If it's actually a UUID, with/without hyphens...
self.JSON = getJSON('player', uuid=UUID)
else:
raise PlayerNotFoundException(UUID)
[docs] def getPlayerInfo(self):
""" This is a simple function to return a bunch of common data about a player. """
JSON = self.JSON
playerInfo = {}
playerInfo['uuid'] = self.UUID
playerInfo['displayName'] = Player.getName(self)
playerInfo['rank'] = Player.getRank(self)
playerInfo['networkLevel'] = Player.getLevel(self)
JSONKeys = ['karma', 'firstLogin', 'lastLogin',
'mcVersionRp', 'networkExp', 'socialMedia', 'prefix']
for item in JSONKeys:
try:
playerInfo[item] = JSON[item]
except KeyError:
pass
return playerInfo
[docs] def getName(self):
""" Just return player's name. """
JSON = self.JSON
return JSON['displayname']
[docs] def getLevel(self):
""" This function calls leveling.py to calculate a player's network level. """
JSON = self.JSON
networkExp = JSON.get('networkExp', 0)
networkLevel = JSON.get('networkLevel', 0)
exp = leveling.getExperience(networkExp, networkLevel)
myoutput = leveling.getExactLevel(exp)
return myoutput
def getUUID(self):
JSON = self.JSON
return JSON['uuid']
[docs] def getRank(self):
""" This function returns a player's rank, from their data. """
JSON = self.JSON
playerRank = {} # Creating dictionary.
playerRank['wasStaff'] = False
possibleRankLocations = ['packageRank', 'newPackageRank', 'monthlyPackageRank', 'rank']
# May need to add support for multiple monthlyPackageRank's in future.
for Location in possibleRankLocations:
if Location in JSON:
if Location == 'rank' and JSON[Location] == 'NORMAL':
playerRank['wasStaff'] = True
else:
if JSON[Location] == "NONE": # If monthlyPackageRank expired, ignore "NONE". See: https://github.com/Snuggle/hypixel.py/issues/9
continue
dirtyRank = JSON[Location].upper().replace("_", " ").replace(" Plus", "+")
playerRank['rank'] = dirtyRank.replace("Superstar", "MVP++").replace("Youtuber", "YouTube")
if 'rank' not in playerRank:
playerRank['rank'] = 'Non'
return playerRank
[docs] def getGuildID(self):
""" This function is used to get a GuildID from a player. """
UUID = self.UUID
GuildID = getJSON('findGuild', byUuid=UUID)
return GuildID['guild']
[docs] def getSession(self):
""" This function is used to get a player's session information. """
UUID = self.UUID
try:
session = getJSON('session', uuid=UUID)
except HypixelAPIError:
session = None
return session
[docs]class Guild:
""" This class represents a guild on Hypixel as a single object.
A guild has a name, members etc.
Parameters
-----------
GuildID : string
The ID for a Guild. This can be found by using :class:`Player.getGuildID()`.
Attributes
-----------
JSON : string
The raw JSON receieved from the Hypixel API.
GuildID : string
The Guild's GuildID.
"""
JSON = None
GuildID = None
def __init__(self, GuildID):
try:
if len(GuildID) == 24:
self.GuildID = GuildID
self.JSON = getJSON('guild', id=GuildID)
except Exception as chainedException:
raise GuildIDNotValid(GuildID) from chainedException
[docs] def getMembers(self):
""" This function enumerates all the members in a guild.
Mojang's API rate-limits this weirdly.
This is an extremely messy helper function. Use at your own risk. """
guildRoles = ['MEMBER', 'OFFICER', 'GUILDMASTER'] # Define variables etc.
memberDict = self.JSON['members']
allGuildMembers = {}
for role in guildRoles: # Make allGuildMembers =
allGuildMembers[role] = [] # {MEMBER: [], OFFICER: [], GUILDMASTER: []}
allURLS = []
URLStoRequest = []
roleOrder = []
memberList = []
requests = None
responses = None
for member in memberDict: # For each member, use the API to get their username.
roleOrder.append(member['rank'])
if UUIDResolverAPI + member['uuid'] in requestCache:
print("cached")
allURLS.append(requestCache[UUIDResolverAPI + member['uuid']]['name'])
else:
print("NOPE")
allURLS.append(UUIDResolverAPI + member['uuid'])
URLStoRequest.append(UUIDResolverAPI + member['uuid'])
requests = (grequests.get(u) for u in URLStoRequest)
responses = grequests.map(requests)
for response in responses:
requestCache[UUIDResolverAPI + response.json()['id']] = response.json()
i = 0
for uindex, user in enumerate(allURLS):
try:
if user.startswith(UUIDResolverAPI):
allURLS[uindex] = responses[i].json()['name']
i += 1
except AttributeError:
pass
i = 0
for name in allURLS:
try:
member = {'role': roleOrder[i], 'name': name}
except KeyError:
member = {'role': roleOrder[i], 'name': 'Unknown'}
memberList.append(member)
i = i + 1
for member in memberList:
roleList = allGuildMembers[member['role']]
roleList.append(member['name'])
return allGuildMembers
class Auction:
""" This class represents an auction on Hypixel Skyblock as a single object.
"""
def __init__(self):
""""Called to create an Auction class."""
pass
def getAuctionInfo(self, PageNumber):
"""Gets all the auction info for a specified page. PageNumber is the page that is requested and can be in int form or string"""
return getJSON("skyblock/auction", page = str(PageNumber))
#TODO Add more info
class SkyblockPlayer:
"""A class for a Skyblock player. It requires a UUID, and will return stats on the player
Raises
------
SkyblockUUIDRequired
If you pass in a normal username such as RedKaneChironic, will throw an error as Hypixel Skyblock's API currently does not support usernames
PlayerNotFoundException
If the player cannot be found, this will be raised.
Parameters
-----------
UUID: string
UUID of the Player
JSON: string
Raw JSON data"""
def __init__(self, UUID):
self.UUID = UUID
if len(UUID) <= 16: #UUID is a Minecraft username
raise SkyblockUUIDRequired(UUID)
elif len(UUID) in (32, 36):
self.JSON = getJSON('skyblock/player', uuid = UUID)
else:
raise PlayerNotFoundException(UUID)
if __name__ == "__main__":
print("This is a Python library and shouldn't be run directly.\n"
"Please look at https://github.com/Snuggle/hypixel.py for usage & installation information.")