diff --git a/readme.md b/readme.md index a7a2109..64c5cb0 100644 --- a/readme.md +++ b/readme.md @@ -8,13 +8,20 @@ A simple python library for interacting with the [TETR.IO](https://tetr.io/) API ## contents -- [About](#About) -- [Installation](#Installation) -- [Documentation](#Documentation) -- [Examples](#Examples) -- [Contribution](#Contribution) -- [Notice](#Notice) -- [Helpful links](#Helpful-links) +- [Tetry](#tetry) + - [contents](#contents) + - [About](#about) + - [Installation](#installation) + - [installing from pip](#installing-from-pip) + - [installing from source](#installing-from-source) + - [Documentation](#documentation) + - [Examples](#examples) + - [General api](#general-api) + - [Chat commands](#chat-commands) + - [Simple auto-host bot](#simple-auto-host-bot) + - [Contribution](#contribution) + - [Notice](#notice) + - [Helpful links](#helpful-links) ## About @@ -47,15 +54,15 @@ Here you will find code examples for the library. ### General api ```python -from tetry import api +from tetry.api import records -def printRecords(username): - records = api.getRecords(username).records +def print_records(username: str): + records: dict[str, int] = records.get_records(username).records for name, record in records.items(): print(f'{name}: {record}') while (name := input()): - printRecords(name) + print_records(name) ``` diff --git a/tetry/api/__init__.py b/tetry/api/__init__.py index 88350e7..4f4373f 100644 --- a/tetry/api/__init__.py +++ b/tetry/api/__init__.py @@ -1,4 +1,4 @@ -from .records import getRecords -from .user import getUser -from .stream import getStream -from .news import getNews +from .records import get_records +from .user import get_user +from .stream import get_stream +from .news import get_news diff --git a/tetry/api/cache.py b/tetry/api/cache.py index 7ff71b4..cdcbb12 100644 --- a/tetry/api/cache.py +++ b/tetry/api/cache.py @@ -4,11 +4,21 @@ class Cache: - def __init__(self, data): - self.data = data - self.status = data['status'] - self.cachedAt = data['cached_at'] - self.expiresAt = data['cached_until'] - - def is_expired(self): - return time.time() > self.expiresAt + ''' + Cache class for TETR.IO data. + ''' + + def __init__(self, data: dict) -> None: + self.data: dict = data + self.status: str = data['status'] + self.cached_at: int = data['cached_at'] + self.expires_at: int = data['cached_until'] + + def is_expired(self) -> bool: + ''' + is_expired Return True if the cache has expired. + + :return: True if the cache has expired. + :rtype: bool + ''' + return time.time() > self.expires_at diff --git a/tetry/api/commitId.py b/tetry/api/commitId.py index 8ed9ea3..7f5271b 100644 --- a/tetry/api/commitId.py +++ b/tetry/api/commitId.py @@ -1,7 +1,7 @@ import requests -from .urls import enviorment +from .urls import environment -def getCommit(): - json = requests.get(enviorment).json() +def get_commit() -> str: + json: dict = requests.get(environment).json() return json['signature']['commit']['id'] diff --git a/tetry/api/exceptions.py b/tetry/api/exceptions.py new file mode 100644 index 0000000..0520928 --- /dev/null +++ b/tetry/api/exceptions.py @@ -0,0 +1,19 @@ +''' +Exceptions for the API. +''' + + +class NewsError(Exception): + pass + + +class RecordError(Exception): + pass + + +class StreamError(Exception): + pass + + +class UserError(Exception): + pass diff --git a/tetry/api/news.py b/tetry/api/news.py index 7b8f7e3..8bde75a 100644 --- a/tetry/api/news.py +++ b/tetry/api/news.py @@ -1,23 +1,40 @@ import requests -from .urls import news, addParam, addQureyParam +from .urls import news, add_param, add_query_param from .cache import Cache +from .exceptions import NewsError class News: - def __init__(self, data): + ''' + News class for TETR.IO news. + ''' + + def __init__(self, data: dict) -> None: self.cache = Cache(data['cache']) data = data['data']['news'] - self.news = data + self.news: dict = data + +def get_news(stream: str = None, limit=25) -> News: + ''' + get_news Get news from TETR.IO news, optionally filtered by stream. -def getNews(stream=None, limit=25): + :param stream: The stream to filter news by. + Check https://tetr.io/about/api/#streamsstream, defaults to None + :type stream: str, optional + :param limit: News length limit, defaults to 25 + :type limit: int, optional + :raises NewsError: Raises NewsError if the news is not found. + :return: The News object with the news data. + :rtype: News + ''' url = news if stream: - url = addParam(url, stream) - url = addQureyParam(url, {'limit': limit}) + url = add_param(url, stream) + url = add_query_param(url, {'limit': limit}) with requests.Session() as ses: resp = ses.get(url).json() if not resp['success']: - raise Exception(resp['error']) + raise NewsError(resp['error']) return News(resp) diff --git a/tetry/api/records.py b/tetry/api/records.py index 7b3abad..2675f0b 100644 --- a/tetry/api/records.py +++ b/tetry/api/records.py @@ -1,28 +1,43 @@ import requests -from .urls import recordUrl +from .urls import record_url from .cache import Cache +from .exceptions import RecordError class Records: - def __init__(self, data): + ''' + Records class for TETR.IO records. + Provides 40 Lines, Blitz and Zen records. + ''' + + def __init__(self, data: dict) -> None: self.cache = Cache(data['cache']) data = data['data'] self.data = data - self.zen = data['zen'] - self.blitz = data['records']['blitz'] - self._40l = data['records']['40l'] + self.zen: dict = data['zen'] + self.blitz: dict = data['records']['blitz'] + self._40l: dict = data['records']['40l'] self.records = { '40l': self._40l['record']['endcontext']['finalTime'], - 'bltz': self.blitz['record']['endcontext']['score'], + 'blitz': self.blitz['record']['endcontext']['score'], 'zen': self.zen['score'] } -def getRecords(name): - url = recordUrl(name.lower()) +def get_records(name: str) -> Records: + ''' + get_records Return records for a given player. + + :param name: The name of the player. + :type name: str + :raises RecordError: Raises RecordError if the player is not found. + :return: The Records object with the records data. + :rtype: Records + ''' + url = record_url(name.lower()) with requests.Session() as ses: - resp = ses.get(url).json() + resp: dict = ses.get(url).json() if not resp['success']: - raise Exception(resp['error']) + raise RecordError(resp['error']) return Records(resp) diff --git a/tetry/api/resolve.py b/tetry/api/resolve.py index e84c78a..c0573b1 100644 --- a/tetry/api/resolve.py +++ b/tetry/api/resolve.py @@ -1,12 +1,25 @@ import requests from .urls import resolve +from .exceptions import UserError -def getId(name, token): +def get_id(name: str, token: str) -> str: + ''' + get_id Get the ID of a user. + + :param name: The name of the user. + :type name: str + :param token: Your bot's token. + :type token: str + :raises UserError: Raises UserError if the user is not found. + :return: The ID of the user. + :rtype: str + ''' name = name.lower() - headers = {'authorization': f'Bearer {token}'} - res = requests.get(resolve(name), headers=headers).json() + headers: dict = {'Authorization': f'Bearer {token}'} + res: requests.Response = requests.get( + resolve(name), headers=headers).json() if not res['success']: - raise BaseException(res['errors'][0]['msg']) + raise UserError(res['errors'][0]['msg']) return res['_id'] diff --git a/tetry/api/stream.py b/tetry/api/stream.py index 1585aa8..479885a 100644 --- a/tetry/api/stream.py +++ b/tetry/api/stream.py @@ -1,23 +1,36 @@ -from .urls import stream - import requests -from .urls import addParam +from .urls import add_param, stream from .cache import Cache +from .exceptions import StreamError class Stream: - def __init__(self, data): + ''' + Stream class for TETR.IO streams. + ''' + + def __init__(self, data: dict) -> None: self.cache = Cache(data['cache']) data = data['data']['records'] self.news = data -def getStream(stream): +def get_stream(stream_id: str) -> Stream: + ''' + get_stream Return the given Stream from TETR.IO. + + :param stream_id: The id of the stream. + Check https://tetr.io/about/api/#streamsstream for more info. + :type stream_id: str + :raises StreamError: Raises StreamError if the stream is not found. + :return: The Stream object. + :rtype: Stream + ''' url = stream - url = addParam(url, stream) + url = add_param(url, stream_id) with requests.Session() as ses: resp = ses.get(url).json() if not resp['success']: - raise Exception(resp['error']) + raise StreamError(resp['error']) return Stream(resp) diff --git a/tetry/api/urls.py b/tetry/api/urls.py index fb0af9b..653e274 100644 --- a/tetry/api/urls.py +++ b/tetry/api/urls.py @@ -6,31 +6,31 @@ activity = f'{base}/general/activity' user = f'{base}/users' tetraLeague = f'{base}/users/lists/league' -fulltetraLeague = f'{tetraLeague}/all' +fullTetraLeague = f'{tetraLeague}/all' xp = f'{base}/users/lists/xp' stream = f'{base}/streams' news = f'{base}/news' -enviorment = 'https://tetr.io/api/server/environment' +environment = 'https://tetr.io/api/server/environment' -def getRankImage(rank): +def get_rank_image(rank): return f'https://tetr.io/res/league-ranks/{rank}.png' -def getAvatar(id): +def get_avatar(id): return f'https://tetr.io/user-content/avatars/{id}.jpg' -def recordUrl(username): - return addParam(user, username) + '/records' +def record_url(username): + return add_param(user, username) + '/records' -def addParam(url, param): +def add_param(url, param): url += f'/{param}' return url -def addQureyParam(url, params): +def add_query_param(url, params): url += f'?{urlencode(params)}' return url diff --git a/tetry/api/user.py b/tetry/api/user.py index eaee6d1..7204e86 100644 --- a/tetry/api/user.py +++ b/tetry/api/user.py @@ -1,40 +1,40 @@ import requests -from requests.api import head -from .records import getRecords -from .urls import addParam, addQureyParam, getAvatar, getRankImage, user +from .records import Records, get_records +from .urls import add_param, add_query_param, get_avatar, get_rank_image, user from .cache import Cache +from .exceptions import UserError class User: - def __init__(self, data): + def __init__(self, data: dict) -> None: self.cache = Cache(data['cache']) data = data['data']['user'] self.data = data - self.id = data['_id'] - self.username = data['username'] - self.avatarRevision = data.get('avatar_revision') or None + self.id: str = data['_id'] + self.username: str = data['username'] + self.avatar_revision: str | None = data.get('avatar_revision') or None self.league = data['league'] - def getPfp(self, rev=True): - url = getAvatar(self.id) - if rev and self.avatarRevision: - url = addQureyParam(url, {'rv': self.avatarRevision}) + def get_pfp(self, rev: bool = True) -> str: + url = get_avatar(self.id) + if rev and self.avatar_revision: + url = add_query_param(url, {'rv': self.avatar_revision}) return url - def getRankImage(self): - return getRankImage(self.data['league']['rank']) + def get_rank_image(self) -> str: + return get_rank_image(self.data['league']['rank']) - def getRecords(self): - return getRecords(self.username) + def get_records(self) -> Records: + return get_records(self.username) -def getUser(name, token=None): +def get_user(name: str, token: str = None) -> user: url = user - url = addParam(url, name.lower()) + url = add_param(url, name.lower()) with requests.Session() as ses: - headers = {'authorization': f'Bearer {token}'} if token else None + headers = {'Authorization': f'Bearer {token}'} if token else None resp = ses.get(url, headers=headers).json() if not resp['success']: - raise Exception(resp['error']) + raise UserError(resp['error']) return User(resp) diff --git a/tetry/bot/bot.py b/tetry/bot/bot.py index 10242b6..4dedc32 100644 --- a/tetry/bot/bot.py +++ b/tetry/bot/bot.py @@ -4,8 +4,8 @@ import requests import trio -from ..api import getUser -from ..api.resolve import getId +from ..api import get_user +from ..api.resolve import get_id from .chatCommands import commandBot from .commands import (createroom, die, dm, invite, joinroom, notificationAck, presence) @@ -116,7 +116,7 @@ async def _finish(*args): return (ev.result, ev.triggerer) if multiple else ev.result def getUser(self, user): - return getUser(user, self.token) + return get_user(user, self.token) async def getPing(self): await self.connection.ping() @@ -144,9 +144,9 @@ async def reconnect(self, endpoint=None): await self.connection.reconnect(endpoint, self.sockid, self.resume) def getOwner(self): - me = getUser(self.id) + me = get_user(self.id) owner = me.data['botmaster'] - ownerId = getId(owner, self.token) + ownerId = get_id(owner, self.token) return {'name': owner, 'id': ownerId} async def _run(self): @@ -183,7 +183,7 @@ async def stop(self): async def dm(self, msg, uid=None, name=None): if not uid and name: - uid = getId(name, self.token) + uid = get_id(name, self.token) await self.connection.send(dm(self.messageId, uid, msg)) # dm message while (_dm := await self.waitFor('dm'))[0].sender != self.id: pass # wait untill the sender is the bot @@ -191,7 +191,7 @@ async def dm(self, msg, uid=None, name=None): async def invite(self, uid=None, name=None): if not uid and name: - uid = getId(name, self.token) + uid = get_id(name, self.token) # invite message await self.connection.send(invite(self.messageId, uid)) @@ -205,7 +205,7 @@ async def notificationAck(self, notif=None): def addFriend(self, uid=None, name=None): if not uid and name: - uid = getId(name, self.token) + uid = get_id(name, self.token) headers = {'authorization': f'Bearer {self.token}'} json = requests.post(friend, headers=headers, json={'user': uid}).json() @@ -219,7 +219,7 @@ def getFriend(self, name=None, uid=None): def removeFriend(self, uid=None, name=None): if not uid and name: - uid = getId(name, self.token) + uid = get_id(name, self.token) headers = {'authorization': f'Bearer {self.token}'} json = requests.post(unfriend, headers=headers, json={'user': uid}).json() diff --git a/tetry/bot/friend.py b/tetry/bot/friend.py index 060b229..0b84f66 100644 --- a/tetry/bot/friend.py +++ b/tetry/bot/friend.py @@ -1,4 +1,4 @@ -from ..api.user import getUser +from ..api.user import get_user class Friend: @@ -19,7 +19,7 @@ def getPresence(self): return self.bot.presences.get(self.id) def getInfo(self): - return getUser(self.id) + return get_user(self.id) def unfriend(self): self.bot.removeFriend(uid=self.id) diff --git a/tetry/bot/responses.py b/tetry/bot/responses.py index 30e8395..cd5e921 100644 --- a/tetry/bot/responses.py +++ b/tetry/bot/responses.py @@ -10,7 +10,7 @@ from .invite import Invite from .room import Room from .notification import Notification -from ..api.commitId import getCommit +from ..api.commitId import get_commit logger = logging.getLogger(__name__) @@ -21,7 +21,7 @@ async def hello(bot, msg, caller): messages = msg['packets'] if not messages: # authorize message - await bot.connection.send(_authorize(bot.messageId, bot.token, bot.handling, getCommit())) + await bot.connection.send(_authorize(bot.messageId, bot.token, bot.handling, get_commit())) # get the ids for the seen messages seenIds = [m.message['id'] for m in bot.serverMessages] for m in messages: