fastapi-interactions 0.0.1__tar.gz → 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fastapi_interactions-0.0.1 → fastapi_interactions-0.1.0}/PKG-INFO +1 -1
- {fastapi_interactions-0.0.1 → fastapi_interactions-0.1.0}/fastapi_interactions/bot.py +24 -14
- {fastapi_interactions-0.0.1 → fastapi_interactions-0.1.0}/fastapi_interactions/commands.py +60 -20
- {fastapi_interactions-0.0.1 → fastapi_interactions-0.1.0}/fastapi_interactions/models.py +22 -2
- {fastapi_interactions-0.0.1 → fastapi_interactions-0.1.0}/fastapi_interactions.egg-info/PKG-INFO +1 -1
- {fastapi_interactions-0.0.1 → fastapi_interactions-0.1.0}/pyproject.toml +1 -1
- {fastapi_interactions-0.0.1 → fastapi_interactions-0.1.0}/README.md +0 -0
- {fastapi_interactions-0.0.1 → fastapi_interactions-0.1.0}/fastapi_interactions/__init__.py +0 -0
- {fastapi_interactions-0.0.1 → fastapi_interactions-0.1.0}/fastapi_interactions/context.py +0 -0
- {fastapi_interactions-0.0.1 → fastapi_interactions-0.1.0}/fastapi_interactions/middleware.py +0 -0
- {fastapi_interactions-0.0.1 → fastapi_interactions-0.1.0}/fastapi_interactions/responses.py +0 -0
- {fastapi_interactions-0.0.1 → fastapi_interactions-0.1.0}/fastapi_interactions.egg-info/SOURCES.txt +0 -0
- {fastapi_interactions-0.0.1 → fastapi_interactions-0.1.0}/fastapi_interactions.egg-info/dependency_links.txt +0 -0
- {fastapi_interactions-0.0.1 → fastapi_interactions-0.1.0}/fastapi_interactions.egg-info/requires.txt +0 -0
- {fastapi_interactions-0.0.1 → fastapi_interactions-0.1.0}/fastapi_interactions.egg-info/top_level.txt +0 -0
- {fastapi_interactions-0.0.1 → fastapi_interactions-0.1.0}/setup.cfg +0 -0
|
@@ -1,24 +1,26 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import inspect
|
|
3
|
+
import json
|
|
4
|
+
import pkgutil
|
|
5
|
+
import types
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
1
9
|
from fastapi import FastAPI, Request
|
|
2
|
-
from .
|
|
3
|
-
from
|
|
10
|
+
from fastapi.responses import JSONResponse
|
|
11
|
+
from loguru import logger
|
|
12
|
+
from pydantic import ValidationError
|
|
13
|
+
|
|
14
|
+
from .commands import Command, CommandRouter
|
|
4
15
|
from .context import Context
|
|
16
|
+
from .middleware import VerifySignatureMiddleware
|
|
5
17
|
from .models import (
|
|
6
|
-
InteractionType,
|
|
7
18
|
ApplicationCommandData,
|
|
8
19
|
Interaction,
|
|
20
|
+
InteractionType,
|
|
9
21
|
Snowflake,
|
|
10
22
|
)
|
|
11
|
-
from
|
|
12
|
-
from .commands import Command
|
|
13
|
-
from pydantic import ValidationError
|
|
14
|
-
from .commands import CommandRouter
|
|
15
|
-
import json
|
|
16
|
-
import httpx
|
|
17
|
-
import importlib
|
|
18
|
-
import pkgutil
|
|
19
|
-
from loguru import logger
|
|
20
|
-
import inspect
|
|
21
|
-
import types
|
|
23
|
+
from .responses import InteractionResponse, MessageResponse, PongResponse
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
async def call_with_options(callback: callable, ctx: Context) -> Any:
|
|
@@ -107,6 +109,7 @@ class Bot:
|
|
|
107
109
|
# print(e.errors())
|
|
108
110
|
return await MessageResponse("Unexpected error", ephemeral=True)
|
|
109
111
|
|
|
112
|
+
raise Exception("Unexpected error occurred")
|
|
110
113
|
context = Context(
|
|
111
114
|
interaction=interaction, options=application_command, http=self.http
|
|
112
115
|
)
|
|
@@ -117,6 +120,12 @@ class Bot:
|
|
|
117
120
|
|
|
118
121
|
return await MessageResponse("Unsupported interaction received", ephemeral=True)
|
|
119
122
|
|
|
123
|
+
async def add_error_handler(self, exc: Exception, func: callable):
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
async def _handle_exception(self, request: Request, exc: Exception):
|
|
127
|
+
return JSONResponse(await MessageResponse(str(exc), ephemeral=True))
|
|
128
|
+
|
|
120
129
|
def _register_routes(self) -> None:
|
|
121
130
|
"""Set up middleware and the interactions endpoint.
|
|
122
131
|
|
|
@@ -124,6 +133,7 @@ class Bot:
|
|
|
124
133
|
for the interactions endpoint. Called during initialization.
|
|
125
134
|
"""
|
|
126
135
|
self.app.add_middleware(VerifySignatureMiddleware, public_key=self.public_key)
|
|
136
|
+
self.app.add_exception_handler(Exception, self._handle_exception)
|
|
127
137
|
self.app.add_api_route(
|
|
128
138
|
self.interactions_path, endpoint=self.process_interactions, methods=["POST"]
|
|
129
139
|
)
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
from dataclasses import dataclass, field
|
|
2
|
-
from .models import Snowflake, ApplicationCommandOptionType
|
|
3
|
-
from typing import Optional
|
|
4
1
|
import sys
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
|
|
4
|
+
from .models import (
|
|
5
|
+
ApplicationCommandOptionType,
|
|
6
|
+
IntegrationType,
|
|
7
|
+
InteractionContext,
|
|
8
|
+
Snowflake,
|
|
9
|
+
)
|
|
5
10
|
|
|
6
11
|
OptionType = ApplicationCommandOptionType
|
|
7
12
|
|
|
@@ -28,27 +33,57 @@ class CommandMeta:
|
|
|
28
33
|
description: str
|
|
29
34
|
options: list[CommandOption] = field(default_factory=list)
|
|
30
35
|
type: int = 1
|
|
36
|
+
integration_types: IntegrationType | None = None
|
|
37
|
+
contexts: InteractionContext | None = None
|
|
31
38
|
|
|
32
39
|
def as_payload(self):
|
|
33
|
-
|
|
40
|
+
payload = {
|
|
34
41
|
"name": self.name,
|
|
35
42
|
"description": self.description,
|
|
36
43
|
"type": self.type,
|
|
37
44
|
"options": [option.as_payload() for option in self.options],
|
|
38
45
|
}
|
|
46
|
+
if self.integration_types is not None:
|
|
47
|
+
payload["integration_types"] = self.integration_types.to_api()
|
|
48
|
+
if self.contexts is not None:
|
|
49
|
+
payload["contexts"] = self.contexts.to_api()
|
|
50
|
+
return payload
|
|
39
51
|
|
|
40
52
|
|
|
41
53
|
@dataclass(slots=True)
|
|
42
54
|
class Command:
|
|
43
55
|
callback: callable
|
|
44
56
|
meta: CommandMeta
|
|
45
|
-
guild_id:
|
|
57
|
+
guild_id: Snowflake | None = None
|
|
46
58
|
|
|
47
59
|
|
|
48
60
|
class CommandRouter:
|
|
49
|
-
def __init__(
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
name: str | None = None,
|
|
64
|
+
guild_id: Snowflake | None = None,
|
|
65
|
+
*,
|
|
66
|
+
integration_types: IntegrationType | None = None,
|
|
67
|
+
contexts: InteractionContext | None = None,
|
|
68
|
+
):
|
|
50
69
|
self.name = name or self.__infer_name()
|
|
51
|
-
self.guild_id
|
|
70
|
+
self.guild_id = guild_id
|
|
71
|
+
|
|
72
|
+
if guild_id is not None and (
|
|
73
|
+
integration_types is not None or contexts is not None
|
|
74
|
+
):
|
|
75
|
+
raise ValueError(
|
|
76
|
+
f"Router {self.name} is guild scoped. Cannt bind integration/interaction types"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if integration_types is not None and not integration_types:
|
|
80
|
+
raise ValueError("integration types cannot be empty;")
|
|
81
|
+
|
|
82
|
+
if contexts is not None and not contexts:
|
|
83
|
+
raise ValueError("contexts cannot be empty")
|
|
84
|
+
|
|
85
|
+
self.integration_types = integration_types
|
|
86
|
+
self.contexts = contexts
|
|
52
87
|
self.commands: dict[str, Command] = {}
|
|
53
88
|
|
|
54
89
|
def __infer_name(self) -> str:
|
|
@@ -86,6 +121,8 @@ class CommandRouter:
|
|
|
86
121
|
)
|
|
87
122
|
meta.name = name
|
|
88
123
|
meta.description = description
|
|
124
|
+
meta.integration_types = self.integration_types
|
|
125
|
+
meta.contexts = self.contexts
|
|
89
126
|
self.commands[name] = Command(
|
|
90
127
|
callback=func, meta=meta, guild_id=self.guild_id
|
|
91
128
|
)
|
|
@@ -97,10 +134,12 @@ class CommandRouter:
|
|
|
97
134
|
|
|
98
135
|
class Option:
|
|
99
136
|
@staticmethod
|
|
100
|
-
def __create_operation_decorator(
|
|
137
|
+
def __create_operation_decorator(
|
|
138
|
+
name: str, description: str, option_type: OptionType, required: bool = True
|
|
139
|
+
):
|
|
101
140
|
def decorator(func):
|
|
102
141
|
meta = getattr(
|
|
103
|
-
func,
|
|
142
|
+
func, "_command_meta", CommandMeta(name="", description="", type=1)
|
|
104
143
|
)
|
|
105
144
|
|
|
106
145
|
meta.options.insert(
|
|
@@ -109,11 +148,12 @@ class Option:
|
|
|
109
148
|
name=name,
|
|
110
149
|
description=description,
|
|
111
150
|
type=option_type,
|
|
112
|
-
required=required
|
|
113
|
-
)
|
|
151
|
+
required=required,
|
|
152
|
+
),
|
|
114
153
|
)
|
|
115
154
|
func._command_meta = meta
|
|
116
155
|
return func
|
|
156
|
+
|
|
117
157
|
return decorator
|
|
118
158
|
|
|
119
159
|
@staticmethod
|
|
@@ -143,7 +183,7 @@ class Option:
|
|
|
143
183
|
name=name,
|
|
144
184
|
description=description,
|
|
145
185
|
option_type=OptionType.STRING,
|
|
146
|
-
required=required
|
|
186
|
+
required=required,
|
|
147
187
|
)
|
|
148
188
|
|
|
149
189
|
@staticmethod
|
|
@@ -173,7 +213,7 @@ class Option:
|
|
|
173
213
|
name=name,
|
|
174
214
|
description=description,
|
|
175
215
|
option_type=OptionType.INTEGER,
|
|
176
|
-
required=required
|
|
216
|
+
required=required,
|
|
177
217
|
)
|
|
178
218
|
|
|
179
219
|
@staticmethod
|
|
@@ -204,7 +244,7 @@ class Option:
|
|
|
204
244
|
name=name,
|
|
205
245
|
description=description,
|
|
206
246
|
option_type=OptionType.USER,
|
|
207
|
-
required=required
|
|
247
|
+
required=required,
|
|
208
248
|
)
|
|
209
249
|
|
|
210
250
|
@staticmethod
|
|
@@ -235,7 +275,7 @@ class Option:
|
|
|
235
275
|
name=name,
|
|
236
276
|
description=description,
|
|
237
277
|
option_type=OptionType.CHANNEL,
|
|
238
|
-
required=required
|
|
278
|
+
required=required,
|
|
239
279
|
)
|
|
240
280
|
|
|
241
281
|
@staticmethod
|
|
@@ -266,7 +306,7 @@ class Option:
|
|
|
266
306
|
name=name,
|
|
267
307
|
description=description,
|
|
268
308
|
option_type=OptionType.ROLE,
|
|
269
|
-
required=required
|
|
309
|
+
required=required,
|
|
270
310
|
)
|
|
271
311
|
|
|
272
312
|
@staticmethod
|
|
@@ -296,7 +336,7 @@ class Option:
|
|
|
296
336
|
name=name,
|
|
297
337
|
description=description,
|
|
298
338
|
option_type=OptionType.MENTIONABLE,
|
|
299
|
-
required=required
|
|
339
|
+
required=required,
|
|
300
340
|
)
|
|
301
341
|
|
|
302
342
|
@staticmethod
|
|
@@ -331,7 +371,7 @@ class Option:
|
|
|
331
371
|
name=name,
|
|
332
372
|
description=description,
|
|
333
373
|
option_type=OptionType.BOOLEAN,
|
|
334
|
-
required=required
|
|
374
|
+
required=required,
|
|
335
375
|
)
|
|
336
376
|
|
|
337
377
|
@staticmethod
|
|
@@ -362,5 +402,5 @@ class Option:
|
|
|
362
402
|
name=name,
|
|
363
403
|
description=description,
|
|
364
404
|
option_type=OptionType.NUMBER,
|
|
365
|
-
required=required
|
|
366
|
-
)
|
|
405
|
+
required=required,
|
|
406
|
+
)
|
|
@@ -1,10 +1,30 @@
|
|
|
1
|
-
from pydantic import BaseModel, ConfigDict, Field
|
|
2
|
-
from typing import Optional, Any
|
|
3
1
|
from enum import IntEnum, IntFlag
|
|
2
|
+
from typing import Any, Optional
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
4
5
|
|
|
5
6
|
Snowflake = str
|
|
6
7
|
|
|
7
8
|
|
|
9
|
+
class _ApiFlag(IntFlag):
|
|
10
|
+
def to_api(self) -> list[int]:
|
|
11
|
+
"""Bit positions set in this flag, e.g. GUILD|PRIVATE_CHANNEL -> [0, 2]."""
|
|
12
|
+
return [i for i in range(self.bit_length()) if self & (1 << i)]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class IntegrationType(_ApiFlag):
|
|
16
|
+
GUILD_INSTALL = 1 << 0 # API VALUE 0
|
|
17
|
+
USER_INSTALL = 1 << 1 # API VALUE 1
|
|
18
|
+
ALL = GUILD_INSTALL | USER_INSTALL
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class InteractionContext(_ApiFlag):
|
|
22
|
+
GUILD = 1 << 0 # API VALUE 0
|
|
23
|
+
BOT_DM = 1 << 1 # API VALUE 1
|
|
24
|
+
PRIVATE_CHANNEL = 1 << 2 # VALUE VALUE 2
|
|
25
|
+
ALL = GUILD | BOT_DM | PRIVATE_CHANNEL
|
|
26
|
+
|
|
27
|
+
|
|
8
28
|
class InteractionType(IntEnum):
|
|
9
29
|
PING = 1
|
|
10
30
|
APPLICATION_COMMAND = 2
|
|
@@ -4,7 +4,7 @@ build-backend = 'setuptools.build_meta'
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = 'fastapi-interactions'
|
|
7
|
-
version = '0.0
|
|
7
|
+
version = '0.1.0'
|
|
8
8
|
description='A lightweight framework for discord interactions over http built on FastAPI'
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_interactions-0.0.1 → fastapi_interactions-0.1.0}/fastapi_interactions/middleware.py
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_interactions-0.0.1 → fastapi_interactions-0.1.0}/fastapi_interactions.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_interactions-0.0.1 → fastapi_interactions-0.1.0}/fastapi_interactions.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|