From 4929d20389cc9b9baf1a09e0c40ceb9bf8c728c9 Mon Sep 17 00:00:00 2001 From: Ram Date: Sat, 14 Jun 2025 01:55:00 -0400 Subject: [PATCH] Update to webook and ratelimitter --- python-sdk-master.zip | Bin 0 -> 59365 bytes topgg/ratelimiter.py | 127 ++++--------- topgg/webhook.py | 418 +++++++++--------------------------------- 3 files changed, 126 insertions(+), 419 deletions(-) create mode 100644 python-sdk-master.zip diff --git a/python-sdk-master.zip b/python-sdk-master.zip new file mode 100644 index 0000000000000000000000000000000000000000..0bd61cf4fda1a4d48147bc4b41c3ff513732a6f1 GIT binary patch literal 59365 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 Nce4)0ws}BC_F8)}5FL&|pck+kC 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 TGO)5Um>L^18XKDcN_hQ0XBQrd literal 0 HcmV?d00001 diff --git a/topgg/ratelimiter.py b/topgg/ratelimiter.py index 028a98ee..e6ab6b9e 100644 --- a/topgg/ratelimiter.py +++ b/topgg/ratelimiter.py @@ -1,110 +1,63 @@ -# -*- coding: utf-8 -*- - -# The MIT License (MIT) - -# Copyright (c) 2021 Assanali Mukhanov - -# Permission is hereby granted, free of charge, to any person obtaining a -# copy of this software and associated documentation files (the "Software"), -# to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, -# and/or sell copies of the Software, and to permit persons to whom the -# Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. - -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. - -import asyncio -import collections -from datetime import datetime +from collections.abc import Iterable from types import TracebackType -from typing import Any, Awaitable, Callable, List, Optional, Type +from collections import deque +from time import time +import asyncio +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Optional +# Shared reusable __aexit__ logic +async def shared_aexit(self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None) -> None: + async with self.__lock: + self.__calls.append(time()) + while self._timespan >= self.__period: + self.__calls.popleft() -class AsyncRateLimiter: - """ - Provides rate limiting for an operation with a configurable number of requests for a time period. - """ - __lock: asyncio.Lock - callback: Optional[Callable[[float], Awaitable[Any]]] - max_calls: int - period: float - calls: collections.deque +class Ratelimiter: + """Handles ratelimits for a specific endpoint.""" - def __init__( - self, - max_calls: int, - period: float = 1.0, - callback: Optional[Callable[[float], Awaitable[Any]]] = None, - ): - if period <= 0: - raise ValueError("Rate limiting period should be > 0") - if max_calls <= 0: - raise ValueError("Rate limiting number of calls should be > 0") - self.calls = collections.deque() + __slots__ = ('__lock', '__max_calls', '__period', '__calls') - self.period = period - self.max_calls = max_calls - self.callback = callback + def __init__(self, max_calls: int, period: float = 1.0): + self.__calls = deque() + self.__period = period + self.__max_calls = max_calls self.__lock = asyncio.Lock() - async def __aenter__(self) -> "AsyncRateLimiter": + async def __aenter__(self) -> 'Ratelimiter': async with self.__lock: - if len(self.calls) >= self.max_calls: - until = datetime.utcnow().timestamp() + self.period - self._timespan - if self.callback: - asyncio.ensure_future(self.callback(until)) - sleep_time = until - datetime.utcnow().timestamp() + if len(self.__calls) >= self.__max_calls: + until = time() + self.__period - self._timespan + sleep_time = until - time() if sleep_time > 0: await asyncio.sleep(sleep_time) - return self - - async def __aexit__( - self, - exc_type: Type[BaseException], - exc_val: BaseException, - exc_tb: TracebackType, - ) -> None: - async with self.__lock: - # Store the last operation timestamp. - self.calls.append(datetime.utcnow().timestamp()) + return self - while self._timespan >= self.period: - self.calls.popleft() + # Assign shared logic + __aexit__ = shared_aexit @property def _timespan(self) -> float: - return self.calls[-1] - self.calls[0] + return self.__calls[-1] - self.__calls[0] if len(self.__calls) >= 2 else 0.0 + +class Ratelimiters: + """Handles ratelimits for multiple endpoints.""" -class AsyncRateLimiterManager: - rate_limiters: List[AsyncRateLimiter] + __slots__ = ('__ratelimiters',) - def __init__(self, rate_limiters: List[AsyncRateLimiter]): - self.rate_limiters = rate_limiters + def __init__(self, ratelimiters: Iterable[Ratelimiter]): + self.__ratelimiters = tuple(ratelimiters) - async def __aenter__(self) -> "AsyncRateLimiterManager": - [await manager.__aenter__() for manager in self.rate_limiters] + async def __aenter__(self) -> 'Ratelimiters': + for ratelimiter in self.__ratelimiters: + await ratelimiter.__aenter__() return self - async def __aexit__( - self, - exc_type: Type[BaseException], - exc_val: BaseException, - exc_tb: TracebackType, - ) -> None: + async def __aexit__(self, exc_type, exc_val, exc_tb): await asyncio.gather( - *[ - manager.__aexit__(exc_type, exc_val, exc_tb) - for manager in self.rate_limiters - ] + *(r.__aexit__(exc_type, exc_val, exc_tb) for r in self.__ratelimiters) ) diff --git a/topgg/webhook.py b/topgg/webhook.py index 4b94ec2b..b7365e46 100644 --- a/topgg/webhook.py +++ b/topgg/webhook.py @@ -1,368 +1,122 @@ -# -*- coding: utf-8 -*- - -# The MIT License (MIT) - -# Copyright (c) 2021 Assanali Mukhanov - -# Permission is hereby granted, free of charge, to any person obtaining a -# copy of this software and associated documentation files (the "Software"), -# to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, -# and/or sell copies of the Software, and to permit persons to whom the -# Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. - -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. - -__all__ = [ - "endpoint", - "BoundWebhookEndpoint", - "WebhookEndpoint", - "WebhookManager", - "WebhookType", -] - -import enum -import typing as t - -import aiohttp +from collections.abc import Awaitable, Callable +from typing import Any, Optional, Union +from inspect import isawaitable +from urllib import parse from aiohttp import web -from topgg.errors import TopGGException +RawCallback = Callable[[web.Request], Awaitable[web.StreamResponse]] +OnVoteCallback = Callable[["Vote"], Any] +OnVoteDecorator = Callable[[OnVoteCallback], RawCallback] -from .data import DataContainerMixin -from .types import BotVoteData, GuildVoteData -if t.TYPE_CHECKING: - from aiohttp.web import Request, StreamResponse +class Vote: + """A dispatched Top.gg vote event.""" -T = t.TypeVar("T", bound="WebhookEndpoint") -_HandlerT = t.Callable[["Request"], t.Awaitable["StreamResponse"]] + __slots__ = ("receiver_id", "voter_id", "is_server", "is_test", "is_weekend", "query") + def __init__(self, json: dict[str, Any]) -> None: + self.receiver_id = int(json.get("bot", json.get("guild"))) + self.voter_id = int(json["user"]) + self.is_server = "guild" in json + self.is_test = json["type"] == "test" + self.is_weekend = bool(json.get("isWeekend")) -class WebhookType(enum.Enum): - """An enum that represents the type of an endpoint.""" + query_str = json.get("query") + self.query = { + k: v[0] for k, v in parse.parse_qs(parse.urlsplit(query_str).query).items() + } if query_str else {} - BOT = enum.auto() - """Marks the endpoint as a bot webhook.""" + def __repr__(self) -> str: + return f"" - GUILD = enum.auto() - """Marks the endpoint as a guild webhook.""" - -class WebhookManager(DataContainerMixin): +class Webhooks: """ - A class for managing Top.gg webhooks. + Receive events from the Top.gg servers. + + :param auth: The default password to use. + :param port: The default port to use. """ - __app: web.Application - _webserver: web.TCPSite - _is_closed: bool - __slots__ = ("__app", "_webserver", "_is_running") + __slots__ = ("__app", "__server", "__default_auth", "__default_port", "__running") - def __init__(self) -> None: - super().__init__() + def __init__(self, auth: Optional[str] = None, port: Optional[int] = None) -> None: self.__app = web.Application() - self._is_running = False - - @t.overload - def endpoint(self, endpoint_: None = None) -> "BoundWebhookEndpoint": - ... - - @t.overload - def endpoint(self, endpoint_: "WebhookEndpoint") -> "WebhookManager": - ... - - def endpoint(self, endpoint_: t.Optional["WebhookEndpoint"] = None) -> t.Any: - """Helper method that returns a WebhookEndpoint object. - - Args: - `endpoint_` (:obj:`typing.Optional` [ :obj:`WebhookEndpoint` ]) - The endpoint to add. - - Returns: - :obj:`typing.Union` [ :obj:`WebhookManager`, :obj:`BoundWebhookEndpoint` ]: - An instance of :obj:`WebhookManager` if endpoint was provided, - otherwise :obj:`BoundWebhookEndpoint`. - - Raises: - :obj:`~.errors.TopGGException` - If the endpoint is lacking attributes. - """ - if endpoint_: - if not hasattr(endpoint_, "_callback"): - raise TopGGException("endpoint missing callback.") - - if not hasattr(endpoint_, "_type"): - raise TopGGException("endpoint missing type.") - - if not hasattr(endpoint_, "_route"): - raise TopGGException("endpoint missing route.") - - self.app.router.add_post( - endpoint_._route, - self._get_handler( - endpoint_._type, endpoint_._auth, endpoint_._callback - ), - ) - return self - - return BoundWebhookEndpoint(manager=self) - - async def start(self, port: int) -> None: - """Runs the webhook. - - Args: - port (int) - The port to run the webhook on. - """ - runner = web.AppRunner(self.__app) - await runner.setup() - self._webserver = web.TCPSite(runner, "0.0.0.0", port) - await self._webserver.start() - self._is_running = True + self.__server = None + self.__default_auth = auth + self.__default_port = port + self.__running = False - @property - def is_running(self) -> bool: - """Returns whether or not the webserver is running.""" - return self._is_running + def __repr__(self) -> str: + return f"" - @property - def app(self) -> web.Application: - """Returns the internal web application that handles webhook requests. + def on_vote( + self, + route: str, + auth: Optional[str] = None, + callback: Optional[OnVoteCallback] = None + ) -> Union[OnVoteCallback, OnVoteDecorator]: + if not isinstance(route, str): + raise TypeError("Missing route argument.") - Returns: - :class:`aiohttp.web.Application`: - The internal web application. - """ - return self.__app + effective_auth = auth or self.__default_auth + if not effective_auth: + raise TypeError("Missing password.") - async def close(self) -> None: - """Stops the webhook.""" - await self._webserver.stop() - self._is_running = False - - def _get_handler( - self, type_: WebhookType, auth: str, callback: t.Callable[..., t.Any] - ) -> _HandlerT: - async def _handler(request: aiohttp.web.Request) -> web.Response: - if request.headers.get("Authorization", "") != auth: - return web.Response(status=401, text="Unauthorized") - - data = await request.json() - await self._invoke_callback( - callback, - (BotVoteData if type_ is WebhookType.BOT else GuildVoteData)(**data), - ) - return web.Response(status=200, text="OK") - - return _handler + def decorator(inner_callback: OnVoteCallback) -> RawCallback: + async def handler(request: web.Request) -> web.Response: + if request.headers.get("Authorization") != effective_auth: + return web.Response(status=401, text="Unauthorized") + result = inner_callback(Vote(await request.json())) + if isawaitable(result): + await result -CallbackT = t.Callable[..., t.Any] + return web.Response(status=200, text="OK") + self.__app.router.add_post(route, handler) + return handler -class WebhookEndpoint: - """ - A helper class to setup webhook endpoint. - """ - - __slots__ = ("_callback", "_auth", "_route", "_type") + if callback: + decorator(callback) + return callback - def __init__(self) -> None: - self._auth = "" + return decorator - def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any: - return self._callback(*args, **kwargs) + async def start(self, port: Optional[int] = None) -> None: + if self.running: + return - def type(self: T, type_: WebhookType) -> T: - """Sets the type of this endpoint. + port = port or self.__default_port + if port is None: + raise TypeError("Missing port.") - Args: - `type_` (:obj:`WebhookType`) - The type of the endpoint. + runner = web.AppRunner(self.__app) + await runner.setup() - Returns: - :obj:`WebhookEndpoint` - """ - self._type = type_ - return self + self.__server = web.TCPSite(runner, "0.0.0.0", port) + await self.__server.start() - def route(self: T, route_: str) -> T: - """ - Sets the route of this endpoint. + self.__running = True - Args: - `route_` (str) - The route of this endpoint. + async def close(self) -> None: + if not self.running: + return - Returns: - :obj:`WebhookEndpoint` - """ - self._route = route_ - return self + await self.__server.stop() + self.__running = False - def auth(self: T, auth_: str) -> T: - """ - Sets the auth of this endpoint. + @property + def running(self) -> bool: + return self.__running - Args: - `auth_` (str) - The auth of this endpoint. + @property + def app(self) -> web.Application: + return self.__app - Returns: - :obj:`WebhookEndpoint` - """ - self._auth = auth_ + async def __aenter__(self) -> "Webhooks": + await self.start() return self - @t.overload - def callback(self, callback_: None) -> t.Callable[[CallbackT], CallbackT]: - ... - - @t.overload - def callback(self: T, callback_: CallbackT) -> T: - ... - - def callback(self, callback_: t.Any = None) -> t.Any: - """ - Registers a vote callback, called whenever this endpoint receives POST requests. - - The callback can be either sync or async. - This method can be used as a decorator or a decorator factory. - - :Example: - .. code-block:: python - - import topgg - - webhook_manager = topgg.WebhookManager() - endpoint = ( - topgg.WebhookEndpoint() - .type(topgg.WebhookType.BOT) - .route("/dblwebhook") - .auth("youshallnotpass") - ) - - # The following are valid. - endpoint.callback(lambda vote_data: print("Receives a vote!", vote_data)) - - # Used as decorator, the decorated function will become the WebhookEndpoint object. - @endpoint.callback - def endpoint(vote_data: topgg.BotVoteData): - ... - - # Used as decorator factory, the decorated function will still be the function itself. - @endpoint.callback() - def on_vote(vote_data: topgg.BotVoteData): - ... - - webhook_manager.endpoint(endpoint) - """ - if callback_ is not None: - self._callback = callback_ - return self - - return self.callback - - -class BoundWebhookEndpoint(WebhookEndpoint): - """ - A WebhookEndpoint with a WebhookManager bound to it. - - You can instantiate this object using the :meth:`WebhookManager.endpoint` method. - - :Example: - .. code-block:: python - - import topgg - - webhook_manager = ( - topgg.WebhookManager() - .endpoint() - .type(topgg.WebhookType.BOT) - .route("/dblwebhook") - .auth("youshallnotpass") - ) - - # The following are valid. - endpoint.callback(lambda vote_data: print("Receives a vote!", vote_data)) - - # Used as decorator, the decorated function will become the BoundWebhookEndpoint object. - @endpoint.callback - def endpoint(vote_data: topgg.BotVoteData): - ... - - # Used as decorator factory, the decorated function will still be the function itself. - @endpoint.callback() - def on_vote(vote_data: topgg.BotVoteData): - ... - - endpoint.add_to_manager() - """ - - __slots__ = ("manager",) - - def __init__(self, manager: WebhookManager): - super().__init__() - self.manager = manager - - def add_to_manager(self) -> WebhookManager: - """ - Adds this endpoint to the webhook manager. - - Returns: - :obj:`WebhookManager` - - Raises: - :obj:`errors.TopGGException`: - If the object lacks attributes. - """ - self.manager.endpoint(self) - return self.manager - - -def endpoint( - route: str, type: WebhookType, auth: str = "" -) -> t.Callable[[t.Callable[..., t.Any]], WebhookEndpoint]: - """ - A decorator factory for instantiating WebhookEndpoint. - - Args: - route (str) - The route for the endpoint. - type (WebhookType) - The type of the endpoint. - auth (str) - The auth for the endpoint. - - Returns: - :obj:`typing.Callable` [[ :obj:`typing.Callable` [..., :obj:`typing.Any` ]], :obj:`WebhookEndpoint` ]: - The actual decorator. - - :Example: - .. code-block:: python - - import topgg - - @topgg.endpoint("/dblwebhook", WebhookType.BOT, "youshallnotpass") - async def on_vote( - vote_data: topgg.BotVoteData, - # database here is an injected data - database: Database = topgg.data(Database), - ): - ... - """ - - def decorator(func: t.Callable[..., t.Any]) -> WebhookEndpoint: - return WebhookEndpoint().route(route).type(type).auth(auth).callback(func) - - return decorator + async def __aexit__(self, *_: Any, **__: Any) -> None: + await self.close()