├── README.md ├── api ├── __init__.py ├── get_api.py └── get_async_api.py ├── donationalerts ├── __init__.py └── donationalerts.py ├── setup.cfg ├── setup.py └── utils ├── .gitignore ├── __init__.py ├── base.py └── models.py /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Typing SVG 4 |

5 | 6 | # О платформе DonationAlerts 7 | **DonationAlerts** - онлайн-платформа для сбора пожертвований от зрителей в реальном времени. Часто используется стримерами и блогерами во время трансляций. Платформа предоставляет сервис для приема электронных пожертвований, уведомления о которых появляются в реальном времени. С помощью DonationAlerts создатели контента могут интегрировать различные платежные системы и настраивать персонализированные сообщения для поддерживающих. 8 | 9 | # Об API 10 | **API DonationAlerts** - это программный интерфейс приложения, который позволяет разработчикам взаимодействовать с функциональностью **DonationAlerts**. С его помощью можно автоматизировать процессы сбора информации о пожертвованиях, управления уведомлениями, и, возможно, другими аспектами, связанными с платформой. 11 | 12 | > [!NOTE] 13 | > _Для работы с **API** понадобится:_ \ 14 | > [Создать свое приложение Donation Alerts](https://www.donationalerts.com/application/clients) \ 15 | > [Официальная документация Donation Alerts API](https://www.donationalerts.com/apidoc) 16 | 17 | > [!WARNING] 18 | > **Новая версия модуля на стадии разработки** 19 | 20 | 21 | ## Установка 22 | - Установка, используя пакетный менеджер pip 23 | ``` 24 | $ pip install DonationAlertsAPI 25 | ``` 26 | - Установка с GitHub *(требуется [git](https://git-scm.com/downloads))* 27 | ``` 28 | $ git clone https://github.com/Fsoky/DonationAlertsAPI 29 | $ cd DonationAlertsAPI 30 | $ python setup.py install 31 | ``` 32 | - Или 33 | ``` 34 | $ pip install git+https://github.com/Fsoky/DonationAlertsAPI 35 | ``` 36 | 37 | ## Пример использования 38 | 39 | >[!TIP] 40 | >Если желаете работать _асинхронно_, импортируйте класс **AIODonationAlertsAPI**, методы работы аналогичны. 41 | 42 | В данном коде реализован пример простого веб-приложения на Flask, которое обеспечивает авторизацию через DonationAlerts API и получение информации о пользователе. Давайте разберем, что происходит шаг за шагом: 43 | 44 | #### Импорт библиотек: 45 | 46 | ```python 47 | from flask import Flask, redirect, request 48 | from donationalerts import DonationAlertsAPI, Scope 49 | ``` 50 | В этом блоке происходит импорт необходимых модулей. Flask используется для создания веб-приложения, а DonationAlertsAPI и Scope - для работы с DonationAlerts API и указания необходимых разрешений (scopes). 51 | 52 | #### Настройка приложения и API: 53 | 54 | ```python 55 | app = Flask(__name__) 56 | api = DonationAlertsAPI( 57 | "CLIENT_ID", 58 | "CLIENT_SECRET", 59 | "http://127.0.0.1:5000/login", 60 | [ 61 | Scope.OAUTH_USER_SHOW, 62 | Scope.OAUTH_DONATION_INDEX 63 | ] 64 | ) 65 | ``` 66 | Здесь создается экземпляр Flask-приложения (app) и объекта DonationAlertsAPI с указанием идентификатора клиента (CLIENT_ID), секрета клиента (CLIENT_SECRET), URI перенаправления после авторизации (http://127.0.0.1:5000/login) и списком разрешений (scopes). 67 | 68 | #### Маршрут для инициации авторизации: 69 | 70 | ```python 71 | @app.route("/") 72 | def index(): 73 | return redirect(api.authorize.login()) 74 | ``` 75 | При переходе на корневой URL приложения происходит перенаправление на URL авторизации DonationAlerts с использованием api.authorize.login(). 76 | 77 | #### Маршрут для обработки ответа после авторизации: 78 | 79 | ```python 80 | @app.route("/login") 81 | def login(): 82 | code = request.args.get("code") 83 | access_token = api.authorize.get_access_token(code) 84 | 85 | user = api.user.get(access_token.access_token) 86 | return user.to_dict() 87 | ``` 88 | После того, как пользователь разрешил доступ, происходит перенаправление на указанный URI (http://127.0.0.1:5000/login). Затем извлекается код доступа (code), который используется для получения токена доступа. С помощью токена доступа запрашивается информация о пользователе, и возвращается словарь с данными пользователя. 89 | 90 | #### Запуск приложения: 91 | 92 | ```python 93 | if __name__ == "__main__": 94 | app.run(debug=True) 95 | ``` 96 | Приложение запускается, если оно запускается напрямую, а не импортируется в другой скрипт. 97 | 98 | Этот код создает простое веб-приложение, которое позволяет пользователям авторизоваться через DonationAlerts, после чего выводится информация о пользователе. Важно убедиться, что идентификатор клиента и секрет клиента правильно указаны, и что URI перенаправления соответствует настройкам вашего приложения в DonationAlerts. 99 | 100 | #### Полный код: 101 | ```python 102 | from flask import Flask, redirect, request 103 | from donationalerts import DonationAlertsAPI, Scope 104 | 105 | app = Flask(__name__) 106 | api = DonationAlertsAPI( 107 | "CLIENT_ID", 108 | "CLIENT_SECRET", 109 | "http://127.0.0.1:5000/login", 110 | [ 111 | Scope.OAUTH_USER_SHOW, 112 | Scope.OAUTH_DONATION_INDEX 113 | ] 114 | ) 115 | 116 | 117 | @app.get("/") 118 | def index(): 119 | return redirect(api.authorize.login()) 120 | 121 | 122 | @app.get("/login") 123 | def login(): 124 | code = request.args.get("code") 125 | access_token = api.authorize.get_access_token(code) 126 | 127 | user = api.user.get(access_token.access_token) 128 | return user.to_dict() 129 | 130 | 131 | if __name__ == "__main__": 132 | app.run(debug=True) 133 | ``` 134 | -------------------------------------------------------------------------------- /api/__init__.py: -------------------------------------------------------------------------------- 1 | from . import get_api, get_async_api -------------------------------------------------------------------------------- /api/get_api.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import List, Dict, Any, Optional 3 | 4 | from urllib.parse import urlencode, quote 5 | from requests import Response, post, get 6 | 7 | from utils.base import DonationAlertsAPIBase 8 | from utils.models import ( 9 | Scope, 10 | AccessToken, 11 | DonationsData, 12 | Donations, 13 | User, 14 | CustomAlert 15 | ) 16 | 17 | DEFAULT_URL = "https://www.donationalerts.com/oauth/" 18 | DEFAULT_API_LINK = "https://www.donationalerts.com/api/v1/" 19 | 20 | 21 | class DonationAlertsAPIAuthorize(DonationAlertsAPIBase): 22 | 23 | def __init__( 24 | self, 25 | client_id: str, 26 | client_secret: str, 27 | redirect_uri: str, 28 | scopes: str | List[Scope] 29 | ) -> None: 30 | super().__init__( 31 | client_id, 32 | client_secret, 33 | redirect_uri, 34 | scopes 35 | ) 36 | 37 | def login(self) -> str: 38 | payload: Dict[str, Any] = { 39 | "client_id": self.client_id, 40 | "redirect_uri": self.redirect_uri, 41 | "response_type": "code", 42 | "scope": self.scopes 43 | } 44 | 45 | return f"{DEFAULT_URL}authorize?{urlencode(payload, quote_via=quote)}" 46 | 47 | def get_access_token(self, code: str) -> AccessToken: 48 | payload: Dict[str, Any] = { 49 | "client_id": self.client_id, 50 | "client_secret": self.client_secret, 51 | "grant_type": "authorization_code", 52 | "code": code, 53 | "redirect_uri": self.redirect_uri, 54 | "scope": self.scopes, 55 | } 56 | response: Response = post(f"{DEFAULT_URL}token", data=payload) 57 | 58 | return AccessToken(**response.json()) 59 | 60 | def refresh_access_token(self, refresh_token: str) -> AccessToken: 61 | headers: Dict[str, Any] = { 62 | "Content-Type": "application/x-www-form-urlencoded" 63 | } 64 | payload: Dict[str, Any] = { 65 | "grant_type": "refresh_token", 66 | "refresh_token": refresh_token, 67 | "client_id": self.client_id, 68 | "client_secret": self.client_secret, 69 | "scope": self.scopes 70 | } 71 | response: Response = post( 72 | f"{DEFAULT_URL}token", 73 | headers=headers, 74 | data=payload 75 | ) 76 | 77 | return AccessToken(**response.json()) 78 | 79 | 80 | class DonationAlertsAPIDonations: 81 | 82 | def __init__(self) -> None: 83 | pass 84 | 85 | def get(self, access_token: str, page: int=1) -> Donations: 86 | headers: Dict[str, str] = { 87 | "Authorization": f"Bearer {access_token}", 88 | "Content-Type": "application/x-www-form-urlencoded", 89 | } 90 | response: Response = get( 91 | f"{DEFAULT_API_LINK}alerts/donations?page={page}", 92 | headers=headers 93 | ) 94 | 95 | donations: DonationsData = [ 96 | DonationsData( 97 | created_at=datetime.strptime(item["created_at"], "%Y-%m-%d %H:%M:%S"), 98 | **{key: value for key, value in item.items() if key != "created_at"} 99 | ) 100 | if "created_at" in item and isinstance(item["created_at"], str) 101 | else DonationsData(**item) 102 | for item in response.json()["data"] 103 | ] 104 | 105 | return Donations(items=donations, links=response.json()["links"], meta=response.json()["meta"]) 106 | 107 | 108 | class DonationAlertsAPIUser: 109 | 110 | def __init__(self) -> None: 111 | pass 112 | 113 | def get(self, access_token: str) -> User: 114 | headers: Dict[str, str] = { 115 | "Authorization": f"Bearer {access_token}", 116 | "Content-Type": "application/x-www-form-urlencoded", 117 | } 118 | response: Response = get( 119 | f"{DEFAULT_API_LINK}user/oauth", 120 | headers=headers 121 | ) 122 | 123 | return User(**response.json()["data"]) 124 | 125 | 126 | class DonationAlertsAPICustomAlert: 127 | 128 | def __init__(self) -> None: 129 | pass 130 | 131 | def send( 132 | self, 133 | access_token: str, 134 | external_id: str, 135 | header: str, 136 | message: str, 137 | *, 138 | is_shown: int=0, 139 | image_url: Optional[str] | None=None, 140 | sound_url: Optional[str] | None=None 141 | ) -> CustomAlert: 142 | headers: Dict[str, str] = { 143 | "Authorization": f"Bearer {access_token}", 144 | "Content-Type": "application/x-www-form-urlencoded", 145 | } 146 | payload: Dict[str, Any] = { 147 | "external_id": external_id, 148 | "header": header, 149 | "message": message, 150 | "is_shown": is_shown, 151 | "image_url": image_url, 152 | "sound_url": sound_url 153 | } 154 | response: Response = post( 155 | f"{DEFAULT_API_LINK}custom_alert", 156 | headers=headers, 157 | data=payload 158 | ) 159 | 160 | return CustomAlert(**response.json()["data"]) -------------------------------------------------------------------------------- /api/get_async_api.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import List, Dict, Any, Optional 3 | 4 | from urllib.parse import urlencode, quote 5 | 6 | from .get_api import DEFAULT_URL, DEFAULT_API_LINK 7 | 8 | from utils.base import AIODonationAlertsAPIBase 9 | from utils.models import ( 10 | Scope, 11 | AccessToken, 12 | DonationsData, 13 | Donations, 14 | User, 15 | CustomAlert 16 | ) 17 | 18 | 19 | class AIODonationAlertsAPIAuthorize(AIODonationAlertsAPIBase): 20 | 21 | def __init__( 22 | self, 23 | client_id: str, 24 | client_secret: str, 25 | redirect_uri: str, 26 | scopes: str | List[Scope] 27 | ) -> None: 28 | super().__init__( 29 | client_id, 30 | client_secret, 31 | redirect_uri, 32 | scopes 33 | ) 34 | 35 | async def login(self) -> str: 36 | payload: Dict[str, Any] = { 37 | "client_id": self.client_id, 38 | "redirect_uri": self.redirect_uri, 39 | "response_type": "code", 40 | "scope": self.scopes 41 | } 42 | 43 | return f"{DEFAULT_URL}authorize?{urlencode(payload, quote_via=quote)}" 44 | 45 | async def get_access_token(self, code: str) -> AccessToken: 46 | payload: Dict[str, Any] = { 47 | "client_id": self.client_id, 48 | "client_secret": self.client_secret, 49 | "grant_type": "authorization_code", 50 | "code": code, 51 | "redirect_uri": self.redirect_uri, 52 | "scope": self.scopes, 53 | } 54 | 55 | async with self.session.post(f"{DEFAULT_URL}token", data=payload) as response: 56 | response_obj: Dict[str, Any] = await response.json() 57 | return AccessToken(**response_obj) 58 | 59 | async def refresh_access_token(self, refresh_token: str) -> AccessToken: 60 | headers: Dict[str, str] = { 61 | "Content-Type": "application/x-www-form-urlencoded" 62 | } 63 | payload: Dict[str, Any] = { 64 | "grant_type": "refresh_token", 65 | "refresh_token": refresh_token, 66 | "client_id": self.client_id, 67 | "client_secret": self.client_secret, 68 | "scope": self.scopes 69 | } 70 | 71 | async with self.session.post(f"{DEFAULT_URL}token", headers=headers, data=payload) as response: 72 | response_obj: Dict[str, Any] = await response.json() 73 | return AccessToken(**response_obj) 74 | 75 | 76 | class AIODonationAlertsAPIUser(AIODonationAlertsAPIBase): 77 | 78 | def __init__(self) -> None: 79 | pass 80 | 81 | async def get(self, access_token: str) -> User: 82 | headers: Dict[str, str] = { 83 | "Authorization": f"Bearer {access_token}", 84 | "Content-Type": "application/x-www-form-urlencoded", 85 | } 86 | 87 | async with self.session.get(f"{DEFAULT_API_LINK}user/oauth", headers=headers) as response: 88 | response_obj: Dict[str, Any] = await response.json() 89 | return User(**response_obj) 90 | 91 | 92 | class AIODonationAlertsAPIDonations(AIODonationAlertsAPIBase): 93 | 94 | def __init__(self) -> None: 95 | pass 96 | 97 | async def get(self, access_token: str, page: int=1) -> Donations: 98 | headers: Dict[str, str] = { 99 | "Authorization": f"Bearer {access_token}", 100 | "Content-Type": "application/x-www-form-urlencoded", 101 | } 102 | 103 | async with self.session.get(f"{DEFAULT_API_LINK}alerts/donations?page={page}", headers=headers) as response: 104 | response_obj: Dict[str, Any] = await response.json() 105 | donations: DonationsData = [ 106 | DonationsData( 107 | created_at=datetime.strptime(item["created_at"], "%Y-%m-%d %H:%M:%S"), 108 | **{key: value for key, value in item.items() if key != "created_at"} 109 | ) 110 | if "created_at" in item and isinstance(item["created_at"], str) 111 | else DonationsData(**item) 112 | for item in response_obj["data"] 113 | ] 114 | 115 | return Donations(items=donations, links=response_obj["links"], meta=response_obj["meta"]) 116 | 117 | 118 | class AIODonationAlertsAPICustomAlert(AIODonationAlertsAPIBase): 119 | 120 | def __init__(self) -> None: 121 | pass 122 | 123 | async def send( 124 | self, 125 | access_token: str, 126 | external_id: str, 127 | header: str, 128 | message: str, 129 | *, 130 | is_shown: int=0, 131 | image_url: Optional[str] | None=None, 132 | sound_url: Optional[str] | None=None 133 | ) -> CustomAlert: 134 | headers: Dict[str, str] = { 135 | "Authorization": f"Bearer {access_token}", 136 | "Content-Type": "application/x-www-form-urlencoded", 137 | } 138 | payload: Dict[str, Any] = { 139 | "external_id": external_id, 140 | "header": header, 141 | "message": message, 142 | "is_shown": is_shown, 143 | "image_url": image_url, 144 | "sound_url": sound_url 145 | } 146 | 147 | async with self.session.post(f"{DEFAULT_API_LINK}custom_alert", headers=headers, data=payload) as response: 148 | response_obj: Dict[str, Any] = await response.json() 149 | return CustomAlert(**response_obj["data"]) -------------------------------------------------------------------------------- /donationalerts/__init__.py: -------------------------------------------------------------------------------- 1 | from .donationalerts import DonationAlertsAPI, AIODonationAlertsAPI, Scope -------------------------------------------------------------------------------- /donationalerts/donationalerts.py: -------------------------------------------------------------------------------- 1 | from api.get_api import ( 2 | DonationAlertsAPIAuthorize, 3 | DonationAlertsAPIDonations, 4 | DonationAlertsAPIUser, 5 | DonationAlertsAPICustomAlert 6 | ) 7 | from api.get_async_api import ( 8 | AIODonationAlertsAPIAuthorize, 9 | AIODonationAlertsAPIUser, 10 | AIODonationAlertsAPIDonations, 11 | AIODonationAlertsAPICustomAlert 12 | ) 13 | 14 | from utils.base import DonationAlertsAPIBase, AIODonationAlertsAPIBase 15 | from utils.models import Scope 16 | 17 | 18 | class DonationAlertsAPI(DonationAlertsAPIBase): 19 | 20 | def __init__( 21 | self, 22 | client_id: str, 23 | client_secret: str, 24 | redirect_uri: str, 25 | scopes: str | list[Scope] 26 | ) -> None: 27 | super().__init__( 28 | client_id, 29 | client_secret, 30 | redirect_uri, 31 | scopes 32 | ) 33 | 34 | @property 35 | def authorize(self) -> DonationAlertsAPIAuthorize: 36 | return DonationAlertsAPIAuthorize( 37 | self.client_id, 38 | self.client_secret, 39 | self.redirect_uri, 40 | self.scopes 41 | ) 42 | 43 | @property 44 | def donations(self) -> DonationAlertsAPIDonations: 45 | return DonationAlertsAPIDonations() 46 | 47 | @property 48 | def user(self) -> DonationAlertsAPIUser: 49 | return DonationAlertsAPIUser() 50 | 51 | @property 52 | def custom_alert(self) -> DonationAlertsAPICustomAlert: 53 | return DonationAlertsAPICustomAlert() 54 | 55 | 56 | class AIODonationAlertsAPI(AIODonationAlertsAPIBase): 57 | def __init__( 58 | self, 59 | client_id: str, 60 | client_secret: str, 61 | redirect_uri: str, 62 | scopes: str | list[Scope] 63 | ) -> None: 64 | super().__init__( 65 | client_id, 66 | client_secret, 67 | redirect_uri, 68 | scopes 69 | ) 70 | 71 | async def __aenter__(self): 72 | return self 73 | 74 | async def __aexit__(self, exc_type, exc_value, traceback): 75 | return self.session.close() 76 | 77 | @property 78 | async def authorize(self) -> AIODonationAlertsAPIAuthorize: 79 | return AIODonationAlertsAPIAuthorize( 80 | self.client_id, 81 | self.client_secret, 82 | self.redirect_uri, 83 | self.scopes 84 | ) 85 | 86 | @property 87 | async def donations(self) -> AIODonationAlertsAPIDonations: 88 | return AIODonationAlertsAPIDonations() 89 | 90 | @property 91 | async def user(self) -> AIODonationAlertsAPIUser: 92 | return AIODonationAlertsAPIUser() 93 | 94 | @property 95 | async def custom_alert(self) -> AIODonationAlertsAPICustomAlert: 96 | return AIODonationAlertsAPICustomAlert() -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [egg_info] 2 | tag_build = 3 | tag_date = 0 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from io import open 3 | 4 | 5 | def read(filename): 6 | with open(filename, "r", encoding="utf-8") as file: 7 | return file.read() 8 | 9 | 10 | setup(name="DonationAlertsAPI", 11 | version="2.1", 12 | description="Module for simple work with API Donation Alerts", 13 | long_description=read("README.md"), 14 | long_description_content_type="text/markdown", 15 | author="Fsoky", 16 | author_email="cyberuest0x12@gmail.com", 17 | url="https://github.com/Fsoky/DonationAlertsAPI", 18 | keywords="api donation donationalerts python", 19 | packages=find_packages() 20 | ) -------------------------------------------------------------------------------- /utils/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/python 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=python 3 | 4 | ### Python ### 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | cover/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | .pybuilder/ 80 | target/ 81 | 82 | # Jupyter Notebook 83 | .ipynb_checkpoints 84 | 85 | # IPython 86 | profile_default/ 87 | ipython_config.py 88 | 89 | # pyenv 90 | # For a library or package, you might want to ignore these files since the code is 91 | # intended to run in multiple environments; otherwise, check them in: 92 | # .python-version 93 | 94 | # pipenv 95 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 96 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 97 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 98 | # install all needed dependencies. 99 | #Pipfile.lock 100 | 101 | # poetry 102 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 103 | # This is especially recommended for binary packages to ensure reproducibility, and is more 104 | # commonly ignored for libraries. 105 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 106 | #poetry.lock 107 | 108 | # pdm 109 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 110 | #pdm.lock 111 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 112 | # in version control. 113 | # https://pdm.fming.dev/#use-with-ide 114 | .pdm.toml 115 | 116 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 117 | __pypackages__/ 118 | 119 | # Celery stuff 120 | celerybeat-schedule 121 | celerybeat.pid 122 | 123 | # SageMath parsed files 124 | *.sage.py 125 | 126 | # Environments 127 | .env 128 | .venv 129 | env/ 130 | venv/ 131 | ENV/ 132 | env.bak/ 133 | venv.bak/ 134 | 135 | # Spyder project settings 136 | .spyderproject 137 | .spyproject 138 | 139 | # Rope project settings 140 | .ropeproject 141 | 142 | # mkdocs documentation 143 | /site 144 | 145 | # mypy 146 | .mypy_cache/ 147 | .dmypy.json 148 | dmypy.json 149 | 150 | # Pyre type checker 151 | .pyre/ 152 | 153 | # pytype static type analyzer 154 | .pytype/ 155 | 156 | # Cython debug symbols 157 | cython_debug/ 158 | 159 | # PyCharm 160 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 161 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 162 | # and can be added to the global gitignore or merged into this file. For a more nuclear 163 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 164 | #.idea/ 165 | 166 | ### Python Patch ### 167 | # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration 168 | poetry.toml 169 | 170 | # ruff 171 | .ruff_cache/ 172 | 173 | # LSP config files 174 | pyrightconfig.json 175 | 176 | # End of https://www.toptal.com/developers/gitignore/api/python -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- 1 | from . import models, base -------------------------------------------------------------------------------- /utils/base.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from .models import Scope 4 | from aiohttp import ClientSession 5 | 6 | 7 | class DonationAlertsAPIBase: 8 | 9 | def __init__( 10 | self, 11 | client_id: str, 12 | client_secret: str, 13 | redirect_uri: str, 14 | scopes: str | List[Scope] 15 | ) -> None: 16 | self.client_id = client_id 17 | self.client_secret = client_secret 18 | self.redirect_uri = redirect_uri 19 | self.scopes = scopes 20 | 21 | if isinstance(scopes, list): 22 | self.scopes = " ".join([scope.value for scope in scopes]) 23 | 24 | 25 | class AIODonationAlertsAPIBase: 26 | 27 | def __init__( 28 | self, 29 | client_id: str=None, 30 | client_secret: str=None, 31 | redirect_uri: str=None, 32 | scopes: str | List[Scope]=None 33 | ) -> None: 34 | self.client_id = client_id 35 | self.client_secret = client_secret 36 | self.redirect_uri = redirect_uri 37 | self.scopes = scopes 38 | 39 | self.session: ClientSession = ClientSession() 40 | 41 | if isinstance(scopes, list): 42 | self.scopes = " ".join([scope.value for scope in scopes]) -------------------------------------------------------------------------------- /utils/models.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional, Any, Dict 2 | from enum import Enum 3 | from datetime import datetime 4 | from pydantic import BaseModel 5 | 6 | 7 | class Scope(Enum): 8 | OAUTH_USER_SHOW: str = "oauth-user-show" 9 | OAUTH_DONATION_SUBSCRIBE: str = "oauth-donation-subscribe" 10 | OAUTH_DONATION_INDEX: str = "oauth-donation-index" 11 | OAUTH_CUSTOM_ALERT_STORE: str = "oauth-custom_alert-store" 12 | OAUTH_GOAL_SUBSCRIBE: str = "oauth-goal-subscribe" 13 | OAUTH_POLL_SUBSCRIBE: str = "oauth-poll-subscribe" 14 | 15 | def __repr__(self): 16 | return self.value 17 | 18 | @classmethod 19 | def all_scopes(cls) -> List[str]: 20 | return list(cls) 21 | 22 | 23 | class CentrifugoChannel(Enum): 24 | NEW_DONATION_ALERTS: str = "$alerts:donation_" 25 | DONATION_GOALS_UPDATES: str = "$goals:goal_" 26 | POLLS_UPDATES: str = "$poll:poll_" 27 | 28 | def __repr__(self) -> str: 29 | return self.value 30 | 31 | @classmethod 32 | def all_channels(cls) -> List[str]: 33 | return list(cls) 34 | 35 | 36 | class AccessToken(BaseModel): 37 | token_type: str 38 | access_token: str 39 | expires_in: int 40 | refresh_token: str 41 | 42 | 43 | class DonationsData(BaseModel): 44 | id: int 45 | name: str 46 | username: str 47 | message_type: str 48 | message: str 49 | amount: int 50 | currency: str 51 | is_shown: int 52 | created_at: datetime 53 | shown_at: Optional[str] | None 54 | 55 | 56 | class DonationsLinks(BaseModel): 57 | first: str 58 | last: str 59 | prev: Optional[Any] | None = None 60 | next: Optional[Any] | None = None 61 | 62 | 63 | class DonationsMeta(BaseModel): 64 | current_page: int 65 | from_page: int 66 | last_page: int 67 | path: str 68 | per_page: int 69 | to: int 70 | total: int 71 | 72 | 73 | class Donations(BaseModel): 74 | items: List[DonationsData] 75 | links: Dict[str, DonationsLinks] 76 | meta: Dict[str, DonationsMeta] 77 | 78 | 79 | class User(BaseModel): 80 | id: int 81 | code: str 82 | name: str 83 | avatar: str 84 | email: str 85 | is_active: int 86 | language: str 87 | socket_connection_token: str 88 | 89 | 90 | class CustomAlert(BaseModel): 91 | id: int 92 | external_id: Optional[int] | None 93 | header: Optional[str] | None 94 | message: Optional[str] | None 95 | image_url: Optional[str] | None 96 | sound_url: Optional[str] | None 97 | is_shown: int 98 | created_at: datetime 99 | shown_at: Optional[str] | None 100 | 101 | 102 | class CentrifugoEventResponse(BaseModel): 103 | pass --------------------------------------------------------------------------------