This commit is contained in:
grey-cat-1908 2023-06-07 12:31:52 +03:00
parent e26366da74
commit daa2e31732
6 changed files with 374 additions and 15 deletions

View file

@ -2,16 +2,15 @@
Boticord API Wrapper
~~~~~~~~~~~~~~~~~~~
A basic wrapper for the BotiCord API.
:copyright: (c) 2022 Marakarka
:copyright: (c) 2023 Marakarka
:license: MIT, see LICENSE for more details.
"""
__title__ = "boticordpy"
__author__ = "Marakarka"
__license__ = "MIT"
__copyright__ = "Copyright 2022 Marakarka"
__version__ = "2.2.2"
__copyright__ = "Copyright 2021 - 2023 Marakarka"
__version__ = "3.0.0a"
from .client import BoticordClient
from .types import *

View file

@ -3,6 +3,7 @@ import typing
from . import types as boticord_types
from .http import HttpClient
from .autopost import AutoPost
from .exceptions import MeilisearchException
class BoticordClient:
@ -18,12 +19,13 @@ class BoticordClient:
BotiCord API version (Default: 3)
"""
__slots__ = ("http", "_autopost", "_token")
__slots__ = ("http", "_autopost", "_token", "_meilisearch_api_key")
http: HttpClient
def __init__(self, token: str = None, version: int = 3):
self._token = token
self._meilisearch_api_key = None
self._autopost: typing.Optional[AutoPost] = None
self.http = HttpClient(token, version)
@ -104,6 +106,79 @@ class BoticordClient:
response = await self.http.get_user_info(user_id)
return boticord_types.UserProfile.from_dict(response)
async def __search_for(self, index, data):
"""Search for smth on BotiCord"""
if self._meilisearch_api_key is None:
token_response = await self.http.get_search_key()
self._meilisearch_api_key = token_response["key"]
try:
response = await self.http.search_for(
index, self._meilisearch_api_key, data
)
except MeilisearchException:
token_response = await self.http.get_search_key()
self._meilisearch_api_key = token_response["key"]
response = await self.http.search_for(
index, self._meilisearch_api_key, data
)
return response["hits"]
async def search_for_bots(
self, **kwargs
) -> typing.List[boticord_types.MeiliIndexedBot]:
"""Search for bots on BotiCord.
Note:
You can find every keyword argument `here <https://www.meilisearch.com/docs/reference/api/search#search-parameters>`_.
Returns:
List[:obj:`~.types.MeiliIndexedBot`]:
List of found bots
"""
response = await self.__search_for("bots", kwargs)
return [boticord_types.MeiliIndexedBot.from_dict(bot) for bot in response]
async def search_for_servers(
self, **kwargs
) -> typing.List[boticord_types.MeiliIndexedServer]:
"""Search for servers on BotiCord.
Note:
You can find every keyword argument `here <https://www.meilisearch.com/docs/reference/api/search#search-parameters>`_.
Returns:
List[:obj:`~.types.MeiliIndexedServer`]:
List of found servers
"""
response = await self.__search_for("servers", kwargs)
return [
boticord_types.MeiliIndexedServer.from_dict(server) for server in response
]
async def search_for_comments(
self, **kwargs
) -> typing.List[boticord_types.MeiliIndexedComment]:
"""Search for comments on BotiCord.
Note:
You can find every keyword argument `here <https://www.meilisearch.com/docs/reference/api/search#search-parameters>`_.
Returns:
List[:obj:`~.types.MeiliIndexedComment`]:
List of found comments
"""
response = await self.__search_for("comments", kwargs)
return [
boticord_types.MeiliIndexedComment.from_dict(comment)
for comment in response
]
def autopost(self) -> AutoPost:
"""Returns a helper instance for auto-posting.

View file

@ -21,7 +21,7 @@ class InternalException(BoticordException):
class HTTPException(BoticordException):
"""Exception that's thrown when an HTTP request operation fails.
"""Exception that's thrown when request to BotiCord API operation fails.
Attributes
----------
@ -37,6 +37,23 @@ class HTTPException(BoticordException):
super().__init__(fmt)
class MeilisearchException(BoticordException):
"""Exception that's thrown when request to Meilisearch API operation fails.
Attributes
----------
response:
The response of the failed HTTP request.
"""
def __init__(self, response):
self.response = response
fmt = f"{self.response['code']} ({self.response['message']})"
super().__init__(fmt)
class StatusCodes(IntEnum):
"""Status codes of response"""

View file

@ -1,10 +1,10 @@
from urllib.parse import urlparse
import asyncio
import typing
import aiohttp
from . import exceptions
from .types import LinkDomain
class HttpClient:
@ -29,13 +29,17 @@ class HttpClient:
self.session = kwargs.get("session") or aiohttp.ClientSession(loop=loop)
async def make_request(self, method: str, endpoint: str, **kwargs) -> dict:
async def make_request(
self, method: str, endpoint: str, *, meilisearch_token: str = None, **kwargs
) -> dict:
"""Send requests to the API"""
kwargs["headers"] = {"Content-Type": "application/json"}
if self.token is not None:
kwargs["headers"]["Authorization"] = self.token
if meilisearch_token is not None:
kwargs["headers"]["Authorization"] = f"Bearer {meilisearch_token}"
url = f"{self.API_URL}{endpoint}"
@ -43,11 +47,14 @@ class HttpClient:
data = await response.json()
if (200, 201).__contains__(response.status):
return data["result"]
return data["result"] if not meilisearch_token else data
else:
if not meilisearch_token:
raise exceptions.HTTPException(
{"status": response.status, "error": data["errors"][0]["code"]}
)
else:
raise exceptions.MeilisearchException(data)
def get_bot_info(self, bot_id: typing.Union[str, int]):
"""Get information about the specified bot"""
@ -64,3 +71,16 @@ class HttpClient:
def get_user_info(self, user_id: typing.Union[str, int]):
"""Get information about specified user"""
return self.make_request("GET", f"users/{user_id}")
def get_search_key(self):
"""Get API key for Meilisearch"""
return self.make_request("GET", f"search-key")
def search_for(self, index: str, api_key: str, data: dict):
"""Search for something on BotiCord."""
return self.make_request(
"POST",
f"search/indexes/{index}/search",
meilisearch_token=api_key,
json=data,
)

View file

@ -559,7 +559,12 @@ class PartialUser(APIObjectBase):
@dataclass(repr=False)
class ResourceServer(APIObjectBase):
"""Information about server from BotiCord."""
"""Information about server from BotiCord.
.. warning::
The result of the reverse conversion (`.to_dict()`) may not match the actual data.
"""
id: str
"""Server's ID"""
@ -700,6 +705,9 @@ class ResourceBot(APIObjectBase):
short_link: Optional[str]
"""Short link to the bot's page"""
standart_banner_id: int
"""Server's standart banner ID"""
invite_link: str
"""Invite link"""
@ -788,6 +796,7 @@ class ResourceBot(APIObjectBase):
self.support_server_invite_link = data.get("support_server_invite")
self.website = data.get("website")
self.up_count = data.get("upCount")
self.standart_banner_id = data.get("standartBannerID")
self.premium_active = data["premium"].get("active")
self.premium_splash_url = data["premium"].get("splashURL")
@ -850,5 +859,231 @@ class UserProfile(PartialUser):
return self
class LinkDomain:
pass
@dataclass(repr=False)
class MeiliIndexedBot(APIObjectBase):
"""Bot found on BotiCord
.. warning::
The result of the reverse conversion (`.to_dict()`) may not match the actual data.
"""
id: str
"""ID of the bot"""
name: str
"""Name of the bot"""
short_description: str
"""Short description of the bot"""
description: str
"""Description of the bot"""
avatar: Optional[str]
"""Avatar of the bot"""
invite: str
"""Invite link"""
premium_active: bool
"""Is premium status active? (True/False)"""
premium_banner: Optional[str]
"""Premium banner URL"""
banner: int
"""Standart banner"""
rating: int
"""Bot's rating"""
discriminator: str
"""Bot's discriminator"""
library: Optional[BotLibrary]
"""The library that the bot is based on"""
guilds: Optional[int]
"""Number of guilds"""
shards: Optional[int]
"""Number of shards"""
members: Optional[int]
"""Number of members"""
tags: List[BotTag]
"""List of bot tags"""
ups: int
"""List of bot's ups"""
@classmethod
def from_dict(cls, data: dict):
"""Generate a MeiliIndexedBot from the given data.
Parameters
----------
data: :class:`dict`
The dictionary to convert into a MeiliIndexedBot.
"""
self: MeiliIndexedBot = super().__new__(cls)
self.id = data.get("id")
self.name = data.get("name")
self.short_description = data.get("shortDescription")
self.description = data.get("description")
self.avatar = data.get("avatar")
self.invite = data.get("invite")
self.discriminator = data.get("discriminator")
self.ups = data.get("ups")
self.rating = data.get("rating")
self.banner = data.get("banner")
self.premium_active = data.get("premiumActive")
self.premium_banner = data.get("premiumBanner")
self.library = (
BotLibrary(data["library"]) if data.get("library") is not None else None
)
self.tags = [BotTag(tag) for tag in data.get("tags", [])]
self.guilds = data.get("guilds")
self.shards = data.get("shards")
self.members = data.get("members")
return self
@dataclass(repr=False)
class MeiliIndexedServer(APIObjectBase):
"""Server found on BotiCord
.. warning::
The result of the reverse conversion (`.to_dict()`) may not match the actual data.
"""
id: str
"""ID of the server"""
name: str
"""Name of the server"""
short_description: str
"""Short description of the server"""
description: str
"""Description of the server"""
avatar: Optional[str]
"""Avatar of the server"""
invite: str
"""Invite link"""
premium_active: bool
"""Is premium status active? (True/False)"""
premium_banner: Optional[str]
"""Premium banner URL"""
banner: int
"""Standart banner"""
discord_banner: Optional[str]
"""Discord banner URL"""
rating: int
"""Server's rating"""
members: Optional[int]
"""Number of members"""
tags: List[ServerTag]
"""List of server tags"""
ups: int
"""List of server's ups"""
@classmethod
def from_dict(cls, data: dict):
"""Generate a MeiliIndexedServer from the given data.
Parameters
----------
data: :class:`dict`
The dictionary to convert into a MeiliIndexedServer.
"""
self: MeiliIndexedServer = super().__new__(cls)
self.id = data.get("id")
self.name = data.get("name")
self.short_description = data.get("shortDescription")
self.description = data.get("description")
self.avatar = data.get("avatar")
self.invite = data.get("invite")
self.ups = data.get("ups")
self.rating = data.get("rating")
self.banner = data.get("banner")
self.premium_active = data.get("premiumActive")
self.premium_banner = data.get("premiumBanner")
self.discord_banner = data.get("discordBanner")
self.tags = [ServerTag(tag) for tag in data.get("tags", [])]
self.members = data.get("members")
return self
@dataclass(repr=False)
class MeiliIndexedComment(APIObjectBase):
"""Comment found on BotiCord"""
id: str
"""ID of the comment"""
author: str
"""Id of the author of the comment"""
rating: int
"""Comment's rating"""
content: str
"""Content of the comment"""
resource: str
"""Id of the resource"""
created: datetime
"""When the comment was created"""
mod_reply: Optional[str]
"""Reply to the comment"""
@classmethod
def from_dict(cls, data: dict):
"""Generate a MeiliIndexedComment from the given data.
Parameters
----------
data: :class:`dict`
The dictionary to convert into a MeiliIndexedComment.
"""
self: MeiliIndexedComment = super().__new__(cls)
self.id = data.get("id")
self.rating = data.get("rating")
self.author = data.get("author")
self.content = data.get("content")
self.resource = data.get("resource")
self.mod_reply = data.get("modReply")
self.created = datetime.utcfromtimestamp(data.get("created") / 1000)
return self

View file

@ -56,3 +56,16 @@ Users
:members:
:exclude-members: to_dict
:inherited-members:
MeiliSearch
------------
.. autoclass:: MeiliIndexedBot
:members:
.. autoclass:: MeiliIndexedServer
:members:
.. autoclass:: MeiliIndexedComment
:members: