├── quant ├── tests │ └── __init__.py ├── api │ ├── entities │ │ └── __init__.py │ ├── __init__.py │ └── core │ │ ├── __init__.py │ │ └── http_manager_abc.py ├── utils │ ├── cache │ │ ├── __init__.py │ │ └── cacheable.py │ ├── asyncio_utils.py │ ├── __init__.py │ └── parser.py ├── impl │ ├── core │ │ ├── exceptions │ │ │ ├── __init__.py │ │ │ ├── event_exceptions.py │ │ │ ├── library_exception.py │ │ │ ├── http_exception.py │ │ │ └── command_exceptions.py │ │ ├── __init__.py │ │ ├── shard.py │ │ ├── http_bot.py │ │ ├── commands.py │ │ ├── context.py │ │ ├── http_manager.py │ │ └── route.py │ ├── __init__.py │ ├── events │ │ ├── guild │ │ │ ├── member_event.py │ │ │ ├── voice_server_update_event.py │ │ │ ├── guild_create_event.py │ │ │ ├── channel_create_event.py │ │ │ ├── __init__.py │ │ │ ├── reaction_event.py │ │ │ ├── voice_state_update_event.py │ │ │ └── message_event.py │ │ ├── bot │ │ │ ├── __init__.py │ │ │ ├── interaction_create_event.py │ │ │ ├── raw_event.py │ │ │ ├── ready_event.py │ │ │ └── exception_event.py │ │ ├── __init__.py │ │ ├── event.py │ │ └── types.py │ └── files.py ├── entities │ ├── color.py │ ├── api │ │ ├── __init__.py │ │ └── backend.py │ ├── factory │ │ ├── __init__.py │ │ └── event_controller.py │ ├── modal │ │ ├── __init__.py │ │ ├── text_input.py │ │ └── modal.py │ ├── model.py │ ├── interactions │ │ ├── component_types.py │ │ ├── __init__.py │ │ ├── choice_response.py │ │ ├── application_command.py │ │ └── slash_option.py │ ├── voice_server_update.py │ ├── message_flags.py │ ├── gateway.py │ ├── locales.py │ ├── action_row.py │ ├── voice_state_update.py │ ├── ratelimits │ │ └── ratelimit.py │ ├── webhook.py │ ├── roles.py │ ├── poll.py │ ├── snowflake.py │ ├── allowed_mentions.py │ ├── intents.py │ ├── invite.py │ ├── user.py │ ├── integration.py │ ├── permissions.py │ ├── emoji.py │ ├── button.py │ ├── member.py │ ├── embeds.py │ ├── __init__.py │ ├── channel.py │ ├── activity.py │ └── guild.py └── __init__.py ├── .flake8 ├── .gitignore ├── assets ├── banner.png └── icon.png ├── docs ├── requirements.txt ├── source │ ├── locales.rst │ ├── client.rst │ ├── message.rst │ ├── rest.rst │ ├── components.rst │ ├── activity.rst │ ├── index.rst │ ├── start.rst │ └── conf.py ├── Makefile └── make.bat ├── .readthedocs.yaml ├── pyproject.toml ├── setup.py ├── LICENSE ├── examples ├── loader.py ├── bot.py └── music.py └── README.md /quant/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /quant/api/entities/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | per-file-ignores = __init__.py:F401 3 | -------------------------------------------------------------------------------- /quant/utils/cache/__init__.py: -------------------------------------------------------------------------------- 1 | from .cacheable import CacheableType 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .idea 3 | *.pyc 4 | est.py 5 | tests.py 6 | .env -------------------------------------------------------------------------------- /assets/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantDiscord/quant/HEAD/assets/banner.png -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantDiscord/quant/HEAD/assets/icon.png -------------------------------------------------------------------------------- /quant/impl/core/exceptions/__init__.py: -------------------------------------------------------------------------------- 1 | from .command_exceptions import NotEnoughPermissions 2 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx==7.1.2 2 | sphinx-rtd-theme==1.3.0rc1 3 | attrs 4 | aiohttp 5 | typing_extensions 6 | furo -------------------------------------------------------------------------------- /docs/source/locales.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: quant 2 | 3 | Discord Locales 4 | ------- 5 | 6 | .. autoclass:: DiscordLocale 7 | -------------------------------------------------------------------------------- /docs/source/client.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: quant 2 | 3 | Client 4 | ====== 5 | 6 | .. attributetable:: Client 7 | 8 | .. autoclass:: Client 9 | -------------------------------------------------------------------------------- /docs/source/message.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: quant 2 | 3 | Message 4 | ------ 5 | 6 | .. attributetable:: Message 7 | 8 | .. autoclass:: Message 9 | -------------------------------------------------------------------------------- /docs/source/rest.rst: -------------------------------------------------------------------------------- 1 | .. py:currentmodule:: quant.api.core 2 | 3 | REST implementation 4 | ----- 5 | 6 | .. attributetable:: RESTAware 7 | 8 | .. autoclass:: RESTAware 9 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: "ubuntu-22.04" 5 | tools: 6 | python: "3.10" 7 | 8 | python: 9 | install: 10 | - requirements: docs/requirements.txt 11 | 12 | sphinx: 13 | configuration: docs/source/conf.py -------------------------------------------------------------------------------- /docs/source/components.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: quant 2 | 3 | Components 4 | ====== 5 | 6 | .. autoclass:: ButtonStyle 7 | 8 | .. autoclass:: Button 9 | 10 | .. autoclass:: TextInputStyle 11 | 12 | .. autoclass:: TextInput 13 | 14 | .. autoclass:: Modal -------------------------------------------------------------------------------- /quant/utils/cache/cacheable.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | 4 | class CacheableType(enum.IntFlag): 5 | USER = 1 << 1 6 | ROLE = 1 << 2 7 | GUILD = 1 << 3 8 | MESSAGE = 1 << 4 9 | EMOJI = 1 << 5 10 | CHANNEL = 1 << 6 11 | 12 | ALL = USER | ROLE | GUILD | MESSAGE | EMOJI | CHANNEL 13 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "quant" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["MagM1go "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.10" 10 | 11 | 12 | [build-system] 13 | requires = ["poetry-core"] 14 | build-backend = "poetry.core.masonry.api" 15 | -------------------------------------------------------------------------------- /docs/source/activity.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: quant 2 | 3 | Activity 4 | ======== 5 | 6 | .. autoclass:: ActivityStatus 7 | 8 | .. autoclass:: ActivityFlags 9 | 10 | .. autoclass:: ActivityType 11 | 12 | .. autoclass:: Activity 13 | 14 | .. autoclass:: ActivityAssets 15 | 16 | .. autoclass:: ActivityData 17 | 18 | .. autoclass:: ActivityBuilder 19 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | from quant import __version__ 4 | 5 | with open("README.md", "r") as file: 6 | readme_text = file.read() 7 | 8 | setup( 9 | name="quant", 10 | version=__version__, 11 | description="Quantum Symphony in Discord Bot Creation.", 12 | long_description=readme_text, 13 | author="MagMigo", 14 | requires=["aiohttp", "attrs"] 15 | ) 16 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to Quant! 2 | ======= 3 | 4 | Quant - Discord API Framework 5 | ------- 6 | 7 | Quant is a Python framework designed to simplify interaction with the Discord API and facilitate the development of Discord bots. 8 | 9 | Quant created because why not? 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | :caption: Documentation 14 | 15 | start 16 | rest 17 | client 18 | message 19 | activity 20 | components 21 | locales 22 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd -------------------------------------------------------------------------------- /docs/source/start.rst: -------------------------------------------------------------------------------- 1 | Getting started 2 | ---------------------- 3 | 4 | Here's basic bot code 5 | ========== 6 | 7 | .. highlight:: python 8 | :linenothreshold: 5 9 | 10 | .. code-block:: python 11 | 12 | from quant import Client, InteractionContext, SlashCommand 13 | 14 | client = Client(token="Bot ") 15 | 16 | 17 | async def slash_command_callback(context: InteractionContext) -> None: 18 | await context.interaction.respond(content="Pong!") 19 | 20 | if __name__ == "__main__": 21 | ping_command = SlashCommand( 22 | name="ping", 23 | description="Based ping command" 24 | ) 25 | ping_command.set_callback(slash_command_callback) 26 | client.add_slash_command(ping_command) 27 | 28 | client.run() 29 | # or 30 | client.run_autoshard() 31 | # or 32 | client.run_with_shards(5) 33 | 34 | 35 | Quant designed to simplify your experience with Discord API. 36 | 37 | If you have some issues or problems you can get help in support server -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 MagM1go 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /quant/entities/color.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations as _ 2 | 3 | from enum import Enum 4 | 5 | from quant.utils.parser import clamp 6 | 7 | 8 | class RGB: 9 | def __new__(cls, r: int, g: int, b: int) -> int: 10 | r, g, b = ( 11 | clamp(r), clamp(g), clamp(b) 12 | ) 13 | hex_string = f"0x{r:02x}{g:02x}{b:02x}" 14 | 15 | return int(hex_string, 16) 16 | 17 | 18 | class Hex: 19 | def __new__(cls, value: str | int) -> int: 20 | if isinstance(value, int): 21 | return value 22 | 23 | if value.startswith("#"): 24 | value = value[1:] 25 | 26 | return int.from_bytes(bytes.fromhex(value), "big") 27 | 28 | 29 | class Color: 30 | RED = 0xcc0000 31 | ORANGE = 0xff9900 32 | YELLOW = 0xffff00 33 | GREEN = 0x66ff00 34 | CYAN = 0x99ffff 35 | BLUE = 0x3399ff 36 | PURPLE = 0x990099 37 | 38 | def __new__(cls, *args): 39 | if len(args) == 1: 40 | return Hex(args[0]) 41 | 42 | if len(args) == 3: 43 | return RGB(*args) 44 | 45 | raise ValueError("Can't convert to HEX or RGB.") 46 | -------------------------------------------------------------------------------- /examples/loader.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations as _ 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | if TYPE_CHECKING: 6 | from bot import Bot 7 | 8 | from quant import SlashCommand, SlashSubCommand, ReadyEvent, VoiceServerUpdateEvent, VoiceStateUpdateEvent 9 | 10 | from music import MusicModule 11 | 12 | 13 | def register_components(bot: Bot) -> None: 14 | music = MusicModule(bot=bot) 15 | 16 | commands = [ 17 | SlashCommand(name="music", description="I like music", options=[ 18 | SlashSubCommand(name="join", description="Join a voice") 19 | .set_callback(music.join_callback), 20 | SlashSubCommand(name="play", description="Play music!") 21 | .set_callback(music.play_callback) 22 | ]) 23 | ] 24 | events = { 25 | ReadyEvent: music.on_ready_callback, 26 | VoiceStateUpdateEvent: music.on_voice_state_update, 27 | VoiceServerUpdateEvent: music.on_voice_server_update 28 | } 29 | 30 | bot.add_slash_command(commands) 31 | 32 | for event_type, callback in events.items(): 33 | bot.add_listener(event_type, callback) 34 | -------------------------------------------------------------------------------- /examples/bot.py: -------------------------------------------------------------------------------- 1 | from os import getenv 2 | from typing import TypeVar, List 3 | 4 | from dotenv import load_dotenv 5 | from lavasnek_rs import Lavalink 6 | 7 | from loader import register_components 8 | from quant import Client, Intents, SlashCommand 9 | 10 | load_dotenv() 11 | 12 | LavalinkT = TypeVar("LavalinkT", bound=Lavalink | None) 13 | 14 | 15 | class Bot(Client): 16 | def __init__(self) -> None: 17 | super().__init__(token=f"Bot {getenv('TOKEN')}", intents=Intents.ALL_UNPRIVILEGED, sync_commands=False) 18 | self._lavalink: LavalinkT = None 19 | self._commands: List[SlashCommand] | None = None 20 | 21 | @property 22 | def lavalink(self) -> LavalinkT: 23 | return self._lavalink 24 | 25 | @lavalink.setter 26 | def lavalink(self, value: Lavalink) -> None: 27 | self._lavalink = value 28 | 29 | @property 30 | def commands(self) -> List[SlashCommand]: 31 | if self._commands is None: 32 | self._commands = [] 33 | return self._commands 34 | 35 | return self._commands 36 | 37 | 38 | if __name__ == "__main__": 39 | client = Bot() 40 | register_components(bot=client) 41 | 42 | client.run() 43 | -------------------------------------------------------------------------------- /quant/impl/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from .core import * 25 | from .events import * 26 | from .files import * 27 | -------------------------------------------------------------------------------- /quant/impl/core/exceptions/event_exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | 25 | 26 | class InvalidEvent(Exception): 27 | ... 28 | -------------------------------------------------------------------------------- /quant/entities/api/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from .backend import CallbackBackend 25 | 26 | __all__ = ("CallbackBackend",) 27 | -------------------------------------------------------------------------------- /quant/api/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from .core import RESTAware, HttpManager 25 | 26 | 27 | __all__ = ("RESTAware", "HttpManager") 28 | -------------------------------------------------------------------------------- /quant/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from .entities import * 25 | from .impl import * 26 | from .utils import has_permissions, CacheableType 27 | 28 | 29 | __version__ = "1.5" 30 | -------------------------------------------------------------------------------- /quant/api/core/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from .http_manager_abc import HttpManager 25 | from .rest_aware import RESTAware 26 | 27 | __all__ = ("RESTAware", "HttpManager") 28 | -------------------------------------------------------------------------------- /quant/impl/core/exceptions/library_exception.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | 25 | 26 | class DiscordException(Exception): 27 | ... 28 | 29 | 30 | class ExperimentalFutureWarning(Warning): 31 | ... 32 | -------------------------------------------------------------------------------- /quant/entities/factory/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from .entity_factory import EntityFactory 25 | from .event_factory import EventFactory 26 | 27 | 28 | __all__ = ("EntityFactory", "EventFactory") 29 | -------------------------------------------------------------------------------- /quant/entities/modal/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from .modal import * 25 | from .text_input import * 26 | 27 | 28 | __all__ = ( 29 | "Modal", 30 | "ModalInteractionCallbackData", 31 | "TextInput", 32 | "TextInputStyle" 33 | ) 34 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | 3 | # -- Project information 4 | import sys 5 | import os 6 | 7 | 8 | sys.path.append(os.path.abspath("../..")) 9 | sys.path.append(os.path.abspath("extensions")) 10 | 11 | project = 'Quant' 12 | copyright = '2024, MagM1go and contributors' 13 | author = 'MagM1go' 14 | 15 | release = '0.1' 16 | version = '1.5' 17 | 18 | # -- General configuration 19 | 20 | extensions = [ 21 | 'sphinx.ext.duration', 22 | 'sphinx.ext.doctest', 23 | 'sphinx.ext.autodoc', 24 | 'sphinx.ext.autosummary', 25 | 'sphinx.ext.intersphinx', 26 | 'attributetable' 27 | ] 28 | 29 | autodoc_default_options = {"members": True, "show-inheritance": True} 30 | 31 | intersphinx_mapping = { 32 | 'python': ('https://docs.python.org/3/', None), 33 | 'sphinx': ('https://www.sphinx-doc.org/en/master/', None), 34 | } 35 | intersphinx_disabled_domains = ['std'] 36 | 37 | templates_path = ['_templates'] 38 | 39 | # -- Options for HTML output 40 | 41 | html_theme = 'furo' 42 | pygments_style = "monokai" 43 | default_dark_mode = True 44 | 45 | # -- Options for EPUB output 46 | epub_show_urls = 'footnote' 47 | 48 | rst_prolog = """ 49 | .. |coro| replace:: This is a |coroutine_link|_ function 50 | .. |coroutine_link| replace:: *coroutine* 51 | .. _coroutine_link: https://docs.python.org/3/library/asyncio-task.html#coroutine 52 | """ -------------------------------------------------------------------------------- /quant/entities/model.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from typing import Any, TYPE_CHECKING 25 | 26 | if TYPE_CHECKING: 27 | from quant.impl.core.client import Client 28 | 29 | 30 | class BaseModel: 31 | client: Any | None = None 32 | 33 | @classmethod 34 | def set_client(cls, client): 35 | cls.client: Client = client 36 | -------------------------------------------------------------------------------- /quant/entities/interactions/component_types.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | import enum 25 | 26 | 27 | class ComponentType(enum.Enum): 28 | NONE = 0 29 | ACTION_ROW = 1 30 | BUTTON = 2 31 | STRING_SELECT = 3 32 | TEXT_INPUT = 4 33 | USER_SELECT = 5 34 | ROLE_SELECT = 6 35 | MENTIONABLE_SELECT = 7 36 | CHANNEL_SELECT = 8 37 | -------------------------------------------------------------------------------- /quant/entities/voice_server_update.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | import attrs 25 | 26 | from .snowflake import Snowflake 27 | 28 | 29 | @attrs.define(kw_only=True, hash=True) 30 | class VoiceServer: 31 | token: str = attrs.field(default=None) 32 | guild_id: Snowflake = attrs.field(default=0) 33 | endpoint: str | None = attrs.field(default=None) 34 | -------------------------------------------------------------------------------- /quant/impl/events/guild/member_event.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations as _ 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | import attrs 6 | 7 | if TYPE_CHECKING: 8 | from typing_extensions import Self 9 | 10 | from quant.entities.user import User 11 | from quant.entities.member import GuildMember 12 | from quant.entities.snowflake import Snowflake 13 | from quant.impl.events.types import EventTypes 14 | from quant.impl.events.event import DiscordEvent 15 | 16 | 17 | @attrs.define(kw_only=True) 18 | class MemberJoinEvent(DiscordEvent): 19 | event_api_name: EventTypes = attrs.field(default=EventTypes.GUILD_MEMBER_ADD) 20 | guild_id: Snowflake = attrs.field(default=Snowflake()) 21 | member: GuildMember = attrs.field(default=None) 22 | 23 | def emit(self, *args, **kwargs) -> Self: 24 | guild_id = kwargs.get("guild_id") 25 | self.guild_id = guild_id 26 | self.member = self.entity_factory.deserialize_member(kwargs, guild_id=guild_id) 27 | return self 28 | 29 | 30 | @attrs.define(kw_only=True) 31 | class MemberLeaveEvent(DiscordEvent): 32 | event_api_name: EventTypes = attrs.field(default=EventTypes.GUILD_MEMBER_REMOVE) 33 | guild_id: Snowflake = attrs.field(default=Snowflake()) 34 | user: User = attrs.field(default=None) 35 | 36 | def emit(self, *args, **kwargs) -> Self: 37 | self.guild_id = kwargs.get("guild_id") 38 | self.user = self.entity_factory.deserialize_user(kwargs) 39 | return self 40 | -------------------------------------------------------------------------------- /quant/impl/core/exceptions/http_exception.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | 25 | 26 | class HTTPException(Exception): 27 | ... 28 | 29 | 30 | class Forbidden(HTTPException): 31 | ... 32 | 33 | 34 | class InternalServerError(HTTPException): 35 | ... 36 | 37 | 38 | class RateLimitExceeded(HTTPException): 39 | ... 40 | 41 | 42 | class UnexpectedError(HTTPException): 43 | ... 44 | -------------------------------------------------------------------------------- /quant/impl/events/bot/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from .ready_event import ReadyEvent, ShardReadyEvent 25 | from .interaction_create_event import InteractionCreateEvent 26 | from .exception_event import QuantExceptionEvent 27 | from .raw_event import RawDispatchEvent 28 | 29 | __all__ = ( 30 | "ReadyEvent", 31 | "ShardReadyEvent", 32 | "InteractionCreateEvent", 33 | "QuantExceptionEvent", 34 | "RawDispatchEvent" 35 | ) 36 | -------------------------------------------------------------------------------- /quant/entities/message_flags.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | import enum 25 | 26 | 27 | class MessageFlags(enum.Enum): 28 | NONE = 0 29 | CROSSPOSTED = 1 << 0 30 | IS_CROSSPOST = 1 << 1 31 | SUPPRESS_EMBEDS = 1 << 2 32 | SOURCE_MESSAGE_DELETED = 1 << 3 33 | URGENT = 1 << 4 34 | HAS_THREAD = 1 << 5 35 | EPHEMERAL = 1 << 6 36 | LOADING = 1 << 7 37 | FAILED_TO_MENTION_SOME_ROLES_IN_THREAD = 1 << 8 38 | SUPPRESS_NOTIFICATIONS = 1 << 12 39 | IS_VOICE_MESSAGE = 1 << 13 40 | -------------------------------------------------------------------------------- /quant/entities/gateway.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | import attrs 25 | 26 | 27 | @attrs.define(kw_only=True) 28 | class SessionStartLimitObject: 29 | total: int = attrs.field() 30 | remaining: int = attrs.field() 31 | reset_after: int = attrs.field() 32 | max_concurrency: int = attrs.field() 33 | 34 | 35 | @attrs.define(kw_only=True, hash=True) 36 | class GatewayInfo: 37 | url: str = attrs.field() 38 | shards: int = attrs.field() 39 | session_start_limit: SessionStartLimitObject = attrs.field() 40 | -------------------------------------------------------------------------------- /quant/entities/interactions/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from .application_command import * 25 | from .interaction import * 26 | from .choice_response import * 27 | from .slash_option import * 28 | 29 | __all__ = ( 30 | "ApplicationCommandOptionType", 31 | "InteractionDataOption", 32 | "Interaction", 33 | "InteractionCallbackData", 34 | "InteractionCallbackType", 35 | "InteractionType", 36 | "InteractionResponse", 37 | "InteractionData", 38 | "ApplicationCommandOption", 39 | "SlashOptionType", 40 | "ApplicationCommandContexts" 41 | ) 42 | -------------------------------------------------------------------------------- /quant/impl/core/exceptions/command_exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from typing import List 25 | 26 | from quant.entities.permissions import Permissions 27 | 28 | 29 | class CommandNotFoundException(Exception): 30 | ... 31 | 32 | 33 | class CommandArgumentsNotFound(Exception): 34 | ... 35 | 36 | 37 | class NotEnoughPermissions(Exception): 38 | def __init__(self, message: str, missing: Permissions, *args) -> None: 39 | super().__init__(*args) 40 | self.message = message 41 | self.permissions = [permission for permission in Permissions if permission & missing] 42 | -------------------------------------------------------------------------------- /quant/entities/interactions/choice_response.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations as _ 25 | 26 | from typing import List, Dict, TYPE_CHECKING 27 | 28 | if TYPE_CHECKING: 29 | from typing_extensions import Self 30 | 31 | import attrs 32 | 33 | from .slash_option import SlashOptionType 34 | 35 | 36 | @attrs.define(kw_only=True, hash=True) 37 | class InteractionDataOption: 38 | name: str = attrs.field() 39 | value: str = attrs.field() 40 | type: SlashOptionType = attrs.field() 41 | options: List[Self] | Dict = attrs.field() 42 | focused: bool = attrs.field() 43 | -------------------------------------------------------------------------------- /quant/entities/interactions/application_command.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from enum import Enum 25 | 26 | 27 | class ApplicationCommandOptionType(Enum): 28 | SUB_COMMAND = 1 29 | SUB_COMMAND_GROUP = 2 30 | STRING = 3 31 | INTEGER = 4 32 | BOOLEAN = 5 33 | USER = 6 34 | CHANNEL = 7 35 | ROLE = 8 36 | MENTIONABLE = 9 37 | NUMBER = 10 38 | ATTACHMENT = 11 39 | 40 | 41 | class ApplicationCommandTypes(Enum): 42 | NONE = 0 43 | CHAT_INPUT = 1 44 | USER = 2 45 | MESSAGE = 3 46 | 47 | 48 | class ApplicationCommandContexts(Enum): 49 | GUILD = 0 50 | BOT_DM = 1 51 | PRIVATE_CHANNEL = 2 52 | -------------------------------------------------------------------------------- /quant/impl/core/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from .context import InteractionContext, ModalContext, ButtonContext 25 | from .commands import SlashCommand, SlashSubCommand, SlashSubGroup 26 | from .client import Client 27 | from .rest import RESTImpl 28 | from .gateway import Gateway 29 | from .http_bot import HTTPBot 30 | from .exceptions import NotEnoughPermissions 31 | 32 | __all__ = ( 33 | "InteractionContext", 34 | "ModalContext", 35 | "ButtonContext", 36 | "SlashCommand", 37 | "SlashSubCommand", 38 | "SlashSubGroup", 39 | "Client", 40 | "RESTImpl", 41 | "Gateway", 42 | "HTTPBot", 43 | "NotEnoughPermissions" 44 | ) 45 | -------------------------------------------------------------------------------- /quant/entities/api/backend.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations as _ 25 | 26 | from typing import TypeVar, Callable, Coroutine, Any, Generic, TYPE_CHECKING 27 | 28 | if TYPE_CHECKING: 29 | from typing_extensions import Self 30 | 31 | ContextT = TypeVar("ContextT") 32 | CoroutineT = TypeVar("CoroutineT", bound=Callable[..., Coroutine[Any, Any, Any]]) 33 | 34 | 35 | class CallbackBackend(Generic[ContextT]): 36 | async def callback(self, context: ContextT): 37 | pass 38 | 39 | callback_func = callback 40 | 41 | def set_callback(self, coro: CoroutineT) -> Self: 42 | self.callback_func = coro 43 | return self 44 | -------------------------------------------------------------------------------- /quant/entities/locales.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class DiscordLocale(str, Enum): 5 | """Provides discord locales""" 6 | 7 | ID = "id" 8 | """Indonesian""" 9 | 10 | DA = "da" 11 | """Danish""" 12 | 13 | DE = "de" 14 | """German""" 15 | 16 | EN_GB = "en-GB" 17 | """English, UK""" 18 | 19 | EN_US = "en-US" 20 | """English, US""" 21 | 22 | ES_ES = "es-ES" 23 | """Spanish""" 24 | 25 | ES_419 = "es-419" 26 | """Spanish, LATAM""" 27 | 28 | FR = "fr" 29 | """French""" 30 | 31 | HR = "hr" 32 | """Croatian""" 33 | 34 | IT = "it" 35 | """Italian""" 36 | 37 | LT = "lt" 38 | """Lithuanian""" 39 | 40 | HU = "hu" 41 | """Hungarian""" 42 | 43 | NL = "nl" 44 | """Dutch""" 45 | 46 | NO = "no" 47 | """Norwegian""" 48 | 49 | PL = "pl" 50 | """Polish""" 51 | 52 | PT_BR = "pt-BR" 53 | """Portuguese, Brazilian""" 54 | 55 | RO = "ro" 56 | """Romanian, Romania""" 57 | 58 | FI = "fi" 59 | """Finnish""" 60 | 61 | SV_SE = "sv-SE" 62 | """Swedish""" 63 | 64 | VI = "vi" 65 | """Vietnamese""" 66 | 67 | TR = "tr" 68 | """Turkish""" 69 | 70 | CS = "cs" 71 | """Czech""" 72 | 73 | EL = "el" 74 | """ Greek""" 75 | 76 | BG = "bg" 77 | """Bulgarian""" 78 | 79 | RU = "ru" 80 | """Russian""" 81 | 82 | UK = "uk" 83 | """Ukrainian""" 84 | 85 | HI = "hi" 86 | """Hindi""" 87 | 88 | TH = "th" 89 | """Thai""" 90 | 91 | ZH_CN = "zh-CN" 92 | """Chinese, China""" 93 | 94 | JA = "ja" 95 | """Japanese""" 96 | 97 | ZH_TW = "zh-TW" 98 | """Chinese, Taiwan""" 99 | 100 | KO = "ko" 101 | """Korean""" 102 | -------------------------------------------------------------------------------- /quant/impl/events/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from .event import Event, InternalEvent 25 | from .types import EventTypes 26 | from .guild import * 27 | from .bot import * 28 | 29 | __all__ = ( 30 | "InternalEvent", 31 | "VoiceServerUpdateEvent", 32 | "ChannelCreateEvent", 33 | "VoiceStateUpdateEvent", 34 | "GuildCreateEvent", 35 | "MessageCreateEvent", 36 | "MessageDeleteEvent", 37 | "ReactionRemoveEvent", 38 | "ReactionAddEvent", 39 | "ShardReadyEvent", 40 | "QuantExceptionEvent", 41 | "MessageEditEvent", 42 | "InteractionCreateEvent", 43 | "ReadyEvent", 44 | "RawDispatchEvent", 45 | "MemberJoinEvent", 46 | "MemberLeaveEvent" 47 | ) 48 | -------------------------------------------------------------------------------- /quant/entities/action_row.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations as _ 25 | 26 | from typing import List, TypeVar 27 | 28 | from quant.entities.button import Button 29 | 30 | ComponentT = TypeVar("ComponentT", bound=Button) 31 | 32 | 33 | class ActionRow: 34 | """Container for components 35 | 36 | Parameters 37 | ========== 38 | components: :class:`List[ComponentT] | ComponentT` 39 | Action row components 40 | """ 41 | INTERACTION_TYPE = 1 42 | 43 | def __init__(self, components: List[ComponentT] | ComponentT) -> None: 44 | if isinstance(components, list): 45 | self.components = components 46 | else: 47 | self.components = [components] 48 | -------------------------------------------------------------------------------- /quant/impl/events/guild/voice_server_update_event.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations as _ 25 | 26 | from typing import TYPE_CHECKING 27 | 28 | import attrs 29 | 30 | if TYPE_CHECKING: 31 | from typing_extensions import Self 32 | 33 | from quant.impl.events.types import EventTypes 34 | from quant.entities.voice_server_update import VoiceServer 35 | from quant.impl.events.event import DiscordEvent 36 | 37 | 38 | @attrs.define(kw_only=True) 39 | class VoiceServerUpdateEvent(DiscordEvent): 40 | event_api_name: EventTypes = attrs.field(default=EventTypes.VOICE_SERVER_UPDATE) 41 | state: VoiceServer = attrs.field(default=None) 42 | 43 | def emit(self, *args, **kwargs) -> Self: 44 | return self 45 | -------------------------------------------------------------------------------- /quant/impl/events/bot/interaction_create_event.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations as _ 25 | 26 | from typing import TYPE_CHECKING 27 | 28 | import attrs 29 | 30 | if TYPE_CHECKING: 31 | from typing_extensions import Self 32 | 33 | from quant.impl.events.types import EventTypes 34 | from quant.entities.interactions.interaction import Interaction 35 | from quant.impl.events.event import DiscordEvent 36 | 37 | 38 | @attrs.define(kw_only=True) 39 | class InteractionCreateEvent(DiscordEvent): 40 | event_api_name: EventTypes = attrs.field(default=EventTypes.INTERACTION_CREATE) 41 | interaction: Interaction = attrs.field(default=None) 42 | 43 | def emit(self, *args, **kwargs) -> Self: 44 | self.interaction = Interaction(**kwargs) 45 | return self 46 | -------------------------------------------------------------------------------- /quant/impl/events/bot/raw_event.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations as _ 25 | 26 | from typing import Dict 27 | 28 | import attrs 29 | 30 | from quant.impl.events.event import InternalEvent 31 | 32 | 33 | @attrs.define(kw_only=True) 34 | class _GatewayData: 35 | event_name: str = attrs.field(alias="t") 36 | sequence: int = attrs.field(alias="s") 37 | opcode: int = attrs.field(alias="op") 38 | data: Dict = attrs.field(alias="d") 39 | 40 | 41 | @attrs.define(kw_only=True) 42 | class RawDispatchEvent(InternalEvent): 43 | data: _GatewayData = attrs.field() 44 | 45 | def emit(self, *args, **kwargs): 46 | return self 47 | 48 | @staticmethod 49 | def build(event: RawDispatchEvent, *args, **kwargs) -> RawDispatchEvent: 50 | return event 51 | -------------------------------------------------------------------------------- /quant/entities/voice_state_update.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from datetime import datetime 25 | 26 | import attrs 27 | 28 | from .snowflake import Snowflake 29 | from .member import GuildMember 30 | 31 | 32 | @attrs.define(kw_only=True, hash=True) 33 | class VoiceState: 34 | guild_id: Snowflake = attrs.field() 35 | channel_id: Snowflake = attrs.field() 36 | user_id: Snowflake = attrs.field() 37 | member: GuildMember = attrs.field() 38 | session_id: str = attrs.field() 39 | deaf: bool = attrs.field() 40 | mute: bool = attrs.field() 41 | self_deaf: bool = attrs.field() 42 | self_mute: bool = attrs.field() 43 | self_stream: bool = attrs.field() 44 | self_video: bool = attrs.field() 45 | suppress: bool = attrs.field() 46 | request_to_speak_timestamp: datetime = attrs.field() 47 | -------------------------------------------------------------------------------- /quant/impl/events/guild/guild_create_event.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations as _ 25 | 26 | from typing import TYPE_CHECKING 27 | 28 | import attrs 29 | 30 | if TYPE_CHECKING: 31 | from typing_extensions import Self 32 | from quant.entities.guild import Guild 33 | 34 | from quant.impl.events.types import EventTypes 35 | from quant.impl.events.event import DiscordEvent 36 | 37 | 38 | @attrs.define(kw_only=True) 39 | class GuildCreateEvent(DiscordEvent): 40 | event_api_name: EventTypes = attrs.field(default=EventTypes.GUILD_CREATE) 41 | guild: Guild = attrs.field(default=None) 42 | 43 | def emit(self, *args, **kwargs) -> Self: 44 | self.guild = self.entity_factory.deserialize_guild(kwargs) 45 | self.cache_manager.add_guild(self.guild) 46 | 47 | return self 48 | -------------------------------------------------------------------------------- /quant/impl/events/guild/channel_create_event.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations as _ 25 | 26 | from typing import TYPE_CHECKING 27 | 28 | import attrs 29 | 30 | if TYPE_CHECKING: 31 | from typing_extensions import Self 32 | 33 | from quant.impl.events.types import EventTypes 34 | from quant.impl.events.event import DiscordEvent 35 | from quant.entities.channel import Channel 36 | 37 | 38 | @attrs.define(kw_only=True) 39 | class ChannelCreateEvent(DiscordEvent): 40 | event_api_name: EventTypes = attrs.field(default=EventTypes.CHANNEL_CREATE) 41 | channel: Channel = attrs.field(default=None) 42 | 43 | def emit(self, *args, **kwargs) -> Self: 44 | self.channel = self.entity_factory.deserialize_channel(kwargs) 45 | self.cache_manager.add_channel(self.channel) 46 | 47 | return self 48 | -------------------------------------------------------------------------------- /quant/impl/events/guild/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from .guild_create_event import GuildCreateEvent 25 | from .message_event import MessageCreateEvent, MessageEditEvent, MessageDeleteEvent 26 | from .reaction_event import ReactionAddEvent, ReactionRemoveEvent 27 | from .voice_server_update_event import VoiceServerUpdateEvent 28 | from .voice_state_update_event import VoiceStateUpdateEvent 29 | from .channel_create_event import ChannelCreateEvent 30 | from .member_event import MemberLeaveEvent, MemberJoinEvent 31 | 32 | 33 | __all__ = ( 34 | "GuildCreateEvent", 35 | "MessageEditEvent", 36 | "MessageCreateEvent", 37 | "MessageDeleteEvent", 38 | "ReactionAddEvent", 39 | "ReactionRemoveEvent", 40 | "VoiceStateUpdateEvent", 41 | "VoiceServerUpdateEvent", 42 | "ChannelCreateEvent", 43 | "MemberLeaveEvent", 44 | "MemberJoinEvent" 45 | ) 46 | -------------------------------------------------------------------------------- /quant/entities/ratelimits/ratelimit.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from datetime import datetime 25 | 26 | import attrs 27 | 28 | 29 | @attrs.define(kw_only=True, hash=True) 30 | class RateLimitBucketReset: 31 | reset_time: datetime = attrs.field() 32 | reset_time_milliseconds: float = attrs.field() 33 | 34 | 35 | @attrs.define(kw_only=True, hash=True) 36 | class RateLimit: 37 | max_retries: int = attrs.field() 38 | remaining_retries: int = attrs.field() 39 | retry_after: float = attrs.field() 40 | message: str = attrs.field() 41 | is_global: bool = attrs.field() 42 | bucket: str = attrs.field() 43 | code: int | None = attrs.field() 44 | scope: str | None = attrs.field() 45 | 46 | 47 | @attrs.define(kw_only=True, hash=True) 48 | class Bucket: 49 | rate_limit: RateLimit | None = attrs.field() 50 | bucket_reset: RateLimitBucketReset = attrs.field() 51 | -------------------------------------------------------------------------------- /quant/impl/events/bot/ready_event.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations 25 | 26 | from typing import TYPE_CHECKING 27 | 28 | import attrs 29 | 30 | if TYPE_CHECKING: 31 | from quant.impl.core.shard import Shard 32 | 33 | from ..event import DiscordEvent, InternalEvent 34 | from ..types import EventTypes 35 | 36 | 37 | @attrs.define(kw_only=True) 38 | class ReadyEvent(DiscordEvent): 39 | event_api_name: EventTypes = attrs.field(default=EventTypes.READY_EVENT) 40 | resume_url: str = attrs.field() 41 | 42 | def emit(self, *args, **kwargs): 43 | return self 44 | 45 | 46 | @attrs.define(kw_only=True) 47 | class ShardReadyEvent(InternalEvent): 48 | shard: Shard = attrs.field(default=None) 49 | 50 | @staticmethod 51 | def build(event: ShardReadyEvent, *args, **kwargs) -> ShardReadyEvent: 52 | event.shard = args[0] 53 | return event 54 | 55 | def emit(self, *args, **kwargs): 56 | return self 57 | -------------------------------------------------------------------------------- /quant/impl/events/bot/exception_event.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations 25 | 26 | import attrs 27 | 28 | from quant.impl.core.context import InteractionContext, ModalContext 29 | from quant.impl.core.exceptions.library_exception import DiscordException 30 | from quant.impl.events.event import InternalEvent 31 | 32 | ContextType = InteractionContext | ModalContext 33 | 34 | 35 | @attrs.define(kw_only=True) 36 | class QuantExceptionEvent(InternalEvent): 37 | exception: Exception = attrs.field(default=None) 38 | context: ContextType = attrs.field(default=None) 39 | 40 | def emit(self, *args, **kwargs): 41 | self.exception = DiscordException(kwargs) 42 | return self 43 | 44 | @staticmethod 45 | def build(event: QuantExceptionEvent, *args, **kwargs) -> QuantExceptionEvent: 46 | context, exception = args[0], args[1] 47 | event.exception = exception 48 | event.context = context 49 | return event 50 | -------------------------------------------------------------------------------- /quant/impl/events/guild/reaction_event.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations as _ 25 | 26 | from typing import TYPE_CHECKING 27 | 28 | import attrs 29 | 30 | if TYPE_CHECKING: 31 | from typing_extensions import Self 32 | 33 | from quant.impl.events.types import EventTypes 34 | from quant.entities.emoji import Reaction 35 | from quant.impl.events.event import DiscordEvent 36 | 37 | 38 | @attrs.define(kw_only=True) 39 | class ReactionAddEvent(DiscordEvent): 40 | event_api_name: EventTypes = attrs.field(default=EventTypes.MESSAGE_REACTION_ADD) 41 | reaction: Reaction = attrs.field(default=None) 42 | 43 | def emit(self, *args, **kwargs) -> Self: 44 | return self 45 | 46 | 47 | @attrs.define(kw_only=True) 48 | class ReactionRemoveEvent(DiscordEvent): 49 | event_api_name: EventTypes = attrs.field(default=EventTypes.MESSAGE_REACTION_REMOVE) 50 | reaction: Reaction = attrs.field(default=None) 51 | 52 | def emit(self, *args, **kwargs) -> Self: 53 | return self 54 | -------------------------------------------------------------------------------- /quant/impl/events/event.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations as _ 25 | 26 | from typing import TypeVar, TYPE_CHECKING 27 | from abc import ABC, abstractmethod 28 | 29 | import attrs 30 | 31 | if TYPE_CHECKING: 32 | from typing_extensions import Self 33 | from quant.entities.factory.entity_factory import EntityFactory 34 | from quant.impl.events.types import EventTypes 35 | from quant.utils.cache.cache_manager import CacheManager 36 | 37 | 38 | class Event: 39 | async def call(self) -> None: 40 | ... 41 | 42 | def emit(self, *args, **kwargs) -> Self: 43 | ... 44 | 45 | 46 | class InternalEvent(ABC, Event): 47 | T = TypeVar("T") 48 | 49 | @staticmethod 50 | @abstractmethod 51 | def build(event: T, *args, **kwargs) -> T: 52 | ... 53 | 54 | 55 | @attrs.define 56 | class DiscordEvent(Event): 57 | event_api_name: EventTypes = attrs.field() 58 | cache_manager: CacheManager = attrs.field() 59 | entity_factory: EntityFactory = attrs.field() 60 | -------------------------------------------------------------------------------- /quant/api/core/http_manager_abc.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from abc import ABC, abstractmethod 25 | from typing import Final, Dict, Any 26 | 27 | from aiohttp import ClientResponse 28 | 29 | 30 | class AcceptContentType: 31 | APPLICATION_JSON: Final[str] = "application/json" 32 | APPLICATION_X_WWW_FORM_URLENCODED: Final[str] = "application/x-www-form-urlencoded" 33 | MULTIPART_FORM_DATA: Final[str] = "multipart/form-data" 34 | AUTHORIZATION: Final[str] = "Authorization" 35 | TEXT_HTML: Final[str] = "text/html" 36 | 37 | class MimeTypes: 38 | IMAGE_PNG: Final[str] = "image/png" 39 | IMAGE_JPG: Final[str] = "image/jpg" 40 | IMAGE_GIF: Final[str] = "image/gif" 41 | IMAGE_WEBP: Final[str] = "image/webp" 42 | 43 | 44 | class HttpManager(ABC): 45 | @abstractmethod 46 | async def request( 47 | self, 48 | method: str, url: str, 49 | data: Dict[str, Any] = None, 50 | headers: Dict[str, str] = None 51 | ) -> ClientResponse | None: 52 | raise NotImplementedError 53 | -------------------------------------------------------------------------------- /quant/entities/webhook.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | import enum 25 | from typing import Any 26 | 27 | import attrs 28 | 29 | from .user import User 30 | from .snowflake import Snowflake 31 | 32 | 33 | @attrs.define(hash=True) 34 | class Webhook: 35 | webhook_url: str = attrs.field(alias="url") 36 | webhook_token: str = attrs.field(alias="token") 37 | webhook_avatar: str = attrs.field(alias="avatar") 38 | webhook_name: str = attrs.field(alias="name") 39 | webhook_id: Snowflake = attrs.field(alias="id") 40 | webhook_type: int = attrs.field(alias="type") 41 | guild_id: Snowflake | None = attrs.field(default=None) 42 | channel_id: Snowflake | None = attrs.field(default=None) 43 | user: User | None = attrs.field(default=None) 44 | application_id: Snowflake | None = attrs.field(default=None) 45 | source_guild: Any | None = attrs.field(default=None) 46 | source_channel: Any | None = attrs.field(default=None) 47 | 48 | 49 | class WebhookTypes(enum.Enum): 50 | INCOMING = 1 51 | CHANNEL_FOLLOWER = 2 52 | APPLICATION = 3 53 | -------------------------------------------------------------------------------- /quant/utils/asyncio_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # cython: language_level=3 3 | # Copyright (c) 2020 Nekokatt 4 | # Copyright (c) 2021-present davfsa 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in all 14 | # copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | import warnings 24 | from asyncio import AbstractEventLoop, get_event_loop_policy, new_event_loop, set_event_loop 25 | from contextlib import suppress 26 | 27 | 28 | def get_loop() -> AbstractEventLoop: 29 | with suppress(RuntimeError): 30 | with warnings.catch_warnings(): 31 | warnings.simplefilter("ignore", DeprecationWarning) 32 | loop = get_event_loop_policy().get_event_loop() 33 | 34 | if not loop.is_closed(): 35 | return loop 36 | 37 | loop = new_event_loop() 38 | set_event_loop(loop) 39 | return loop 40 | 41 | 42 | def create_loop() -> AbstractEventLoop: 43 | loop = new_event_loop() 44 | set_event_loop(loop) 45 | 46 | return loop 47 | 48 | 49 | def kill_loop() -> None: 50 | loop = get_loop() 51 | 52 | if loop.is_closed(): 53 | return 54 | 55 | loop.close() 56 | set_event_loop(None) 57 | loop.stop() 58 | -------------------------------------------------------------------------------- /quant/impl/events/guild/voice_state_update_event.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations as _ 25 | 26 | from typing import TYPE_CHECKING 27 | 28 | import attrs 29 | 30 | if TYPE_CHECKING: 31 | from typing_extensions import Self 32 | 33 | from quant.impl.events.types import EventTypes 34 | from quant.entities.voice_state_update import VoiceState 35 | from quant.impl.events.event import DiscordEvent 36 | 37 | 38 | @attrs.define(kw_only=True) 39 | class VoiceStateUpdateEvent(DiscordEvent): 40 | event_api_name: EventTypes = attrs.field(default=EventTypes.VOICE_STATE_UPDATE) 41 | old_state: VoiceState = attrs.field(default=None) 42 | new_state: VoiceState = attrs.field(default=None) 43 | 44 | def emit(self, *args, **kwargs) -> Self: 45 | self.new_state = self.entity_factory.deserialize_voice_state(kwargs) 46 | state = self.cache_manager.get_voice_state(guild_id=self.new_state.guild_id, user_id=self.new_state.user_id) 47 | 48 | if state is None: 49 | return 50 | 51 | self.old_state = state 52 | return self 53 | -------------------------------------------------------------------------------- /quant/entities/roles.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from typing import List, Any 25 | 26 | import attrs 27 | 28 | from .snowflake import Snowflake 29 | from .permissions import Permissions 30 | 31 | 32 | @attrs.define(kw_only=True, hash=True) 33 | class GuildRole: 34 | id: Snowflake = attrs.field() 35 | name: str = attrs.field() 36 | color: int = attrs.field() 37 | hoist: bool = attrs.field() 38 | icon: str | None = attrs.field() 39 | unicode_emoji: str | None = attrs.field() 40 | position: int = attrs.field() 41 | permissions: Permissions = attrs.field() 42 | managed: bool = attrs.field() 43 | mentionable: bool = attrs.field() 44 | tags: List[Any] = attrs.field() 45 | flags: int = attrs.field() 46 | 47 | @property 48 | def mention(self) -> str: 49 | return f"<@&{self.id}>" 50 | 51 | 52 | def empty_role() -> GuildRole: 53 | return GuildRole( 54 | id=Snowflake(), 55 | name="", 56 | color=0, 57 | hoist=False, 58 | icon=None, 59 | unicode_emoji=None, 60 | position=0, 61 | permissions=Permissions.NONE, 62 | managed=False, 63 | mentionable=False, 64 | tags=[], 65 | flags=0 66 | ) 67 | -------------------------------------------------------------------------------- /quant/entities/poll.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import List, Dict 3 | from datetime import datetime 4 | 5 | import attrs 6 | 7 | from quant.entities.emoji import Emoji, PartialEmoji 8 | from quant.utils.parser import string_to_emoji 9 | 10 | 11 | class PollMediaType(int, Enum): 12 | DEFAULT = 1 13 | 14 | 15 | @attrs.define(kw_only=True, hash=True) 16 | class PollMedia: 17 | text: str | None = attrs.field(default=None) 18 | emoji: str | Emoji | PartialEmoji | None = attrs.field(default=None) 19 | 20 | 21 | @attrs.define(kw_only=True, hash=True) 22 | class PollAnswer: 23 | answer_id: int = attrs.field() 24 | poll_media: PollMedia = attrs.field() 25 | 26 | 27 | @attrs.define(kw_only=True, hash=True) 28 | class Poll: 29 | question: str = attrs.field() 30 | answers: List[PollAnswer] = attrs.field() 31 | expiry: float = attrs.field() 32 | allow_multiselect: bool = attrs.field() 33 | layout_type: PollMediaType = attrs.field() 34 | 35 | 36 | @attrs.define(kw_only=True, hash=True) 37 | class _PollAnswerCount: 38 | id: int = attrs.field() 39 | count: int = attrs.field() 40 | me_voted: bool = attrs.field() 41 | 42 | 43 | @attrs.define(kw_only=True, hash=True) 44 | class PollResults: 45 | is_finalized: bool = attrs.field() 46 | answer_counts: _PollAnswerCount = attrs.field() 47 | 48 | 49 | @attrs.define(kw_only=True, hash=True) 50 | class Answer: 51 | text: str = attrs.field(default=None) 52 | emoji: str | Emoji | PartialEmoji = attrs.field(default=None) 53 | 54 | 55 | def poll( 56 | question: str, 57 | answers: List[Answer], 58 | allow_multiselect: bool = False, 59 | expiry: datetime | None = None 60 | ) -> Poll: # I don't know why custom emojis don't work 61 | if expiry is not None: 62 | expiry = expiry.timestamp() 63 | 64 | done_answers = [] 65 | 66 | for answer_id, answer in enumerate(answers, start=1): 67 | if isinstance(answer.emoji, str): 68 | answer.emoji = string_to_emoji(emoji=answer.emoji) 69 | 70 | done_answers.append(PollAnswer( 71 | answer_id=answer_id, 72 | poll_media=PollMedia( 73 | text=answer.text, 74 | emoji=answer.emoji 75 | ) 76 | )) 77 | 78 | return Poll( 79 | question=question, 80 | answers=done_answers, 81 | expiry=expiry, 82 | allow_multiselect=allow_multiselect, 83 | layout_type=PollMediaType.DEFAULT 84 | ) 85 | -------------------------------------------------------------------------------- /quant/impl/events/types.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from typing import Final 25 | 26 | 27 | class _DynamicEnum: 28 | _values = {} 29 | 30 | def __init__(self, value): 31 | self.value = value 32 | _DynamicEnum._values[value] = self 33 | 34 | def __str__(self): 35 | return self.value 36 | 37 | @classmethod 38 | def get(cls, value): 39 | return cls._values.get(value, None) 40 | 41 | 42 | class EventTypes(_DynamicEnum): 43 | MESSAGE_CREATE: Final[str] = "MESSAGE_CREATE" 44 | MESSAGE_DELETE: Final[str] = "MESSAGE_DELETE" 45 | MESSAGE_UPDATE: Final[str] = "MESSAGE_UPDATE" 46 | MESSAGE_REACTION_ADD: Final[str] = "MESSAGE_REACTION_ADD" 47 | MESSAGE_REACTION_REMOVE: Final[str] = "MESSAGE_REACTION_REMOVE" 48 | GUILD_CREATE: Final[str] = "GUILD_CREATE" 49 | GUILD_DELETE: Final[str] = "GUILD_DELETE" 50 | READY_EVENT: Final[str] = "READY" 51 | INTERACTION_CREATE: Final[str] = "INTERACTION_CREATE" 52 | VOICE_STATE_UPDATE: Final[str] = "VOICE_STATE_UPDATE" 53 | VOICE_SERVER_UPDATE: Final[str] = "VOICE_SERVER_UPDATE" 54 | CHANNEL_CREATE: Final[str] = "CHANNEL_CREATE" 55 | PRESENCE_UPDATE: Final[str] = "PRESENCE_UPDATE" 56 | TYPING_START: Final[str] = "TYPING_START" 57 | GUILD_MEMBER_ADD: Final[str] = "GUILD_MEMBER_ADD" 58 | GUILD_MEMBER_REMOVE: Final[str] = "GUILD_MEMBER_REMOVE" 59 | GUILD_MEMBERS_CHUNK: Final[str] = "GUILD_MEMBERS_CHUNK" 60 | -------------------------------------------------------------------------------- /quant/utils/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | import logging 25 | 26 | from quant.impl.core.exceptions.command_exceptions import NotEnoughPermissions 27 | from quant.entities.permissions import Permissions 28 | from quant.utils.cache.cacheable import CacheableType 29 | 30 | __all__ = ( 31 | "has_permissions", 32 | "logger", 33 | "CacheableType" 34 | ) 35 | 36 | 37 | def has_permissions(member_permissions: Permissions, needed_permissions: Permissions) -> bool: 38 | if member_permissions == Permissions.NONE: 39 | return False 40 | 41 | if member_permissions == Permissions.ADMINISTRATOR or member_permissions & Permissions.ADMINISTRATOR: 42 | return True 43 | 44 | missing_permissions = needed_permissions & ~member_permissions 45 | if missing_permissions == Permissions.NONE: 46 | return True 47 | 48 | if missing_permissions is not None: 49 | raise NotEnoughPermissions( 50 | f"Missing permissions: {missing_permissions} ({missing_permissions.value})", 51 | missing=missing_permissions 52 | ) 53 | 54 | return member_permissions.value & needed_permissions.value 55 | 56 | 57 | logging.basicConfig(format="%(levelname)s | %(asctime)s %(module)s - %(message)s", level=logging.INFO) 58 | logging.captureWarnings(True) 59 | 60 | 61 | def _get_logger() -> logging.Logger: 62 | return logging.getLogger(__name__) 63 | 64 | 65 | logger = _get_logger() 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![https://discord.gg/MnECK7DJ6n](assets/banner.png) 2 | 3 | > [!WARNING] 4 | > **LIBRARY UNDER DEVELOPMENT** 5 | > So, if you catch some bugs you can create issue or join to support server (click banner) 6 | 7 | ## Example Usage 8 | 9 | Here's an example code showcasing how to use quant: 10 | 11 | ```python 12 | from quant import Client, Intents, ReadyEvent 13 | 14 | client = Client( 15 | token="Bot YOUR_DISCORD_BOT_TOKEN", 16 | intents=Intents.ALL 17 | ) 18 | 19 | 20 | async def on_ready(_: ReadyEvent): 21 | print('Bot is ready!') 22 | 23 | 24 | client.add_listener(on_ready) 25 | client.run() 26 | ``` 27 | Replace `YOUR_DISCORD_BOT_TOKEN` with your actual Discord bot token. 28 | 29 | # Slash Commands (Echo bot example) 30 | 31 | ```python 32 | from quant import ( 33 | Client, 34 | SlashCommand, 35 | InteractionContext, 36 | Intents 37 | ) 38 | 39 | client = Client(token="Bot YOUR_DISCORD_BOT_TOKEN", intents=Intents.ALL_MESSAGES) 40 | 41 | 42 | async def slash_command_callback(context: InteractionContext) -> None: 43 | text = await context.get_option("text") 44 | await context.interaction.respond(content=text) 45 | 46 | 47 | command = SlashCommand( 48 | name="say", 49 | description="Say something" 50 | ) 51 | command.option(name="text", description="Your cool text", required=True) 52 | command.set_callback(slash_command_callback) 53 | 54 | client.add_slash_command(command) 55 | client.run() 56 | ``` 57 | 58 | # [Official support server](https://discord.gg/MnECK7DJ6n) 59 | 60 | # Getting Started 61 | ## Installation: 62 | You can install quant using pip: 63 | 64 | `pip install quant` (not available at this moment, use `pip install git+https://github.com/QuantDiscord/quant.git`) 65 | 66 | # Usage: 67 | 68 | Create a new instance of Client by providing your Discord bot token and desired options. 69 | Define callback functions for your commands and events. 70 | Add commands and event listeners using the appropriate methods provided by the Client class. 71 | Start the bot using client.run(). 72 | 73 | # Documentation: 74 | Read documentation here: https://quant.rtfd.io/ 75 | 76 | 77 | # Contibuting: 78 | Coming soon. 79 | 80 | 81 | # License: 82 | Quant is licensed under the MIT License. See the [LICENSE](https://github.com/MagM1go/quant/blob/main/LICENSE) file for more details. 83 | 84 | 85 | # Thanks 86 | Thanks to JetBrains for open source license 87 | 88 | You can apply it [here](https://www.jetbrains.com/community/opensource/). 89 | 90 | [Hikari](https://github.com/hikari-py/hikari/) for inspiring 91 | 92 | [Melisa](https://github.com/MelisaDev/melisa) for help with docs 93 | -------------------------------------------------------------------------------- /examples/music.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations as _ 2 | 3 | from os import getenv 4 | from typing import TYPE_CHECKING 5 | 6 | if TYPE_CHECKING: 7 | from bot import Bot 8 | 9 | from quant import VoiceStateUpdateEvent, VoiceServerUpdateEvent, InteractionContext, ReadyEvent 10 | from lavasnek_rs import LavalinkBuilder 11 | 12 | 13 | class MusicModule: 14 | def __init__(self, bot: Bot) -> None: 15 | self.bot = bot 16 | 17 | async def on_voice_state_update(self, event: VoiceStateUpdateEvent) -> None: 18 | self.bot.lavalink.raw_handle_event_voice_state_update( 19 | event.new_state.guild_id, 20 | event.new_state.user_id, 21 | event.new_state.session_id, 22 | event.new_state.channel_id 23 | ) 24 | 25 | async def on_voice_server_update(self, event: VoiceServerUpdateEvent) -> None: 26 | await self.bot.lavalink.raw_handle_event_voice_server_update( 27 | event.state.guild_id, 28 | event.state.endpoint, 29 | event.state.token 30 | ) 31 | 32 | async def on_ready_callback(self, _: ReadyEvent) -> None: 33 | builder = ( 34 | LavalinkBuilder( 35 | self.bot.client_id, getenv("TOKEN") 36 | ) 37 | .set_host("127.0.0.1") 38 | .set_port(8081) 39 | .set_password("your_lava_token") 40 | .set_start_gateway(False) 41 | ) 42 | 43 | event_handler = type("EventHandler", (), {}) 44 | lava_client = await builder.build(event_handler) 45 | 46 | self.bot.lavalink = lava_client 47 | 48 | async def join_callback(self, ctx: InteractionContext) -> None: 49 | guild_id = ctx.interaction.guild_id 50 | voice_state = self.bot.cache.get_voice_state(guild_id, ctx.author.id) 51 | channel_id = voice_state.channel_id 52 | 53 | await self.bot.gateway.voice_connect(guild_id=guild_id, channel_id=channel_id) 54 | await ctx.interaction.respond(content=f"Connected to <#{channel_id}>") 55 | connection = await self.bot.lavalink.wait_for_full_connection_info_insert(guild_id) 56 | 57 | await self.bot.lavalink.create_session(connection) 58 | 59 | async def play_callback(self, ctx: InteractionContext) -> None: 60 | query = await ctx.get_option("query") 61 | auto_search = await self.bot.lavalink.auto_search_tracks(" ".join(query)) 62 | track = auto_search.tracks[0] 63 | 64 | await self.bot.lavalink.play(ctx.interaction.guild_id, track).requester(ctx.author.id).queue() 65 | await ctx.interaction.respond(content=f"Now playing **{track.info.title}**") 66 | -------------------------------------------------------------------------------- /quant/entities/snowflake.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from functools import reduce 25 | from typing import TypeVar 26 | 27 | 28 | class SnowflakeException(Exception): 29 | ... 30 | 31 | 32 | class Snowflake(int): 33 | DISCORD_EPOCH = 14_200_704_000_00 34 | 35 | def __new__(cls, object_id: int | str | None = None): 36 | if object_id is None: 37 | return Snowflake(0) 38 | 39 | return super().__new__(cls, int(object_id)) 40 | 41 | def __init__(self, object_id: int | str | None = None) -> None: 42 | if object_id is None: 43 | object_id = 0 44 | 45 | self.object_id = int(object_id) 46 | self.timestamp = ((self.object_id >> 22) + self.DISCORD_EPOCH) / 1000 47 | self.increment = 0 48 | 49 | def generate_snowflake( 50 | self, 51 | timestamp: int, 52 | worker_id: int = 0, 53 | process_id: int = 0 54 | ) -> int: 55 | if timestamp <= self.DISCORD_EPOCH: 56 | raise SnowflakeException(f"Timestamp can't be earlier than discord epoch ({self.DISCORD_EPOCH})") 57 | 58 | self.increment += 1 59 | 60 | snowflake_segments = [ 61 | (timestamp - self.DISCORD_EPOCH) << 22, 62 | worker_id << 17, 63 | process_id << 12, 64 | self.increment 65 | ] 66 | 67 | if self.increment >= 4096: 68 | self.increment = 0 69 | 70 | return reduce(lambda x, y: x + y, snowflake_segments) 71 | 72 | 73 | SnowflakeOrInt = TypeVar("SnowflakeOrInt", bound=Snowflake | int) 74 | -------------------------------------------------------------------------------- /quant/entities/allowed_mentions.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | import enum 25 | from typing import List 26 | 27 | import attrs 28 | 29 | 30 | @attrs.define(hash=True) 31 | class AllowedMentions: 32 | parse: List[str] = attrs.field() 33 | roles: List[int] = attrs.field(default=list()) 34 | users: List[int] = attrs.field(default=list()) 35 | replied_user: bool = attrs.field(default=False) 36 | 37 | 38 | class AllowedMentionsTypes(enum.Enum): 39 | ROLE_MENTIONS = "roles" 40 | USER_MENTIONS = "users" 41 | EVERYONE_MENTIONS = "everyone" 42 | 43 | ROLE_AND_USER = [ROLE_MENTIONS, USER_MENTIONS] 44 | ROLE_AND_EVERYONE = [ROLE_MENTIONS, EVERYONE_MENTIONS] 45 | 46 | USER_AND_EVERYONE = [USER_MENTIONS, EVERYONE_MENTIONS] 47 | 48 | ALL = [ROLE_MENTIONS, USER_MENTIONS, EVERYONE_MENTIONS] 49 | 50 | 51 | def suppress_mentions( 52 | roles: bool = False, 53 | users: bool = False, 54 | replied_user: bool = False, 55 | everyone: bool = False, 56 | suppress_all: bool = False 57 | ) -> AllowedMentions: 58 | if all((roles, users, replied_user, everyone)) is False: 59 | return AllowedMentions(parse=[]) 60 | 61 | if suppress_all: 62 | return AllowedMentions(parse=[]) 63 | 64 | parse = [] 65 | if roles: 66 | parse.append(AllowedMentionsTypes.ROLE_MENTIONS.value) 67 | if users: 68 | parse.append(AllowedMentionsTypes.USER_MENTIONS.value) 69 | if everyone: 70 | parse.append(AllowedMentionsTypes.EVERYONE_MENTIONS.value) 71 | 72 | return AllowedMentions(parse=parse, replied_user=replied_user) 73 | -------------------------------------------------------------------------------- /quant/entities/modal/text_input.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from enum import Enum 25 | 26 | import attrs 27 | 28 | 29 | class TextInputStyle(Enum): 30 | """Discord text input style 31 | 32 | Attributes 33 | ========== 34 | SHORT: 35 | Small text input 36 | PARAGRAPH: 37 | Big text input 38 | """ 39 | NONE = 0 40 | SHORT = 1 41 | PARAGRAPH = 2 42 | 43 | 44 | @attrs.define(kw_only=True, hash=True) 45 | class TextInput: 46 | """Represents a discord text input 47 | 48 | Parameters 49 | ========= 50 | custom_id: :class:`str` 51 | Text input custom ID 52 | style: :class:`TextInputStyle` 53 | Text input style 54 | label: :class:`str` 55 | Text input label 56 | min_length: :class:`int` 57 | Minimal content length 58 | max_length: :class:`int` 59 | Maximal content length 60 | required: :class:`bool` 61 | Required field or no 62 | value: :class:`str` 63 | Text input value 64 | placeholder: :class:`str` 65 | Placeholder 66 | """ 67 | INTERACTION_TYPE = 4 68 | 69 | type: int = attrs.field(default=INTERACTION_TYPE) 70 | custom_id: str = attrs.field() 71 | style: TextInputStyle = attrs.field(default=TextInputStyle.SHORT) 72 | label: str | None = attrs.field() 73 | min_length: int = attrs.field(default=0) 74 | max_length: int = attrs.field(default=4000) 75 | required: bool = attrs.field(default=True) 76 | value: str | None = attrs.field(default=None) 77 | placeholder: str | None = attrs.field(default=None) 78 | -------------------------------------------------------------------------------- /quant/entities/intents.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | import enum 25 | 26 | 27 | class Intents(enum.IntFlag): 28 | GUILDS = 1 << 0 29 | GUILD_MEMBERS = 1 << 1 30 | GUILD_MODERATION = 1 << 2 31 | GUILD_EMOJIS = 1 << 3 32 | GUILD_INTEGRATIONS = 1 << 4 33 | GUILD_WEBHOOKS = 1 << 5 34 | GUILD_INVITES = 1 << 6 35 | GUILD_VOICE_STATES = 1 << 7 36 | GUILD_PRESENCES = 1 << 8 37 | GUILD_MESSAGES = 1 << 9 38 | GUILD_MESSAGE_REACTIONS = 1 << 10 39 | GUILD_MESSAGE_TYPING = 1 << 11 40 | DM_MESSAGES = 1 << 12 41 | DM_MESSAGE_REACTIONS = 1 << 13 42 | DM_MESSAGE_TYPING = 1 << 14 43 | MESSAGE_CONTENT = 1 << 15 44 | 45 | GUILD_SCHEDULED_EVENTS = 1 << 16 46 | 47 | ALL_GUILDS_UNPRIVILEGED = ( 48 | GUILDS 49 | | GUILD_EMOJIS 50 | | GUILD_INTEGRATIONS 51 | | GUILD_WEBHOOKS 52 | | GUILD_INVITES 53 | | GUILD_VOICE_STATES 54 | | GUILD_MESSAGES 55 | | GUILD_MESSAGE_REACTIONS 56 | | GUILD_MESSAGE_TYPING 57 | | GUILD_MODERATION 58 | | GUILD_SCHEDULED_EVENTS 59 | ) 60 | 61 | ALL_GUILDS_PRIVILEGED = GUILD_MEMBERS | GUILD_PRESENCES 62 | ALL_GUILDS = ALL_GUILDS_UNPRIVILEGED | ALL_GUILDS_PRIVILEGED 63 | ALL_DMS = DM_MESSAGES | DM_MESSAGE_TYPING | DM_MESSAGE_REACTIONS 64 | ALL_MESSAGES = DM_MESSAGES | GUILD_MESSAGES 65 | ALL_MESSAGE_REACTIONS = DM_MESSAGE_REACTIONS | GUILD_MESSAGE_REACTIONS 66 | ALL_MESSAGE_TYPING = DM_MESSAGE_TYPING | GUILD_MESSAGE_TYPING 67 | ALL_UNPRIVILEGED = ALL_GUILDS_UNPRIVILEGED | ALL_DMS 68 | ALL_PRIVILEGED = ALL_GUILDS_PRIVILEGED | MESSAGE_CONTENT 69 | 70 | ALL = ALL_UNPRIVILEGED | ALL_PRIVILEGED 71 | -------------------------------------------------------------------------------- /quant/entities/invite.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | import enum 25 | import datetime 26 | from typing import Any 27 | 28 | import attrs 29 | 30 | from .guild import Guild 31 | from .channel import Channel 32 | from .user import User 33 | from .snowflake import Snowflake 34 | 35 | 36 | class InviteTargetType(enum.Enum): 37 | NONE = 0 38 | STREAM = 1 39 | EMBEDDED_APPLICATION = 2 40 | 41 | 42 | @attrs.define(kw_only=True) 43 | class _InviteMetadata: 44 | uses: int = attrs.field(default=0) 45 | max_uses: int = attrs.field(default=0) 46 | max_age: int = attrs.field(default=0) 47 | temporary: bool = attrs.field(default=False) 48 | created_at: datetime.datetime = attrs.field(default=None) 49 | 50 | 51 | @attrs.define(hash=True, kw_only=True) 52 | class Invite: 53 | code: str 54 | guild_id: Snowflake | None = attrs.field(default=None) 55 | guild: Guild | None = attrs.field(default=None) 56 | channel: Channel | None = attrs.field(default=None) 57 | inviter: User | None = attrs.field(default=None) 58 | target_type: InviteTargetType | None = attrs.field(default=0) 59 | target_user: User | None = attrs.field(default=None) 60 | target_application: Any | None = attrs.field(default=None) 61 | approximate_presence_count: int | None = attrs.field(default=0) 62 | approximate_member_count: int | None = attrs.field(default=0) 63 | expires_at: datetime.datetime | None = attrs.field(default=None) 64 | stage_instance: Any | None = attrs.field(default=None) 65 | guild_scheduled_event: Any | None = attrs.field(default=None) 66 | metadata: _InviteMetadata | None = attrs.field(default=None) 67 | _type: int = attrs.field(alias="type", default=-1) 68 | _flags: int = attrs.field(alias="flags", default=-1) 69 | -------------------------------------------------------------------------------- /quant/impl/core/shard.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations 25 | 26 | import asyncio 27 | from typing import TYPE_CHECKING 28 | 29 | if TYPE_CHECKING: 30 | from quant.impl.core.client import Client 31 | 32 | from quant.entities.intents import Intents 33 | from quant.entities.activity import Activity 34 | from .gateway import Gateway 35 | 36 | 37 | class Shard: 38 | def __init__( 39 | self, 40 | num_shards: int, 41 | shard_id: int, 42 | intents: Intents = Intents.ALL_UNPRIVILEGED, 43 | mobile: bool = False, 44 | activity: Activity | None = None 45 | ) -> None: 46 | self.shard_id = shard_id 47 | self.num_shards = num_shards 48 | self.gateway: Gateway | None = None 49 | self.intents = intents 50 | self.mobile = mobile 51 | self.activity = activity 52 | 53 | self._latency: float = float("nan") 54 | 55 | async def start(self, client: Client, loop: asyncio.AbstractEventLoop = None) -> None: 56 | self.gateway = Gateway( 57 | intents=self.intents, 58 | shard_id=self.shard_id, 59 | num_shards=self.num_shards, 60 | client=client, 61 | mobile=self.mobile 62 | ) 63 | 64 | if loop is not None: 65 | client.loop = loop 66 | 67 | asyncio.create_task(self.gateway.connect()) 68 | 69 | if self.shard_id == 0: 70 | client.gateway = self.gateway 71 | 72 | async def close(self) -> None: 73 | asyncio.create_task(self.gateway.close()) 74 | 75 | @property 76 | def latency(self) -> float: 77 | return self._latency 78 | 79 | @latency.setter 80 | def latency(self, value: float) -> None: 81 | self._latency = value 82 | -------------------------------------------------------------------------------- /quant/entities/interactions/slash_option.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations as _ 25 | 26 | import enum 27 | import warnings 28 | from typing import List, Any, TYPE_CHECKING, Dict 29 | 30 | if TYPE_CHECKING: 31 | from typing_extensions import Self 32 | 33 | import attrs 34 | 35 | from quant.entities.locales import DiscordLocale 36 | from quant.entities.api.backend import CallbackBackend, CoroutineT 37 | 38 | 39 | class SlashOptionType(enum.Enum): 40 | NONE = 0 41 | SUB_COMMAND = 1 42 | SUB_COMMAND_GROUP = 2 43 | STRING = 3 44 | INTEGER = 4 45 | BOOLEAN = 5 46 | USER = 6 47 | CHANNEL = 7 48 | ROLE = 8 49 | MENTIONABLE = 9 50 | NUMBER = 10 51 | ATTACHMENT = 11 52 | 53 | 54 | @attrs.define(hash=True) 55 | class ApplicationCommandOption(CallbackBackend): 56 | name: str = attrs.field() 57 | description: str = attrs.field(default="Empty description") 58 | name_localizations: Dict[DiscordLocale, str] = attrs.field(default=None) 59 | description_localizations: Dict[DiscordLocale, str] = attrs.field(default=None) 60 | min_value: int = attrs.field(default=None) 61 | max_value: int = attrs.field(default=None) 62 | min_length: int = attrs.field(default=None) 63 | max_length: int = attrs.field(default=None) 64 | autocomplete: bool = attrs.field(default=False) 65 | channel_types: List[Any] = attrs.field(default=None) 66 | options: List[ApplicationCommandOption] = attrs.field(default=None) 67 | choices: List[Any] = attrs.field(default=None) 68 | required: bool = attrs.field(default=False) 69 | type: SlashOptionType = attrs.field(default=None) 70 | 71 | def set_callback(self, coro: CoroutineT) -> Self: 72 | if self.type != SlashOptionType.SUB_COMMAND: 73 | warnings.warn("Impossible set callback to other option type instead of SUB_COMMAND") 74 | return 75 | 76 | self.callback_func = coro 77 | return self 78 | -------------------------------------------------------------------------------- /quant/entities/user.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations as _ 25 | 26 | import urllib.parse 27 | from typing import Any 28 | 29 | import attrs 30 | 31 | from quant.entities.model import BaseModel 32 | from quant.impl.core.route import CDN_AVATARS 33 | 34 | from .snowflake import Snowflake 35 | 36 | 37 | @attrs.define(hash=True) 38 | class User(BaseModel): 39 | username: str = attrs.field() 40 | id: Snowflake = attrs.field(default=0) 41 | discriminator: str | None = attrs.field(default=None) 42 | display_name: str | None = attrs.field(default=None) 43 | global_name: str | None = attrs.field(default=None) 44 | avatar: str | None = attrs.field(default=None) 45 | bot: bool = attrs.field(default=False) 46 | system: bool = attrs.field(default=False) 47 | mfa_enabled: bool = attrs.field(default=False) 48 | banner: str = attrs.field(default=None) 49 | accent_color: int | None = attrs.field(default=None) 50 | banner_color: int | None = attrs.field(default=None) 51 | avatar_decoration: str | None = attrs.field(default=None) 52 | locale: str | None = attrs.field(default=None) 53 | flags: int = attrs.field(default=0) 54 | premium_type: int = attrs.field(default=0) 55 | public_flags: int = attrs.field(default=0) 56 | avatar_decoration_data: Any = attrs.field(default=None) 57 | verified: bool = attrs.field(default=False) 58 | _email: str = attrs.field(default=None, alias="email", repr=False) 59 | member: Any = attrs.field(default=None) 60 | 61 | @property 62 | def mention(self) -> str: 63 | return f"<@{self.id}>" 64 | 65 | def get_avatar(self, extension: str = "png", size: int = 1024) -> str: 66 | as_params = {"size": size} 67 | base = CDN_AVATARS.format(self.id, self.avatar, extension) 68 | encoded_params = urllib.parse.urlencode(as_params) 69 | 70 | return f"{base}?{encoded_params}" 71 | 72 | async def fetch(self) -> User: 73 | return await self.client.rest.fetch_user(user_id=self.id) 74 | -------------------------------------------------------------------------------- /quant/entities/integration.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | import enum 25 | from datetime import datetime 26 | 27 | import attrs 28 | 29 | from .snowflake import Snowflake 30 | from .model import BaseModel 31 | from .user import User 32 | 33 | 34 | class IntegrationTypes(enum.Enum): 35 | GUILD_INSTALL = 0 36 | USER_INSTALL = 1 37 | 38 | 39 | class IntegrationExpireBehaviours(enum.Enum): 40 | NONE = -1 41 | REMOVE_ROLE = 0 42 | KICK = 1 43 | 44 | 45 | @attrs.define(hash=True) 46 | class IntegrationAccountObject(BaseModel): 47 | account_id: str = attrs.field(alias="id", default=None) 48 | name: str = attrs.field(default=None) 49 | 50 | 51 | @attrs.define(hash=True) 52 | class IntegrationApplicationObject(BaseModel): 53 | application_id: Snowflake = attrs.field(default=None) 54 | name: str = attrs.field(default=None) 55 | icon: str | None = attrs.field(default=None) 56 | description: str = attrs.field(default=None) 57 | bot: User | None = attrs.field(default=None) 58 | 59 | 60 | @attrs.define(kw_only=True, hash=True) 61 | class Integration: 62 | integration_id: Snowflake = attrs.field(alias="id", default=0) 63 | integration_name: str = attrs.field(default=None, alias="name") 64 | integration_type: str = attrs.field(default=None, alias="type") 65 | enabled: bool = attrs.field(default=False) 66 | syncing: bool = attrs.field(default=False) 67 | role_id: Snowflake = attrs.field(default=0) 68 | enable_emoticons: bool = attrs.field(default=False) 69 | expire_behaviour: IntegrationExpireBehaviours = attrs.field( 70 | default=IntegrationExpireBehaviours.NONE, converter=IntegrationExpireBehaviours 71 | ) 72 | expire_grace_period: int = attrs.field(default=0) 73 | user: User = attrs.field(default=None) 74 | account: IntegrationAccountObject = attrs.field(default=None) 75 | synced_at: datetime = attrs.field(default=None) 76 | subscriber_count: int = attrs.field(default=0) 77 | revoked: bool = attrs.field(default=False) 78 | application: IntegrationApplicationObject = attrs.field(default=None) 79 | -------------------------------------------------------------------------------- /quant/entities/permissions.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations as _ 25 | 26 | from enum import Flag 27 | 28 | 29 | class Permissions(Flag): 30 | NONE = 0 31 | CREATE_INSTANT_INVITE = 1 << 0 32 | KICK_MEMBERS = 1 << 1 33 | BAN_MEMBERS = 1 << 2 34 | ADMINISTRATOR = 1 << 3 35 | MANAGE_CHANNELS = 1 << 4 36 | MANAGE_GUILD = 1 << 5 37 | ADD_REACTIONS = 1 << 6 38 | VIEW_AUDIT_LOG = 1 << 7 39 | PRIORITY_SPEAKER = 1 << 8 40 | STREAM = 1 << 9 41 | VIEW_CHANNEL = 1 << 10 42 | SEND_MESSAGES = 1 << 11 43 | SEND_TTS_MESSAGES = 1 << 12 44 | MANAGE_MESSAGES = 1 << 13 45 | EMBED_LINKS = 1 << 14 46 | ATTACH_FILES = 1 << 15 47 | READ_MESSAGE_HISTORY = 1 << 16 48 | MENTION_EVERYONE = 1 << 17 49 | USE_EXTERNAL_EMOJIS = 1 << 18 50 | VIEW_GUILD_INSIGHTS = 1 << 19 51 | CONNECT = 1 << 20 52 | SPEAK = 1 << 21 53 | MUTE_MEMBERS = 1 << 22 54 | DEAFEN_MEMBERS = 1 << 23 55 | MOVE_MEMBERS = 1 << 24 56 | USE_VAD = 1 << 25 57 | CHANGE_NICKNAME = 1 << 26 58 | MANAGE_NICKNAMES = 1 << 27 59 | MANAGE_ROLES = 1 << 28 60 | MANAGE_WEBHOOKS = 1 << 29 61 | MANAGE_GUILD_EXPRESSIONS = 1 << 30 62 | USE_APPLICATION_COMMANDS = 1 << 31 63 | REQUEST_TO_SPEAK = 1 << 32 64 | MANAGE_EVENTS = 1 << 33 65 | MANAGE_THREADS = 1 << 34 66 | CREATE_PUBLIC_THREADS = 1 << 35 67 | CREATE_PRIVATE_THREADS = 1 << 36 68 | USE_EXTERNAL_STICKERS = 1 << 37 69 | SEND_MESSAGES_IN_THREADS = 1 << 38 70 | USE_EMBEDDED_ACTIVITIES = 1 << 39 71 | MODERATE_MEMBERS = 1 << 40 72 | VIEW_CREATOR_MONETIZATION_ANALYTICS = 1 << 41 73 | USE_SOUNDBOARD = 1 << 42 74 | CREATE_GUILD_EXPRESSIONS = 1 << 43 75 | CREATE_EVENTS = 1 << 44 76 | USE_EXTERNAL_SOUNDS = 1 << 45 77 | SEND_VOICE_MESSAGES = 1 << 46 78 | 79 | @classmethod 80 | def all_permissions(cls) -> Permissions: 81 | permissions = Permissions.NONE 82 | 83 | for permission in Permissions: 84 | permissions |= permission 85 | 86 | return permissions 87 | 88 | def __repr__(self) -> str: 89 | return f"Permissions <{self.value}>" 90 | -------------------------------------------------------------------------------- /quant/impl/events/guild/message_event.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations as _ 25 | 26 | from typing import TYPE_CHECKING 27 | 28 | import attrs 29 | 30 | if TYPE_CHECKING: 31 | from typing_extensions import Self 32 | 33 | from quant.impl.events.types import EventTypes 34 | from quant.entities.user import User 35 | from quant.impl.events.event import DiscordEvent 36 | from quant.entities.message import Message 37 | 38 | 39 | @attrs.define(kw_only=True) 40 | class MessageCreateEvent(DiscordEvent): 41 | event_api_name: EventTypes = attrs.field(default=EventTypes.MESSAGE_CREATE) 42 | message: Message = attrs.field(default=None) 43 | 44 | def emit(self, **kwargs) -> Self: 45 | if kwargs is None: 46 | return 47 | 48 | self.message = self.entity_factory.deserialize_message(kwargs) 49 | self.cache_manager.add_message(self.message) 50 | 51 | return self 52 | 53 | 54 | @attrs.define(kw_only=True) 55 | class MessageEditEvent(DiscordEvent): 56 | event_api_name: EventTypes = attrs.field(default=EventTypes.MESSAGE_UPDATE) 57 | old_message: Message = attrs.field(default=None) 58 | new_message: Message = attrs.field(default=None) 59 | 60 | def emit(self, *args, **kwargs) -> Self: 61 | self.new_message = self.entity_factory.deserialize_message(kwargs) 62 | message = self.cache_manager.get_message(int(kwargs.get("id"))) 63 | 64 | if message is None: 65 | return 66 | 67 | self.old_message = message 68 | return self 69 | 70 | 71 | @attrs.define(kw_only=True) 72 | class MessageDeleteEvent(DiscordEvent): 73 | event_api_name: EventTypes = attrs.field(default=EventTypes.MESSAGE_DELETE) 74 | author: User = attrs.field(default=None) 75 | message: Message = attrs.field(default=None) 76 | 77 | def emit(self, *args, **kwargs) -> Self: 78 | message = self.cache_manager.get_message(int(kwargs.get("id"))) 79 | if message is not None: 80 | self.message = message 81 | 82 | return self 83 | 84 | @classmethod 85 | def set_author(cls, user: User): 86 | cls.author = user 87 | -------------------------------------------------------------------------------- /quant/entities/emoji.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations 25 | 26 | from typing import List, Any, TYPE_CHECKING 27 | 28 | import attrs 29 | 30 | from .model import BaseModel 31 | 32 | 33 | if TYPE_CHECKING: 34 | from .user import User 35 | from .member import GuildMember 36 | from .snowflake import Snowflake 37 | 38 | 39 | @attrs.define(hash=True) 40 | class PartialReaction(BaseModel): 41 | emoji_name: str = attrs.field(alias="name") 42 | emoji_id: Snowflake = attrs.field(default=0, alias="id", hash=True) 43 | animated: bool = attrs.field(default=False) 44 | 45 | def __str__(self) -> str: 46 | if self.emoji_id > 0: 47 | return f"<:{self.emoji_name}:{self.emoji_id}>" 48 | else: 49 | return self.emoji_name 50 | 51 | 52 | @attrs.define(hash=True) 53 | class PartialEmoji: 54 | id: Snowflake | int = attrs.field() 55 | name: str = attrs.field() 56 | animated: bool = attrs.field(default=False) 57 | 58 | def __str__(self) -> str: 59 | if self.id > 0: 60 | animated = "a" if self.animated else "" 61 | return f"<{animated}:{self.name}:{self.id}>" 62 | else: 63 | return self.name 64 | 65 | 66 | @attrs.define(kw_only=True, hash=True) 67 | class Emoji(PartialEmoji): 68 | roles: List[Any] = attrs.field() 69 | user: User = attrs.field() 70 | require_colons: bool = attrs.field() 71 | managed: bool = attrs.field() 72 | available: bool = attrs.field() 73 | version: int = attrs.field() 74 | 75 | 76 | @attrs.define 77 | class Reaction(BaseModel): 78 | user_id: Snowflake = attrs.field() 79 | type: int = attrs.field() 80 | message_id: Snowflake = attrs.field() 81 | message_author_id: Snowflake = attrs.field() 82 | member: GuildMember = attrs.field() 83 | emoji: PartialReaction = attrs.field() 84 | channel_id: Snowflake = attrs.field() 85 | burst: bool = attrs.field() 86 | guild_id: Snowflake = attrs.field() 87 | burst_colors: List[Any] = attrs.field() 88 | 89 | def __str__(self) -> str: 90 | return str(self.emoji) 91 | 92 | def __hash__(self) -> int: 93 | return hash(self.emoji.emoji_id) 94 | -------------------------------------------------------------------------------- /quant/impl/core/http_bot.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | import http 25 | import warnings 26 | 27 | from aiohttp import ClientSession, web 28 | 29 | from quant.impl.core.route import GET, POST 30 | from quant.impl.core.rest import RESTImpl 31 | from quant.utils.asyncio_utils import kill_loop 32 | from quant.utils.cache.cache_manager import CacheManager 33 | from quant.utils.cache.cacheable import CacheableType 34 | from quant.entities.interactions.interaction import InteractionType, InteractionCallbackType 35 | from quant.entities.factory.event_factory import EventFactory 36 | from quant.entities.factory.event_controller import EventController 37 | 38 | 39 | class HTTPBot: 40 | """ 41 | An interaction only bot implementation 42 | """ 43 | def __init__(self, token: str, cacheable: CacheableType = CacheableType.ALL) -> None: 44 | warnings.warn("It's will be fully added in future. Now it's useless.") 45 | quit(1) 46 | 47 | self.cache = CacheManager() 48 | self.cache = CacheManager(cacheable=cacheable) 49 | self.rest = RESTImpl(token=token, cache=self.cache) 50 | self.event_factory = EventFactory(cache_manager=self.cache) 51 | self.event_controller = EventController(factory=self.event_factory) 52 | 53 | self.session = ClientSession() 54 | self._application = web.Application() 55 | 56 | async def _interaction_handler(self, request: web.Request) -> web.Response: 57 | if not request.method == POST: 58 | return web.Response(status=http.HTTPStatus.BAD_REQUEST) 59 | 60 | data: dict = await request.json() 61 | data_type = data.get("type") 62 | 63 | if data_type == InteractionType.PING.value: 64 | return web.json_response({"type": InteractionCallbackType.PONG}) 65 | 66 | # okay it's future . . . 67 | 68 | def _setup_routes(self) -> None: 69 | self._application.add_routes([ 70 | web.route(POST, "/interactions", self._interaction_handler) 71 | ]) 72 | 73 | def run_server(self, host: str = "localhost", port: int = 8080) -> None: 74 | self._setup_routes() 75 | 76 | web.run_app( 77 | app=self._application, 78 | host=host, 79 | port=port 80 | ) 81 | -------------------------------------------------------------------------------- /quant/impl/files.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations 25 | 26 | import os 27 | from io import BytesIO 28 | from abc import ABC, abstractmethod 29 | from pathlib import Path 30 | from urllib import parse 31 | 32 | 33 | def file_to_bytes(file: File | None) -> bytes: 34 | if file is None: 35 | return bytes(0) 36 | 37 | buffer = BytesIO(file.path.read_bytes()) 38 | return buffer.getvalue() 39 | 40 | 41 | def get_image_ending(data: bytes) -> str | None: 42 | if data[6:10] in (b'JFIF', b'Exif'): 43 | return 'jpeg' 44 | elif data.startswith(b'\211PNG\r\n\032\n'): 45 | return 'png' 46 | elif data[:6] in (b'GIF87a', b'GIF89a'): 47 | return 'gif' 48 | elif data.startswith(b'RIFF') and data[8:12] == b'WEBP': 49 | return 'webp' 50 | else: 51 | raise ValueError("Provided file type isn't image") 52 | 53 | 54 | def get_image_mimetype(file: File): 55 | return "image/" + get_image_ending(file_to_bytes(file)) 56 | 57 | 58 | def is_image_file(file: File) -> bool: 59 | return get_image_ending(file_to_bytes(file)) is not None 60 | 61 | 62 | class Attachable(ABC): 63 | @property 64 | @abstractmethod 65 | def url(self) -> str: 66 | """Attachment url""" 67 | 68 | @property 69 | @abstractmethod 70 | def filename(self) -> str: 71 | """Attachment filename""" 72 | 73 | 74 | class File(Attachable): 75 | def __init__(self, path: Path | str, spoiler: bool = False) -> None: 76 | self.path = Path(path) 77 | self.spoiler = spoiler 78 | 79 | self._filename = self.path.name 80 | 81 | @property 82 | def filename(self) -> str: 83 | if self.spoiler: 84 | return "SPOILER_" + self._filename 85 | return self._filename 86 | 87 | @property 88 | def url(self) -> Path: 89 | return self.path.absolute() 90 | 91 | 92 | class AttachableURL(Attachable): 93 | def __init__(self, url: str) -> None: 94 | self._url = url 95 | 96 | @property 97 | def url(self) -> str: 98 | return self._url 99 | 100 | @property 101 | def filename(self) -> str: 102 | return os.path.basename(parse.urlparse(self.url).path) 103 | -------------------------------------------------------------------------------- /quant/entities/button.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | import enum 25 | 26 | import attrs 27 | 28 | from .emoji import Emoji 29 | from quant.entities.api.backend import CallbackBackend 30 | from quant.impl.core.context import ButtonContext 31 | 32 | 33 | class ButtonStyle(enum.Enum): 34 | """Discord button styles""" 35 | PRIMARY = 1 36 | SECONDARY = 2 37 | SUCCESS = 3 38 | DANGER = 4 39 | LINK = 5 40 | 41 | 42 | @attrs.define(hash=True) 43 | class Button(CallbackBackend[ButtonContext]): 44 | """Represents a discord button 45 | 46 | Parameters 47 | ========== 48 | custom_id: :class:`str | None` 49 | Button custom ID 50 | style: :class:`ButtonStyle` 51 | Discord button style, default PRIMARY 52 | label: :class:`str` 53 | Button label 54 | emoji: :class:`Emoji | None` 55 | Button emoji 56 | url: :class:`str | None` 57 | Button redirect url 58 | disabled: :class:`bool` 59 | Set/check is button disabled 60 | """ 61 | BUTTON_COMPONENT_TYPE = 2 62 | INTERACTION_TYPE = 3 63 | 64 | custom_id: str = attrs.field(default=None) 65 | label: str | None = attrs.field(default=None) 66 | style: ButtonStyle = attrs.field(default=ButtonStyle.PRIMARY) 67 | emoji: Emoji | str | None = attrs.field(default=None) 68 | url: str | None = attrs.field(default=None) 69 | disabled: bool = attrs.field(default=False) 70 | 71 | 72 | def button( 73 | custom_id: str | None = None, 74 | label: str | None = None, 75 | style: ButtonStyle | int = ButtonStyle.PRIMARY, 76 | emoji: Emoji | str | None = None, 77 | url: str | None = None, 78 | disabled: bool = False, 79 | ) -> Button: 80 | if isinstance(style, int): 81 | style = ButtonStyle(style) 82 | 83 | btn = Button( 84 | custom_id=custom_id, 85 | label=label, 86 | style=style, 87 | emoji=emoji, 88 | url=url, 89 | disabled=disabled 90 | ) 91 | 92 | if custom_id is None or url is None: 93 | if url is None: 94 | raise TypeError(f"Specify url in button with label {label}") 95 | 96 | if url is not None: 97 | return btn 98 | else: 99 | TypeError(f"Specify custom_id in button with label {label}") 100 | 101 | return btn 102 | -------------------------------------------------------------------------------- /quant/entities/member.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations 25 | 26 | import datetime 27 | from typing import Any, List, TYPE_CHECKING 28 | 29 | import attrs 30 | 31 | if TYPE_CHECKING: 32 | from .user import User 33 | 34 | from .model import BaseModel 35 | from .snowflake import Snowflake 36 | from .roles import GuildRole 37 | from .permissions import Permissions 38 | 39 | 40 | @attrs.define(kw_only=True) 41 | class GuildMember(BaseModel): 42 | deaf: bool = attrs.field() 43 | mute: bool = attrs.field() 44 | flags: int = attrs.field() 45 | pending: bool = attrs.field(repr=False) 46 | permissions: Permissions | None = attrs.field(repr=False) 47 | nick: str | None = attrs.field() 48 | roles: List[GuildRole] | None = attrs.field(repr=False) 49 | joined_at: datetime.datetime = attrs.field() 50 | premium_since: int | None = attrs.field() 51 | communication_disabled_until: int | None = attrs.field() 52 | user: User = attrs.field() 53 | guild_id: Snowflake | int = attrs.field() 54 | unusual_dm_activity_until: Any = attrs.field() 55 | 56 | def __hash__(self) -> int: 57 | return hash(self.user) 58 | 59 | @property 60 | def mention(self) -> str: 61 | return self.user.mention 62 | 63 | @property 64 | def id(self) -> Snowflake: 65 | return self.user.id 66 | 67 | @property 68 | def avatar_hash(self) -> str: 69 | return self.user.avatar 70 | 71 | def get_avatar(self, extension: str = "png", size: int = 1024) -> str: 72 | return self.user.get_avatar(extension=extension, size=size) 73 | 74 | async def add_role(self, role: GuildRole | Snowflake | int) -> None: 75 | if isinstance(role, GuildRole): 76 | role = role.id 77 | 78 | await self.client.rest.add_guild_member_role( 79 | guild_id=self.guild_id, 80 | role_id=role, 81 | user_id=self.id 82 | ) 83 | 84 | def get_permissions(self) -> Permissions: 85 | permissions = Permissions.NONE 86 | 87 | guild = self.client.cache.get_guild(guild_id=self.guild_id) 88 | if self.id == guild.owner_id or self.permissions & Permissions.ADMINISTRATOR: 89 | return Permissions.ADMINISTRATOR 90 | 91 | roles = [guild.get_everyone_role()] + self.roles 92 | for role in roles: 93 | permissions |= role.permissions 94 | 95 | return permissions 96 | -------------------------------------------------------------------------------- /quant/entities/embeds.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from typing import List, Any 25 | from datetime import datetime 26 | 27 | import attrs 28 | from attrs import define, field 29 | 30 | 31 | @define(hash=True) 32 | class EmbedField: 33 | name: str 34 | value: Any 35 | inline: bool = False 36 | 37 | 38 | @define(hash=True) 39 | class EmbedFooter: 40 | text: Any 41 | icon_url: str | None = None 42 | proxy_icon_url: str | None = None 43 | 44 | 45 | @define(hash=True) 46 | class EmbedAuthor: 47 | name: str 48 | url: str | None = None 49 | icon_url: str | None = None 50 | proxy_icon_url: str | None = None 51 | 52 | 53 | @define(hash=True) 54 | class EmbedImage: 55 | url: str 56 | proxy_url: str | None = None 57 | height: int | None = None 58 | width: int | None = None 59 | placeholder: str = attrs.field(default=None, repr=False) 60 | placeholder_version: int = attrs.field(default=None, repr=False) 61 | 62 | 63 | EmbedThumbnail = EmbedImage 64 | 65 | 66 | @define(hash=True) 67 | class Embed: 68 | title: str = field() 69 | description: str = field() 70 | url: str = field() 71 | timestamp: datetime = field() 72 | color: str | int = field() 73 | footer: EmbedFooter = field() 74 | image: EmbedImage = field() 75 | thumbnail: EmbedThumbnail = field() 76 | author: EmbedAuthor = field() 77 | fields: List[EmbedField] = field() 78 | _type: str = field(alias="type", default="rich") 79 | 80 | 81 | def embed( 82 | title: str | None = None, 83 | description: str | None = None, 84 | url: str | None = None, 85 | timestamp: datetime | None = None, 86 | color: str | int | None = None, 87 | footer: EmbedFooter | None = None, 88 | image: EmbedImage | str | None = None, 89 | thumbnail: EmbedThumbnail | str | None = None, 90 | author: EmbedAuthor | None = None, 91 | fields: List[EmbedField] | None = None, 92 | ) -> Embed: 93 | if isinstance(image, str): 94 | image = EmbedImage(url=image, proxy_url=None, width=1024, height=1024) 95 | 96 | if fields is None: 97 | fields = [] 98 | 99 | return Embed( 100 | title=title, 101 | description=description, 102 | url=url, 103 | timestamp=timestamp, 104 | color=color, 105 | footer=footer, 106 | image=image, 107 | thumbnail=thumbnail, 108 | author=author, 109 | fields=fields 110 | ) 111 | -------------------------------------------------------------------------------- /quant/utils/parser.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations 25 | 26 | from typing import Any, TYPE_CHECKING 27 | from datetime import datetime 28 | 29 | if TYPE_CHECKING: 30 | from quant.impl.core.client import Client 31 | from quant.entities.interactions.interaction import Interaction 32 | 33 | from quant.entities.emoji import PartialEmoji 34 | from quant.entities.permissions import Permissions 35 | from quant.entities.interactions.slash_option import SlashOptionType 36 | 37 | 38 | async def parse_option_type( 39 | client: Client, 40 | interaction: Interaction, 41 | option_type: SlashOptionType, 42 | value: Any 43 | ) -> Any | None: 44 | guild = client.cache.get_guild(interaction.guild_id) 45 | 46 | match option_type: 47 | case SlashOptionType.USER: 48 | return await client.rest.fetch_user(int(value)) 49 | case SlashOptionType.ROLE: 50 | return guild.get_role(int(value)) 51 | case SlashOptionType.CHANNEL: 52 | return guild.get_channel(int(value)) 53 | case _: 54 | return value 55 | 56 | 57 | def parse_permissions(permission_value: int) -> Permissions: 58 | decoded_permissions = Permissions(0) 59 | 60 | if isinstance(permission_value, str): 61 | permission_value = int(permission_value) 62 | 63 | for permission in Permissions: 64 | try: 65 | value = permission.value 66 | except TypeError: 67 | value = permission.value() 68 | 69 | if permission_value is None: 70 | return Permissions.NONE 71 | 72 | if permission_value & value == Permissions.ADMINISTRATOR.value: 73 | return Permissions.ADMINISTRATOR 74 | 75 | if permission_value & value == permission.value: 76 | decoded_permissions |= permission 77 | 78 | return decoded_permissions 79 | 80 | 81 | def iso_to_datetime(time: str = None) -> datetime | None: 82 | if time is None: 83 | return 84 | 85 | return datetime.fromisoformat(time) 86 | 87 | 88 | def timestamp_to_datetime(time: str | int = None) -> datetime | None: 89 | if time is None: 90 | return 91 | 92 | return datetime.fromtimestamp(time) 93 | 94 | 95 | def clamp(x: int) -> int: 96 | return max(0, min(x, 255)) 97 | 98 | 99 | def string_to_emoji(emoji: str) -> PartialEmoji: 100 | animated = emoji.startswith("", "").replace("<", "").split(":") 102 | 103 | if len(split_emoji) == 1: 104 | emoji_name, emoji_id = split_emoji[0], None 105 | else: 106 | emoji_name, emoji_id = split_emoji 107 | 108 | return PartialEmoji(id=emoji_id, name=emoji_name, animated=animated) 109 | -------------------------------------------------------------------------------- /quant/entities/factory/event_controller.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | import inspect 25 | from typing import Dict, List, TypeVar, Callable, Set, overload 26 | 27 | from quant.impl.events.types import EventTypes 28 | from quant.entities.factory.event_factory import EventFactory, EventT 29 | 30 | EventNameT = TypeVar("EventNameT", bound=str) 31 | 32 | 33 | class EventController: 34 | def __init__(self, factory: EventFactory) -> None: 35 | self.factory = factory 36 | self._builtin_events = { 37 | EventTypes.READY_EVENT: self.factory.deserialize_ready_event, 38 | EventTypes.GUILD_CREATE: self.factory.deserialize_guild_create_event, 39 | EventTypes.INTERACTION_CREATE: self.factory.deserialize_interaction_event, 40 | EventTypes.MESSAGE_CREATE: self.factory.deserialize_message_create_event, 41 | EventTypes.MESSAGE_DELETE: self.factory.deserialize_message_delete_event, 42 | EventTypes.MESSAGE_UPDATE: self.factory.deserialize_message_edit_event, 43 | EventTypes.VOICE_STATE_UPDATE: self.factory.deserialize_voice_state_update_event, 44 | EventTypes.VOICE_SERVER_UPDATE: self.factory.deserialize_voice_server_update_event, 45 | EventTypes.CHANNEL_CREATE: self.factory.entity_factory.deserialize_channel, 46 | EventTypes.GUILD_MEMBER_ADD: self.factory.deserialize_guild_member_add_event, 47 | EventTypes.GUILD_MEMBER_REMOVE: self.factory.deserialize_guild_member_remove_event 48 | } 49 | 50 | event_func_prefix = "when_" 51 | 52 | for event_name, callback in self._builtin_events.items(): 53 | setattr(self, event_func_prefix + event_name.lower(), callback) 54 | 55 | self.available: List[str] = [ 56 | member[0][len(event_func_prefix):].upper() 57 | for member in inspect.getmembers(self) 58 | if member[0].startswith(event_func_prefix) 59 | ] 60 | 61 | @overload 62 | async def dispatch(self, event: EventT) -> None: 63 | ... 64 | 65 | @overload 66 | async def dispatch(self, event_name: str, details: Dict) -> None: 67 | ... 68 | 69 | async def dispatch(self, *args) -> None: 70 | if len(args) == 1: 71 | event = args[0] 72 | event_callbacks = self.factory.added_listeners.get(type(event)) 73 | 74 | if event_callbacks is not None: 75 | for event_callback in event_callbacks: 76 | await event_callback(event) 77 | 78 | if event is not None: 79 | await event.call() 80 | 81 | if len(args) == 2: 82 | event_name, details = args 83 | if event_name not in self.available: 84 | return 85 | 86 | event = getattr(self, 'when_' + event_name.lower()) 87 | await self.dispatch(event(details)) 88 | -------------------------------------------------------------------------------- /quant/entities/modal/modal.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations 25 | 26 | from typing import List, overload, TYPE_CHECKING 27 | 28 | import attrs 29 | 30 | if TYPE_CHECKING: 31 | from quant.entities.action_row import ActionRow 32 | 33 | from quant.impl.core.context import ModalContext 34 | from quant.entities.api.backend import CallbackBackend 35 | from quant.entities.modal.text_input import TextInputStyle, TextInput 36 | 37 | 38 | @attrs.define(kw_only=True, hash=True) 39 | class Modal(CallbackBackend[ModalContext]): 40 | """Represents a discord modal window 41 | 42 | Parameters 43 | ========== 44 | title: :class:`str` 45 | Modal window title 46 | custom_id: :class:`str` 47 | Modal custom ID 48 | components: :class:`List[ActionRow]` 49 | Modal components 50 | """ 51 | title: str = attrs.field(default="Modal") 52 | custom_id: str = attrs.field() 53 | components: List[ActionRow] = attrs.field(default=[]) 54 | 55 | @overload 56 | def add_short_text_input(self, text_input: TextInput) -> None: 57 | """Append your short text input""" 58 | 59 | @overload 60 | def add_short_text_input( 61 | self, 62 | custom_id: str, 63 | label: str, 64 | min_length: int = 0, 65 | max_length: int = 4000, 66 | required: bool = True, 67 | value: str | None = None, 68 | placeholder: str | None = None 69 | ) -> None: 70 | """Append new short text input""" 71 | 72 | def add_short_text_input(self, *args, **kwargs) -> None: 73 | from quant.entities.action_row import ActionRow 74 | 75 | if len(args) == 1: 76 | if not isinstance(args[0], TextInput): 77 | return 78 | 79 | text_input = args[0] 80 | else: 81 | text_input = TextInput(style=TextInputStyle.SHORT.value, **kwargs) 82 | 83 | self.components.append(ActionRow([text_input])) 84 | 85 | @overload 86 | def add_paragraph_text_input( 87 | self, 88 | custom_id: str, 89 | label: str, 90 | min_length: int = 0, 91 | max_length: int = 4000, 92 | required: bool = True, 93 | value: str | None = None, 94 | placeholder: str | None = None 95 | ) -> None: 96 | """Append new paragraph text input""" 97 | 98 | def add_paragraph_text_input(self, *args, **kwargs) -> None: 99 | from quant.entities.action_row import ActionRow 100 | 101 | if len(args) == 1: 102 | if not isinstance(args[0], TextInput): 103 | return 104 | 105 | text_input = args[0] 106 | else: 107 | text_input = TextInput(style=TextInputStyle.PARAGRAPH.value, **kwargs) 108 | 109 | self.components.append(ActionRow([text_input])) 110 | 111 | 112 | @attrs.define(kw_only=True, hash=True) 113 | class ModalInteractionCallbackData: 114 | title: str = attrs.field() 115 | custom_id: str = attrs.field() 116 | components: List[ActionRow] = attrs.field() 117 | -------------------------------------------------------------------------------- /quant/entities/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from .action_row import ActionRow 25 | from .activity import ( 26 | Activity, 27 | ActivityBuilder, 28 | ActivityFlags, 29 | ActivityType, 30 | ActivityStatus, 31 | ActivityAssets, 32 | ActivityData 33 | ) 34 | from .allowed_mentions import AllowedMentions, AllowedMentionsTypes, suppress_mentions 35 | from .button import Button, ButtonStyle, button 36 | from .channel import Channel, Thread, ThreadMetadata, ChannelType, VoiceChannel 37 | from .embeds import * 38 | from .emoji import Emoji, Reaction, PartialReaction, PartialEmoji 39 | from .guild import Guild 40 | from .invite import Invite 41 | from .intents import Intents 42 | from .member import GuildMember 43 | from .roles import GuildRole 44 | from .message_flags import MessageFlags 45 | from .snowflake import Snowflake 46 | from .user import User 47 | from .voice_server_update import VoiceServer 48 | from .voice_state_update import VoiceState 49 | from .webhook import Webhook, WebhookTypes 50 | from .modal import Modal, ModalInteractionCallbackData, TextInput, TextInputStyle 51 | from .interactions import ( 52 | Interaction, 53 | InteractionData, 54 | InteractionType, 55 | InteractionResponse, 56 | InteractionCallbackType, 57 | InteractionCallbackData, 58 | InteractionDataOption, 59 | ApplicationCommandOptionType, 60 | ApplicationCommandOption, 61 | SlashOptionType, 62 | ApplicationCommandContexts 63 | ) 64 | from .integration import IntegrationTypes, Integration 65 | from .permissions import Permissions 66 | from .message import Message, MessageReference, Attachment 67 | from .color import Color, RGB, Hex 68 | from .poll import Poll, PollResults, PollAnswer, PollMedia, PollMediaType, poll, Answer 69 | from .locales import DiscordLocale 70 | 71 | 72 | __all__ = ( 73 | "Attachment", 74 | "ActionRow", 75 | "Activity", 76 | "ActivityFlags", 77 | "ActivityBuilder", 78 | "ActivityType", 79 | "ActivityAssets", 80 | "ActivityStatus", 81 | "ActivityData", 82 | "AllowedMentions", 83 | "AllowedMentionsTypes", 84 | "Button", 85 | "ButtonStyle", 86 | "Channel", 87 | "Message", 88 | "MessageReference", 89 | "Embed", 90 | "EmbedThumbnail", 91 | "EmbedImage", 92 | "EmbedField", 93 | "EmbedAuthor", 94 | "EmbedFooter", 95 | "Emoji", 96 | "Reaction", 97 | "PartialReaction", 98 | "Guild", 99 | "GuildMember", 100 | "MessageFlags", 101 | "Snowflake", 102 | "User", 103 | "VoiceState", 104 | "VoiceServer", 105 | "Webhook", 106 | "WebhookTypes", 107 | "Intents", 108 | "ApplicationCommandOptionType", 109 | "InteractionDataOption", 110 | "Interaction", 111 | "InteractionCallbackData", 112 | "InteractionCallbackType", 113 | "InteractionType", 114 | "InteractionResponse", 115 | "InteractionData", 116 | "ApplicationCommandOption", 117 | "SlashOptionType", 118 | "Modal", 119 | "ModalInteractionCallbackData", 120 | "TextInput", 121 | "TextInputStyle", 122 | "Invite", 123 | "Thread", 124 | "ThreadMetadata", 125 | "ChannelType", 126 | "VoiceChannel", 127 | "embed", 128 | "button", 129 | "Permissions", 130 | "GuildRole", 131 | "Integration", 132 | "IntegrationTypes", 133 | "ApplicationCommandContexts", 134 | "suppress_mentions", 135 | "Color", 136 | "RGB", 137 | "Hex", 138 | "PollMedia", 139 | "Poll", 140 | "PollMediaType", 141 | "PollResults", 142 | "poll", 143 | "Answer", 144 | "PartialEmoji", 145 | "DiscordLocale" 146 | ) 147 | -------------------------------------------------------------------------------- /quant/impl/core/commands.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations as _ 25 | 26 | from typing import Any, List, TypeVar, Dict 27 | 28 | import attrs 29 | 30 | from quant.entities.api.backend import CallbackBackend 31 | from quant.entities.integration import IntegrationTypes 32 | from quant.entities.interactions.application_command import ApplicationCommandTypes, ApplicationCommandContexts 33 | from quant.entities.interactions.slash_option import ApplicationCommandOption, SlashOptionType 34 | from quant.entities.permissions import Permissions 35 | from quant.entities.snowflake import Snowflake 36 | from quant.entities.locales import DiscordLocale 37 | from quant.impl.core.context import BaseContext, InteractionContext 38 | 39 | ContextT = TypeVar("ContextT", bound=BaseContext | InteractionContext) 40 | 41 | 42 | @attrs.define 43 | class _Command(CallbackBackend[ContextT]): 44 | name: str = attrs.field() 45 | description: str = attrs.field(default="Empty description") 46 | 47 | 48 | @attrs.define 49 | class ApplicationCommandObject(_Command): 50 | name_localizations: Dict[DiscordLocale, str] = attrs.field(default=None) 51 | description_localizations: Dict[DiscordLocale, str] = attrs.field(default=None) 52 | cmd_id: Snowflake = attrs.field(default=Snowflake()) 53 | application_id: Snowflake = attrs.field(default=Snowflake()) 54 | dm_permissions: bool = attrs.field(default=False) 55 | default_permission: bool = attrs.field(default=False) 56 | nsfw: bool = attrs.field(default=False) 57 | cmd_type: ApplicationCommandTypes = attrs.field(default=ApplicationCommandTypes.CHAT_INPUT) 58 | guild_id: Snowflake = attrs.field(default=None), 59 | options: List[ApplicationCommandOption] = attrs.field(default=None), 60 | default_member_permissions: Permissions = attrs.field(default=Permissions.NONE), 61 | integration_types: List[IntegrationTypes] | None = attrs.field(default=None) 62 | contexts: List[ApplicationCommandContexts] | None = attrs.field(default=None) 63 | 64 | @property 65 | def mention(self) -> str: 66 | return f"" 67 | 68 | def option( 69 | self, 70 | name: str, 71 | description: str, 72 | min_value: int | None = None, 73 | max_value: int | None = None, 74 | min_length: int | None = None, 75 | max_length: int | None = None, 76 | autocomplete: bool = False, 77 | channel_types: List[Any] | None = None, 78 | options: List[ApplicationCommandOption] | None = None, 79 | choices: List[Any] | None = None, 80 | required: bool = False, 81 | option_type: SlashOptionType | None = None 82 | ) -> ApplicationCommandOption: 83 | option = ApplicationCommandOption( 84 | name=name, 85 | description=description, 86 | min_value=min_value, 87 | max_value=max_value, 88 | min_length=min_length, 89 | max_length=max_length, 90 | autocomplete=autocomplete, 91 | channel_types=channel_types, 92 | options=options if options else [], 93 | choices=choices, 94 | required=required, 95 | type=option_type 96 | ) 97 | 98 | if self.options is not None: 99 | self.options.append(option) 100 | return option 101 | 102 | self.options = [option] 103 | return option 104 | 105 | def __hash__(self) -> int: 106 | return hash(self.cmd_id) 107 | 108 | 109 | @attrs.define(hash=True) 110 | class SlashSubCommand(ApplicationCommandOption, CallbackBackend[ContextT]): 111 | type: SlashOptionType = attrs.field(default=SlashOptionType.SUB_COMMAND) 112 | 113 | 114 | @attrs.define(hash=True) 115 | class SlashSubGroup(ApplicationCommandOption): 116 | type: SlashOptionType = attrs.field(default=SlashOptionType.SUB_COMMAND_GROUP) 117 | 118 | 119 | @attrs.define 120 | class SlashCommand(ApplicationCommandObject): 121 | options: List[ApplicationCommandOption] | None = attrs.field(default=None) 122 | guild_ids: List[Snowflake | int] | None = attrs.field(default=None) 123 | 124 | def __hash__(self) -> int: 125 | return hash(self.cmd_id) 126 | -------------------------------------------------------------------------------- /quant/entities/channel.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations 25 | 26 | from enum import Enum 27 | from datetime import datetime 28 | from typing import Any, List, TYPE_CHECKING 29 | 30 | import attrs 31 | 32 | if TYPE_CHECKING: 33 | from .user import User 34 | from .message import MessageReference, Attachment, MessageFlags, Message 35 | from .action_row import ActionRow 36 | 37 | from .model import BaseModel 38 | from .snowflake import Snowflake 39 | from .embeds import Embed 40 | from .allowed_mentions import AllowedMentions 41 | from .permissions import Permissions 42 | 43 | 44 | class ChannelType(int, Enum): 45 | GUILD_TEXT = 0 46 | DM = 1 47 | GUILD_VOICE = 2 48 | GROUP_DM = 3 49 | GUILD_CATEGORY = 4 50 | GUILD_ANNOUNCEMENT = 5 51 | ANNOUNCEMENT_THREAD = 10 52 | PUBLIC_THREAD = 11 53 | PRIVATE_THREAD = 12 54 | GUILD_STAGE_VOICE = 13 55 | GUILD_DIRECTORY = 14 56 | GUILD_FORUM = 15 57 | GUILD_MEDIA = 16 58 | 59 | 60 | @attrs.define(kw_only=True, hash=True) 61 | class Channel(BaseModel): 62 | id: Snowflake = attrs.field(default=0) 63 | type: ChannelType = attrs.field(default=0) 64 | guild_id: Snowflake = attrs.field(default=0) 65 | position: int = attrs.field(default=0) 66 | permission_overwrites: Any = attrs.field(default=None) 67 | name: str = attrs.field(default=None) 68 | topic: str = attrs.field(default=None) 69 | nsfw: bool = attrs.field(default=False) 70 | version: str = attrs.field(default=None, repr=False) 71 | status: str = attrs.field(default=None, repr=False) 72 | theme_color: str = attrs.field(default=None, repr=False) 73 | icon_emoji: str = attrs.field(default=None, repr=False) 74 | template: str = attrs.field(default=None, repr=False) 75 | icon: str = attrs.field(default=None) 76 | owner_id: Snowflake = attrs.field(default=0) 77 | application_id: Snowflake = attrs.field(default=0) 78 | managed: bool = attrs.field(default=False) 79 | parent_id: Snowflake = attrs.field(default=0) 80 | member_count: int = attrs.field(default=0) 81 | member: Any = attrs.field(default=None) 82 | default_auto_archive_duration: int = attrs.field(default=0) 83 | permissions: Permissions = attrs.field(default=None) 84 | flags: int = attrs.field(default=0) 85 | # Is forum 86 | # default_reaction_emoji: List[Reaction] = attrs.field(default=None) 87 | default_thread_rate_limit_per_user: int = attrs.field(default=0) 88 | default_sort_order: int = attrs.field(default=0) 89 | 90 | @property 91 | def mention(self) -> str: 92 | return f"<#{self.id}>" 93 | 94 | async def send_message( 95 | self, 96 | content: Any = None, 97 | nonce: str | int = None, 98 | tts: bool = False, 99 | embed: Embed = None, 100 | embeds: List[Embed] = None, 101 | allowed_mentions: AllowedMentions = None, 102 | message_reference: MessageReference | None = None, 103 | components: ActionRow | None = None, 104 | sticker_ids: List = None, 105 | files=None, 106 | payload_json: str = None, 107 | attachments: List[Attachment] = None, 108 | flags: MessageFlags | int | None = None 109 | ) -> Message: 110 | return await self.client.rest.create_message( 111 | channel_id=self.id, 112 | content=str(content), 113 | nonce=nonce, 114 | tts=tts, 115 | embed=embed, 116 | embeds=embeds, 117 | allowed_mentions=allowed_mentions, 118 | message_reference=message_reference, 119 | components=components, 120 | sticker_ids=sticker_ids, 121 | files=files, 122 | payload_json=payload_json, 123 | attachments=attachments, 124 | flags=flags 125 | ) 126 | 127 | 128 | @attrs.define(kw_only=True, hash=True) 129 | class ThreadMetadata: 130 | archived: bool = attrs.field() 131 | auto_archive_duration: int = attrs.field() 132 | archive_timestamp: datetime = attrs.field() 133 | locked: bool = attrs.field() 134 | invitable: bool = attrs.field(default=False) 135 | create_timestamp: datetime = attrs.field() 136 | 137 | 138 | @attrs.define(kw_only=True, hash=True) 139 | class VoiceChannel(Channel): 140 | bitrate: int = attrs.field(default=0) 141 | user_limit: int = attrs.field(default=0) 142 | rtc_region: str = attrs.field(default=None) 143 | video_quality_mode: int = attrs.field(default=0) 144 | 145 | 146 | @attrs.define(kw_only=True, hash=True) 147 | class TextChannel(Channel): 148 | last_message_id: Snowflake = attrs.field(default=0) 149 | rate_limit_per_user: int = attrs.field(default=0) 150 | last_pin_timestamp: datetime = attrs.field(default=None) 151 | 152 | 153 | @attrs.define(kw_only=True, hash=True) 154 | class Thread(TextChannel): 155 | total_message_sent: int = attrs.field() 156 | message_count: int = attrs.field() 157 | available_tags: List[Any] = attrs.field() 158 | applied_tags: List[Snowflake] = attrs.field() 159 | thread_metadata: ThreadMetadata = attrs.field() 160 | default_auto_archive_duration: datetime = attrs.field() 161 | member_count: int = attrs.field() 162 | recipients: List[User] = attrs.field(default=None) 163 | -------------------------------------------------------------------------------- /quant/entities/activity.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations as _ 25 | 26 | import enum 27 | from typing import TYPE_CHECKING, List 28 | 29 | import attrs 30 | 31 | if TYPE_CHECKING: 32 | from typing_extensions import Self 33 | 34 | from quant.entities.snowflake import Snowflake 35 | 36 | 37 | class ActivityStatus(enum.Enum): 38 | """Discord activity statuses""" 39 | ONLINE = "online" 40 | DO_NOT_DISTRIBUTE = "dnd" 41 | IDLE = "idle" 42 | OFFLINE = "offline" 43 | 44 | DND = DO_NOT_DISTRIBUTE 45 | AFK = IDLE 46 | INVISIBLE = OFFLINE 47 | 48 | 49 | class ActivityFlags(enum.IntFlag): 50 | """Discord activity flags""" 51 | INSTANCE = 1 << 0 52 | JOIN = 1 << 1 53 | SPECTATE = 1 << 2 54 | JOIN_REQUEST = 1 << 3 55 | SYNC = 1 << 4 56 | PLAY = 1 << 5 57 | PARTY_PRIVACY_FRIENDS = 1 << 6 58 | PARTY_PRIVACY_VOICE_CHANNEL = 1 << 7 59 | EMBEDDED = 1 << 8 60 | 61 | 62 | class ActivityType(enum.IntEnum): 63 | """Discord activity types""" 64 | GAME = 0 65 | STREAMING = 1 66 | LISTENING = 2 67 | WATCHING = 3 68 | CUSTOM = 4 69 | COMPETING = 5 70 | 71 | 72 | class ClientStatus(enum.Enum): 73 | """Discord client statuses""" 74 | DESKTOP = "desktop" 75 | MOBILE = "mobile" 76 | WEB = "web" 77 | 78 | 79 | @attrs.define(hash=True) 80 | class Activity: 81 | """Represents a discord activity 82 | 83 | Parameters 84 | ========== 85 | name: :class:`str` 86 | Activity name 87 | url: :class:`str | None` 88 | Activity url (twitch for example) 89 | type: :class:`ActivityType` 90 | Activity type 91 | created_at: :class:`float` 92 | Activity created at timestamp 93 | state: :class:`str | None` 94 | State text 95 | details: :class:`ActivityType` 96 | Details text 97 | """ 98 | name: str | None = attrs.field(default=None) 99 | created_at: float = attrs.field(default=None) 100 | url: str | None = attrs.field(default=None) 101 | type: ActivityType = attrs.field(default=ActivityType.GAME) 102 | timestamps: List = attrs.field(default=None) 103 | application_id: Snowflake = attrs.field(default=None) 104 | details: str = attrs.field(default=None) 105 | state: str = attrs.field(default=None) 106 | emoji: str = attrs.field(default=None) 107 | 108 | 109 | @attrs.define(hash=True) 110 | class ActivityAssets: 111 | """Represents activity assets (image, text) 112 | 113 | Parameters 114 | ========== 115 | large_image: :class:`str` 116 | Large activity image 117 | large_text: :class:`str` 118 | Large activity text 119 | small_image: :class:`str` 120 | Small activity image 121 | small_text: :class:`str` 122 | Small activity text 123 | 124 | """ 125 | large_image: str = attrs.field(default=None) 126 | large_text: str = attrs.field(default=None) 127 | small_image: str = attrs.field(default=None) 128 | small_text: str = attrs.field(default=None) 129 | 130 | 131 | @attrs.define(hash=True) 132 | class ActivityData: 133 | """Activity data 134 | 135 | Parameters 136 | ========= 137 | activity: :class:`Activity` 138 | Setting up main activity 139 | status: :class:`ActivityStatus` 140 | Setting up activity status 141 | since: :class:`int | None` 142 | Time since enabled 143 | afk: :class:`bool` 144 | Is AFK or no 145 | """ 146 | activity: Activity | None = attrs.field(default=None) 147 | status: ActivityStatus | None = attrs.field(default=None) 148 | since: int | None = attrs.field(default=None) 149 | afk: bool | None = attrs.field(default=None) 150 | 151 | 152 | class ActivityBuilder: 153 | """Build an activity 154 | 155 | .. attributetable:: ActivityBuilder 156 | """ 157 | 158 | def __init__(self) -> None: 159 | self._activity: Activity | None = None 160 | self._status: ActivityStatus | None = None 161 | self._since: int | None = None 162 | self._afk: bool | None = None 163 | 164 | def set_activity( 165 | self, 166 | name: str | None = None, 167 | url: str | None = None, 168 | created_at: float = None, 169 | activity_type: ActivityType = ActivityType.GAME, 170 | application_id: Snowflake | None = None, 171 | details: str | None = None, 172 | state: str | None = None, 173 | emoji: str | None = None, 174 | ) -> Self: 175 | """Set an activity""" 176 | self._activity = Activity( 177 | name=name, 178 | url=url, 179 | type=activity_type, 180 | created_at=created_at, 181 | application_id=application_id, 182 | details=details, 183 | state=state, 184 | emoji=emoji 185 | ) 186 | return self 187 | 188 | def set_status(self, status: ActivityStatus) -> Self: 189 | """Build status""" 190 | self._status = status 191 | return self 192 | 193 | def set_since(self, value: int) -> Self: 194 | """Set since value""" 195 | self._since = value 196 | return self 197 | 198 | def set_afk(self, value: bool) -> Self: 199 | """Set AFK value""" 200 | self._afk = value 201 | return self 202 | 203 | def build(self) -> ActivityData: 204 | """Build an ActivityData""" 205 | return ActivityData( 206 | activity=self._activity, 207 | status=self._status, 208 | since=self._since, 209 | afk=self._afk 210 | ) 211 | -------------------------------------------------------------------------------- /quant/impl/core/context.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations 25 | 26 | from typing import List, TYPE_CHECKING, Any, TypeVar 27 | 28 | if TYPE_CHECKING: 29 | from quant.impl.core.client import Client 30 | from quant.entities.interactions.interaction import Interaction 31 | from quant.entities.message import Message, MessageReference, MessageFlags 32 | from quant.entities.button import Button 33 | from quant.entities.user import User 34 | from quant.entities.member import GuildMember 35 | from quant.entities.action_row import ActionRow 36 | from quant.entities.modal.modal import ModalInteractionCallbackData 37 | from quant.entities.modal.text_input import TextInput 38 | from quant.entities.modal.modal import Modal 39 | 40 | from quant.entities.message import Attachment 41 | from quant.impl.files import AttachableURL, File 42 | from quant.entities.snowflake import Snowflake 43 | from quant.entities.roles import GuildRole 44 | from quant.entities.channel import TextChannel, VoiceChannel, Thread 45 | from quant.entities.interactions.choice_response import InteractionDataOption 46 | from quant.entities.embeds import Embed 47 | from quant.entities.allowed_mentions import AllowedMentions 48 | from quant.utils.parser import parse_option_type 49 | 50 | AttachmentT = TypeVar("AttachmentT", bound=AttachableURL | File | Attachment) 51 | 52 | 53 | class BaseContext: 54 | def __init__(self, client, message) -> None: 55 | self.original_message: Message = message 56 | self.client: Client = client 57 | 58 | @property 59 | def author(self) -> User: 60 | return self.original_message.author 61 | 62 | async def send_message( 63 | self, 64 | channel_id: int = None, 65 | content: Any = None, 66 | nonce: str | int = None, 67 | tts: bool = False, 68 | embed: Embed = None, 69 | embeds: List[Embed] = None, 70 | allowed_mentions: AllowedMentions = None, 71 | message_reference: MessageReference | None = None, 72 | components: ActionRow | None = None, 73 | sticker_ids: List = None, 74 | files=None, 75 | payload_json: str = None, 76 | attachments: List[AttachmentT] = None, 77 | flags: MessageFlags | int | None = None 78 | ) -> Message: 79 | return await self.client.rest.create_message( 80 | channel_id=channel_id if channel_id is not None else self.original_message.channel_id, 81 | content=str(content), 82 | nonce=nonce, 83 | tts=tts, 84 | embed=embed, 85 | embeds=embeds, 86 | allowed_mentions=allowed_mentions, 87 | message_reference=message_reference, 88 | components=components, 89 | sticker_ids=sticker_ids, 90 | files=files, 91 | payload_json=payload_json, 92 | attachments=attachments, 93 | flags=flags 94 | ) 95 | 96 | 97 | OptionT = TypeVar("OptionT", bound=InteractionDataOption | None) 98 | ChannelT = TypeVar("ChannelT", bound=TextChannel | VoiceChannel | Thread) 99 | 100 | 101 | class InteractionContext: 102 | def __init__(self, client: Client, interaction: Interaction) -> None: 103 | self.client = client 104 | self.interaction = interaction 105 | 106 | @property 107 | def guild_id(self) -> Snowflake | int: 108 | return self.interaction.guild_id 109 | 110 | @property 111 | def author(self) -> GuildMember: 112 | return self.interaction.member 113 | 114 | async def get_option(self, name: str, default: Any | None = None) -> OptionT: 115 | interaction_options = self.interaction.data.options 116 | if interaction_options is None: 117 | return default 118 | 119 | options = list(filter(lambda x: x.name.lower() == name.lower(), interaction_options)) 120 | if len(options) == 0: 121 | return default 122 | 123 | option = options[0] 124 | return await parse_option_type(self.client, self.interaction, option.type, option.value) 125 | 126 | async def get_user_option(self, name: str) -> User | None: 127 | return await self.get_option(name=name) 128 | 129 | async def get_channel_option(self, name: str) -> ChannelT | None: 130 | return await self.get_option(name=name) 131 | 132 | async def get_role_option(self, name: str) -> GuildRole | None: 133 | return await self.get_option(name=name) 134 | 135 | async def respond( 136 | self, 137 | content: str | Any | None = None, 138 | tts: bool = False, 139 | embed: Embed | None = None, 140 | embeds: List[Embed] | None = None, 141 | allowed_mentions: AllowedMentions | None = None, 142 | flags: MessageFlags | int = 0, 143 | components: ActionRow = None, 144 | attachments: List[AttachmentT] = None 145 | ) -> None: 146 | await self.interaction.respond( 147 | content=content, 148 | tts=tts, 149 | embed=embed, 150 | embeds=embeds, 151 | allowed_mentions=allowed_mentions, 152 | flags=flags, 153 | components=components, 154 | attachments=attachments 155 | ) 156 | 157 | async def respond_modal(self, modal: Modal) -> None: 158 | await self.interaction.respond_modal(modal=modal) 159 | 160 | 161 | class ButtonContext(InteractionContext): 162 | def __init__(self, client, interaction, button) -> None: 163 | self.button: Button = button 164 | super().__init__(client, interaction) 165 | 166 | 167 | class ModalContext(InteractionContext): 168 | def __init__(self, client: Client, interaction: Interaction) -> None: 169 | self.values = {} 170 | 171 | interaction_data: ModalInteractionCallbackData = interaction.data 172 | for action_row in interaction_data.components: 173 | for component in action_row.components: 174 | component: TextInput = component 175 | self.values[component.custom_id] = component.value 176 | 177 | super().__init__(client, interaction) 178 | -------------------------------------------------------------------------------- /quant/impl/core/http_manager.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | import asyncio 25 | import http 26 | import json 27 | from typing import Dict, Any, TypeVar, Tuple 28 | from datetime import datetime 29 | 30 | from aiohttp import ClientSession, ClientResponse, FormData 31 | 32 | from quant.utils import logger, parser 33 | from quant.api.core.http_manager_abc import HttpManager, AcceptContentType 34 | from quant.entities.ratelimits.ratelimit import RateLimit, RateLimitBucketReset, Bucket 35 | from quant.impl.core.exceptions.http_exception import Forbidden, InternalServerError, HTTPException 36 | 37 | DataT = TypeVar("DataT", bound=Dict[str, Any] | FormData | list) 38 | HeadersT = TypeVar("HeadersT", bound=Dict[str, Any] | None) 39 | 40 | 41 | class HttpManagerImpl(HttpManager): 42 | def __init__(self, authorization: str | None = None) -> None: 43 | self.authorization = authorization 44 | self.max_retries = 3 45 | self.session = ClientSession() 46 | 47 | async def parse_ratelimits(self, response: ClientResponse) -> Bucket | None: 48 | headers = response.headers 49 | if headers is None: 50 | return 51 | 52 | if (max_retries := headers.get("X-RateLimit-Limit")) is not None: 53 | self.max_retries = int(max_retries) 54 | 55 | remaining_retries, ratelimit_reset, reset_after, scope, bucket = ( 56 | headers.get("X-RateLimit-Remaining"), 57 | headers.get("X-RateLimit-Reset"), 58 | headers.get("X-RateLimit-Reset-After"), 59 | headers.get("x-RateLimit-Scope"), 60 | headers.get("X-RateLimit-Bucket") 61 | ) 62 | 63 | if response.status != http.HTTPStatus.TOO_MANY_REQUESTS: 64 | return 65 | 66 | if response.content_type != AcceptContentType.APPLICATION_JSON: 67 | return 68 | 69 | time = parser.timestamp_to_datetime(int(ratelimit_reset[:-4])) 70 | bucket_reset_time = RateLimitBucketReset( 71 | reset_time=time, 72 | reset_time_milliseconds=float(reset_after) 73 | ) 74 | if bucket_reset_time.reset_time <= datetime.now() or bucket_reset_time.reset_time_milliseconds <= 0: 75 | return Bucket( 76 | rate_limit=None, 77 | bucket_reset=bucket_reset_time 78 | ) 79 | 80 | response_data: Dict = await response.json() 81 | retry_after, message, is_global, code = ( 82 | response_data.get("retry_after"), 83 | response_data.get("message"), 84 | response_data.get("global"), 85 | response_data.get("code") 86 | ) 87 | 88 | ratelimit = RateLimit( 89 | max_retries=self.max_retries, 90 | remaining_retries=int(remaining_retries), 91 | retry_after=retry_after, 92 | message=message, 93 | is_global=is_global, 94 | code=code, 95 | scope=scope, 96 | bucket=bucket 97 | ) 98 | return Bucket( 99 | rate_limit=ratelimit, 100 | bucket_reset=bucket_reset_time 101 | ) 102 | 103 | def _build_base_headers(self, headers: DataT = None) -> Dict: 104 | if headers is None: 105 | headers = {} 106 | 107 | header_keys = {"Authorization": self.authorization, "Content-Type": AcceptContentType.APPLICATION_JSON} 108 | for key, value in header_keys.items(): 109 | if headers.get(key) is None: 110 | headers[key] = value 111 | 112 | return headers 113 | 114 | async def request( 115 | self, 116 | method: str, url: str, 117 | data: DataT = None, 118 | headers: HeadersT = None, 119 | pre_build_headers: bool = True, 120 | form_data: FormData | None = None 121 | ) -> ClientResponse | None: 122 | if data is not None and form_data is not None: 123 | raise HTTPException("Can't handle form data and data at same time") 124 | 125 | if pre_build_headers: 126 | headers = self._build_base_headers(headers) 127 | 128 | request_data = { 129 | "method": method, 130 | "url": url 131 | } 132 | 133 | if headers is not None: 134 | request_headers = request_data.get("headers") 135 | if request_headers is None: 136 | request_data["headers"] = headers 137 | 138 | if isinstance(request_headers, dict): 139 | request_headers.update(headers) 140 | 141 | async def perform_request() -> Tuple[ClientResponse, Bucket]: 142 | if data is not None: 143 | request_data["data"] = json.dumps(data) 144 | 145 | if form_data is not None: 146 | request_data["data"] = form_data 147 | 148 | response = await self.session.request(**request_data) 149 | bucket = await self.parse_ratelimits(response=response) 150 | return response, bucket 151 | 152 | response, bucket = await perform_request() 153 | if response.ok: 154 | return response 155 | 156 | match response.status: 157 | case http.HTTPStatus.TOO_MANY_REQUESTS: 158 | ratelimit = bucket.rate_limit 159 | for i in range(ratelimit.max_retries): 160 | logger.warn( 161 | f"You're being rate limited, retrying " 162 | f"(attempt: {i + 1}, retry time: {ratelimit.retry_after}, scope: {ratelimit.scope})" 163 | ) 164 | response, bucket = await perform_request() 165 | if response.ok: 166 | return response 167 | 168 | ratelimit = bucket.rate_limit 169 | await asyncio.sleep(ratelimit.retry_after) 170 | return response 171 | case http.HTTPStatus.FORBIDDEN: 172 | raise Forbidden("Missing permissions") 173 | case http.HTTPStatus.INTERNAL_SERVER_ERROR: 174 | raise InternalServerError("Server issue, try again") 175 | case http.HTTPStatus.NO_CONTENT: 176 | return 177 | 178 | if response.status not in ( 179 | http.HTTPStatus.TOO_MANY_REQUESTS, 180 | http.HTTPStatus.NO_CONTENT 181 | ): 182 | response_text = await response.read() 183 | raise HTTPException( 184 | f"Request corrupted or invalid request body. More data below\n" 185 | f"Response: {response_text}\n" 186 | f"Method: {method}\n" 187 | f"URL: {response.url}\n" 188 | f"Data: {data}\n" 189 | ) 190 | -------------------------------------------------------------------------------- /quant/entities/guild.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations 25 | 26 | import datetime 27 | import typing 28 | from typing import List, Any, TYPE_CHECKING 29 | 30 | import attrs 31 | 32 | if TYPE_CHECKING: 33 | from .invite import Invite 34 | 35 | from .member import GuildMember 36 | from .user import User 37 | from .channel import Channel 38 | from .voice_state_update import VoiceState 39 | from .model import BaseModel 40 | from .snowflake import Snowflake 41 | from .roles import GuildRole 42 | from .permissions import Permissions 43 | 44 | 45 | @attrs.define(hash=True) 46 | class Guild(BaseModel): 47 | id: Snowflake = attrs.field() 48 | name: str = attrs.field() 49 | owner_id: Snowflake = attrs.field() 50 | verification_level: int = attrs.field() 51 | explicit_content_filter: int = attrs.field() 52 | default_message_notifications: int = attrs.field() 53 | mfa_level: int = attrs.field() 54 | threads: List[Any] = attrs.field() 55 | stage_instances: List[Any] = attrs.field() 56 | unavailable: bool = attrs.field() 57 | embedded_activities: List[Any] = attrs.field() 58 | channels: List[Channel] = attrs.field() 59 | guild_scheduled_events: List[Any] = attrs.field() 60 | guild_hashes: List[Any] = attrs.field() 61 | lazy: bool = attrs.field() 62 | application_command_counts: int = attrs.field() 63 | member_count: int = attrs.field() 64 | presences: List[Any] = attrs.field() 65 | members: List[GuildMember] = attrs.field() 66 | large: bool = attrs.field() 67 | permissions: Permissions = attrs.field() 68 | roles: List[GuildRole] = attrs.field() 69 | emojis: List[Any] = attrs.field() 70 | icon: str = attrs.field() 71 | icon_hash: str = attrs.field() 72 | splash: str = attrs.field() 73 | discovery_slash: str = attrs.field() 74 | owner: bool = attrs.field() 75 | region: str = attrs.field() 76 | afk_channel_id: int = attrs.field() 77 | afk_timeout: int = attrs.field() 78 | widget_enabled: bool = attrs.field() 79 | widget_channel_id: int = attrs.field() 80 | features: List[Any] = attrs.field() 81 | application_id: int = attrs.field() 82 | system_channel_id: int = attrs.field() 83 | system_channel_flags: int = attrs.field() 84 | rules_channel_id: int = attrs.field() 85 | max_presences: int = attrs.field() 86 | max_members: int = attrs.field() 87 | vanity_url_code: str = attrs.field() 88 | description: str = attrs.field() 89 | banner: str = attrs.field() 90 | premium_tier: int = attrs.field() 91 | premium_subscription_count: int = attrs.field() 92 | preferred_locale: str = attrs.field() 93 | public_updates_channel_id: int = attrs.field() 94 | max_video_channel_users: int = attrs.field() 95 | max_stage_video_channel_users: int = attrs.field() 96 | approximate_member_count: int = attrs.field() 97 | approximate_presence_count: int = attrs.field() 98 | welcome_screen: Any = attrs.field() 99 | nsfw_level: int = attrs.field() 100 | stickers: List[Any] = attrs.field() 101 | premium_progress_bar_enabled: bool = attrs.field() 102 | safety_alerts_channel_id: int = attrs.field() 103 | voice_states: List[VoiceState] = attrs.field() 104 | home_header: str = attrs.field() 105 | discovery_splash: str = attrs.field() 106 | hub_type: int = attrs.field() 107 | latest_onboarding_question_id: int = attrs.field() 108 | nsfw: bool = attrs.field() 109 | incidents_data: str = attrs.field() 110 | embed_enabled: bool = attrs.field() 111 | embed_channel_id: int = attrs.field() 112 | inventory_settings: Any = attrs.field() 113 | soundboard_sounds: List[Any] = attrs.field() 114 | version: int = attrs.field() 115 | locale: str = attrs.field() 116 | 117 | async def delete(self) -> None: 118 | await self.client.rest.delete_guild(self.id) 119 | 120 | def get_member(self, member_id: Snowflake | int) -> GuildMember: 121 | return self.client.cache.get_member(self.id, member_id) 122 | 123 | async def fetch_member(self, member_id: Snowflake | int) -> GuildMember: 124 | return await self.client.rest.fetch_guild_member(guild_id=self.id, user_id=member_id) 125 | 126 | async def fetch_members(self, limit: int = 1000, after: Snowflake | int = Snowflake()) -> List[GuildMember]: 127 | return await self.client.rest.fetch_guild_members(guild_id=self.id, limit=limit, after=after) 128 | 129 | async def ban( 130 | self, 131 | member: Snowflake | int | GuildMember, 132 | reason: str = None, 133 | delete_message_days: int = 0, 134 | delete_message_seconds: int = 0 135 | ) -> None: 136 | await self.client.rest.create_guild_ban( 137 | guild_id=self.id, 138 | member_id=member.user.id if isinstance(member, GuildMember) else member, 139 | reason=reason, 140 | delete_message_days=delete_message_days, 141 | delete_message_seconds=delete_message_seconds 142 | ) 143 | 144 | async def kick(self, member: Snowflake | int | GuildMember, reason: str | None = None) -> None: 145 | await self.client.rest.remove_guild_member( 146 | user_id=member.user.id, 147 | guild_id=self.id, 148 | reason=reason 149 | ) 150 | 151 | async def fetch_invites(self) -> List[Invite]: 152 | return await self.client.rest.fetch_guild_invites(guild_id=self.id) 153 | 154 | def get_role(self, role_id: Snowflake | int) -> GuildRole: 155 | return self.client.cache.get_role(role_id=role_id) 156 | 157 | def get_channel(self, channel_id: Snowflake | int) -> Channel: 158 | return self.client.cache.get_channel(channel_id=channel_id) 159 | 160 | async def modify_member( 161 | self, 162 | member: Snowflake | int | GuildMember | User, 163 | nick: str | None = None, 164 | roles: List[Snowflake | int] | None = None, 165 | mute: bool | None = None, 166 | deaf: bool | None = None, 167 | move_channel_id: Snowflake | int | None = None, 168 | communication_disabled_until: datetime.datetime | None = None, 169 | flags: int | None = None, 170 | reason: str | None = None 171 | ) -> GuildMember: 172 | return await self.client.rest.modify_guild_member( 173 | user_id=member if not isinstance(member, (GuildMember, User)) else member.id, 174 | guild_id=self.id, 175 | nick=nick, 176 | roles=roles, 177 | mute=mute, 178 | deaf=deaf, 179 | move_channel_id=move_channel_id, 180 | communication_disabled_until=communication_disabled_until, 181 | flags=flags, 182 | reason=reason 183 | ) 184 | 185 | def get_everyone_role(self) -> GuildRole: 186 | return list(filter(lambda r: r.id == self.id, self.roles))[0] 187 | -------------------------------------------------------------------------------- /quant/impl/core/route.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2024 MagM1go 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from __future__ import annotations as _ 25 | 26 | from urllib.parse import urlencode 27 | from typing import Final, Dict 28 | 29 | import attrs 30 | 31 | 32 | @attrs.define 33 | class URI: 34 | _url_string: str 35 | auto_format: bool = True 36 | 37 | @property 38 | def url_string(self) -> str: 39 | if not self.auto_format: 40 | return self._url_string 41 | return Gateway.DISCORD_MAIN_API_URL + self._url_string 42 | 43 | 44 | @attrs.define 45 | class Route(attrs.AttrsInstance): 46 | method: str 47 | uri: URI 48 | api_version: int = 10 49 | 50 | def build(self, query_params: Dict | None = None, **kwargs) -> Route: 51 | url = self.uri.url_string 52 | if query_params is not None: 53 | if '?' in url: 54 | separator = '&' 55 | else: 56 | separator = '?' 57 | 58 | query_string = urlencode(query_params) 59 | url += f"{separator}{query_string}" 60 | 61 | return Route( 62 | method=self.method, 63 | uri=URI(url.format(**kwargs), auto_format=False) 64 | ) 65 | 66 | 67 | GET: Final[str] = "GET" 68 | DELETE: Final[str] = "DELETE" 69 | PATCH: Final[str] = "PATCH" 70 | PUT: Final[str] = "PUT" 71 | POST: Final[str] = "POST" 72 | 73 | _ROUTE_FIELDS = attrs.fields(Route) 74 | 75 | CDN_AVATARS: Final[str] = "https://cdn.discordapp.com/avatars/{0}/{1}.{2}" 76 | 77 | 78 | class Gateway: 79 | OPTIONS: str = "?v={}&encoding=json&compress=zlib-stream".format(_ROUTE_FIELDS.api_version.default) 80 | DISCORD_MAIN_API_URL: Final[str] = "https://discord.com/api/v{}".format(_ROUTE_FIELDS.api_version.default) 81 | DISCORD_WS_URL: Final[Route] = Route( 82 | "GET", URI( 83 | "wss://gateway.discord.gg/{}".format(OPTIONS), 84 | auto_format=False 85 | ) 86 | ) 87 | GATEWAY_BOT: Final[Route] = Route("GET", URI("/gateway/bot")) 88 | 89 | 90 | class Message: 91 | GET_MESSAGE: Final[Route] = Route(GET, URI("/channels/{channel_id}/messages/{message_id}")) 92 | CREATE_MESSAGE: Final[Route] = Route(POST, URI("/channels/{channel_id}/messages")) 93 | EDIT_MESSAGE: Final[Route] = Route(PATCH, URI("/channels/{channel_id}/messages/{message_id}")) 94 | DELETE_MESSAGE: Final[Route] = Route(DELETE, URI("/channels/{channel_id}/messages/{message_id}")) 95 | CREATE_REACTION: Final[Route] = Route(PUT, URI("/channels/{channel_id}/messages/{message_id}/reactions/{emoji}/@me")) 96 | GET_GUILD_EMOJI: Final[Route] = Route(GET, URI("/guilds/{guild_id}/emojis/{emoji_id}")) 97 | DELETE_ALL_REACTIONS: Final[Route] = Route(DELETE, URI("/channels/{channel_id}/messages/{message_id}/reactions")) 98 | DELETE_ALL_REACTION_FOR_EMOJI: Final[Route] = Route( 99 | DELETE, URI("/channels/{channel_id}/messages/{message_id}/reactions/{emoji}") 100 | ) 101 | GET_POLL_ANSWER_VOTES: Final[Route] = Route( 102 | GET, URI("/channels/{channel_id}/polls/{message_id}/answers/{answer_id}") 103 | ) 104 | POST_END_POLL: Final[Route] = Route( 105 | POST, URI("/channels/{channel_id}/polls/{message_id}/expire") 106 | ) 107 | 108 | 109 | class WebHook: 110 | CREATE_WEBHOOK: Final[Route] = Route(POST, URI("/channels/{channel_id}/webhooks")) 111 | GET_CHANNEL_WEBHOOKS: Final[Route] = Route(GET, URI("/channels/{channel_id}/webhooks")) 112 | CREATE_FOLLOWUP_MESSAGE: Final[Route] = Route(POST, URI("/webhooks/{application_id}/{interaction_token}")) 113 | 114 | 115 | class Guild: 116 | GET_GUILD: Final[Route] = Route(GET, URI("/guilds/{guild_id}")) 117 | CREATE_GUILD: Final[Route] = Route(POST, URI("/guilds")) 118 | DELETE_GUILD: Final[Route] = Route(DELETE, URI("/guilds/{guild_id}")) 119 | CREATE_GUILD_BAN: Final[Route] = Route(PUT, URI("/guilds/{guild_id}/bans/{user_id}")) 120 | GET_INVITE: Final[Route] = Route(GET, URI("/invites/{invite_code}")) 121 | DELETE_INVITE: Final[Route] = Route(DELETE, URI("/invites/{invite_code}")) 122 | GET_GUILD_INVITES: Final[Route] = Route(GET, URI("/guilds/{guild_id}/invites")) 123 | GET_GUILD_ROLES: Final[Route] = Route(GET, URI("/guilds/{guild_id}/roles")) 124 | CREATE_GUILD_ROLES: Final[Route] = Route(POST, URI("/guilds/{guild_id}/roles")) 125 | DELETE_GUILD_ROLE: Final[Route] = Route(DELETE, URI("/guilds/{guild_id}/roles/{role_id}")) 126 | GET_GUILD_MEMBERS: Final[Route] = Route(GET, URI("/guilds/{guild_id}/members")) 127 | GET_GUILD_MEMBER: Final[Route] = Route(GET, URI("/guilds/{guild_id}/members/{user_id}")) 128 | MODIFY_GUILD_MEMBER: Final[Route] = Route(PATCH, URI("/guilds/{guild_id}/members/{user_id}")) 129 | REMOVE_GUILD_MEMBER: Final[Route] = Route(DELETE, URI("/guilds/{guild_id}/members/{user_id}")) 130 | ADD_GUILD_MEMBER_ROLE: Final[Route] = Route(PUT, URI("/guilds/{guild_id}/members/{user_id}/roles/{role_id}")) 131 | 132 | 133 | class Interaction: 134 | CREATE_INTERACTION_RESPONSE: Final[Route] = Route( 135 | POST, URI("/interactions/{interaction_id}/{interaction_token}/callback") 136 | ) 137 | GET_ORIGINAL_INTERACTION_RESPONSE: Final[Route] = Route( 138 | GET, URI("/webhooks/{application_id}/{interaction_token}/messages/@original") 139 | ) 140 | EDIT_ORIGINAL_INTERACTION_RESPONSE: Final[Route] = Route( 141 | PATCH, URI("/webhooks/{application_id}/{interaction_token}/messages/@original") 142 | ) 143 | DELETE_ORIGINAL_INTERACTION_RESPONSE: Final[Route] = Route( 144 | DELETE, URI("/webhooks/{application_id}/{interaction_token}/messages/@original") 145 | ) 146 | CREATE_APPLICATION_COMMAND: Final[Route] = Route("POST", URI("/applications/{application_id}/commands")) 147 | CREATE_GUILD_APPLICATION_COMMAND: Final[Route] = Route( 148 | POST, URI("/applications/{application_id}/guilds/{guild_id}/commands") 149 | ) 150 | GET_GLOBAL_APPLICATION_COMMANDS: Final[Route] = Route( 151 | GET, URI("/applications/{application_id}/commands") 152 | ) 153 | GET_GUILD_APPLICATION_COMMANDS: Final[Route] = Route( 154 | GET, URI("/applications/{application_id}/guilds/{guild_id}/commands") 155 | ) 156 | DELETE_GLOBAL_APPLICATION_COMMAND: Final[Route] = Route( 157 | DELETE, URI("/applications/{application_id}/commands/{command_id}") 158 | ) 159 | DELETE_GUILD_APPLICATION_COMMAND: Final[Route] = Route( 160 | DELETE, URI("/applications/{application_id}/guilds/{guild_id}/commands/{command_id}") 161 | ) 162 | BULK_OVERWRITE_GLOBAL_APPLICATION_COMMAND: Final[Route] = Route( 163 | PUT, URI("/applications/{application_id}/commands") 164 | ) 165 | BULK_OVERWRITE_GUILD_APPLICATION_COMMAND: Final[Route] = Route( 166 | PUT, URI("/applications/{application_id}/guilds/{guild_id}/commands") 167 | ) 168 | 169 | 170 | class Channel: 171 | GET_CHANNEL_MESSAGES: Final[Route] = Route(GET, URI("/channels/{channel_id}/messages")) 172 | 173 | 174 | class User: 175 | GET_CURRENT_USER: Final[Route] = Route(GET, URI("/users/@me")) 176 | GET_USER: Final[Route] = Route(GET, URI("/users/{user_id}")) 177 | --------------------------------------------------------------------------------