satori-python 0.9.2__tar.gz → 0.11.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.
Files changed (28) hide show
  1. {satori_python-0.9.2 → satori_python-0.11.0}/PKG-INFO +7 -7
  2. {satori_python-0.9.2 → satori_python-0.11.0}/pyproject.toml +25 -17
  3. {satori_python-0.9.2 → satori_python-0.11.0}/src/satori/__init__.py +2 -1
  4. satori_python-0.11.0/src/satori/client/__init__.py +218 -0
  5. {satori_python-0.9.2 → satori_python-0.11.0}/src/satori/client/account.py +7 -4
  6. {satori_python-0.9.2 → satori_python-0.11.0}/src/satori/client/account.pyi +11 -4
  7. {satori_python-0.9.2 → satori_python-0.11.0}/src/satori/client/network/base.py +2 -4
  8. {satori_python-0.9.2 → satori_python-0.11.0}/src/satori/client/network/websocket.py +5 -3
  9. {satori_python-0.9.2 → satori_python-0.11.0}/src/satori/const.py +2 -0
  10. {satori_python-0.9.2 → satori_python-0.11.0}/src/satori/element.py +187 -172
  11. satori_python-0.11.0/src/satori/event.py +63 -0
  12. {satori_python-0.9.2 → satori_python-0.11.0}/src/satori/model.py +75 -23
  13. satori_python-0.11.0/src/satori/parser.py +406 -0
  14. {satori_python-0.9.2 → satori_python-0.11.0}/src/satori/server/__init__.py +175 -5
  15. {satori_python-0.9.2 → satori_python-0.11.0}/src/satori/server/adapter.py +7 -16
  16. satori_python-0.11.0/src/satori/server/model.py +36 -0
  17. satori_python-0.11.0/src/satori/server/route.py +240 -0
  18. satori_python-0.9.2/src/satori/client/__init__.py +0 -108
  19. satori_python-0.9.2/src/satori/parser.py +0 -124
  20. satori_python-0.9.2/src/satori/server/model.py +0 -40
  21. {satori_python-0.9.2 → satori_python-0.11.0}/LICENSE +0 -0
  22. {satori_python-0.9.2 → satori_python-0.11.0}/README.md +0 -0
  23. {satori_python-0.9.2 → satori_python-0.11.0}/src/satori/client/network/__init__.py +0 -0
  24. {satori_python-0.9.2 → satori_python-0.11.0}/src/satori/client/network/webhook.py +0 -0
  25. {satori_python-0.9.2 → satori_python-0.11.0}/src/satori/client/session.py +0 -0
  26. {satori_python-0.9.2 → satori_python-0.11.0}/src/satori/config.py +0 -0
  27. {satori_python-0.9.2 → satori_python-0.11.0}/src/satori/exception.py +0 -0
  28. {satori_python-0.9.2 → satori_python-0.11.0}/src/satori/server/conection.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: satori-python
3
- Version: 0.9.2
3
+ Version: 0.11.0
4
4
  Summary: Satori Protocol SDK for python
5
5
  Home-page: https://github.com/RF-Tar-Railt/satori-python
6
6
  Author-Email: RF-Tar-Railt <rf_tar_railt@qq.com>
@@ -17,14 +17,14 @@ Classifier: Operating System :: OS Independent
17
17
  Project-URL: Homepage, https://github.com/RF-Tar-Railt/satori-python
18
18
  Project-URL: Repository, https://github.com/RF-Tar-Railt/satori-python
19
19
  Requires-Python: >=3.9
20
- Requires-Dist: aiohttp>=3.8.6
20
+ Requires-Dist: aiohttp>=3.9.3
21
21
  Requires-Dist: loguru>=0.7.2
22
22
  Requires-Dist: launart>=0.8.2
23
- Requires-Dist: typing-extensions>=4.8.0
24
- Requires-Dist: graia-amnesia>=0.8.2
25
- Requires-Dist: starlette>=0.32.0.post1
26
- Requires-Dist: uvicorn[standard]>=0.24.0.post1
27
- Requires-Dist: yarl>=1.9.3
23
+ Requires-Dist: typing-extensions>=4.10.0
24
+ Requires-Dist: graia-amnesia>=0.9.0
25
+ Requires-Dist: starlette>=0.37.2
26
+ Requires-Dist: uvicorn[standard]>=0.28.0
27
+ Requires-Dist: yarl>=1.9.4
28
28
  Description-Content-Type: text/markdown
29
29
 
30
30
  # satori-python
@@ -6,14 +6,14 @@ authors = [
6
6
  ]
7
7
  dynamic = []
8
8
  dependencies = [
9
- "aiohttp>=3.8.6",
9
+ "aiohttp>=3.9.3",
10
10
  "loguru>=0.7.2",
11
11
  "launart>=0.8.2",
12
- "typing-extensions>=4.8.0",
13
- "graia-amnesia>=0.8.2",
14
- "starlette>=0.32.0.post1",
15
- "uvicorn[standard]>=0.24.0.post1",
16
- "yarl>=1.9.3",
12
+ "typing-extensions>=4.10.0",
13
+ "graia-amnesia>=0.9.0",
14
+ "starlette>=0.37.2",
15
+ "uvicorn[standard]>=0.28.0",
16
+ "yarl>=1.9.4",
17
17
  ]
18
18
  requires-python = ">=3.9"
19
19
  readme = "README.md"
@@ -28,7 +28,7 @@ classifiers = [
28
28
  "Programming Language :: Python :: 3.12",
29
29
  "Operating System :: OS Independent",
30
30
  ]
31
- version = "0.9.2"
31
+ version = "0.11.0"
32
32
 
33
33
  [project.license]
34
34
  text = "MIT"
@@ -39,19 +39,19 @@ repository = "https://github.com/RF-Tar-Railt/satori-python"
39
39
 
40
40
  [build-system]
41
41
  requires = [
42
- "mina-build>=0.5.1",
42
+ "mina-build<0.6,>=0.5.1",
43
43
  ]
44
44
  build-backend = "mina.backend"
45
45
 
46
46
  [tool.pdm.dev-dependencies]
47
47
  dev = [
48
- "isort>=5.12.0",
49
- "black>=23.11.0",
50
- "ruff>=0.1.5",
51
- "pre-commit>=3.5.0",
48
+ "isort>=5.13.2",
49
+ "black>=24.2.0",
50
+ "ruff>=0.3.2",
51
+ "pre-commit>=3.6.2",
52
52
  "fix-future-annotations>=0.5.0",
53
- "mina-build>=0.5.2",
54
- "pdm-mina>=0.3.1",
53
+ "mina-build<0.6,>=0.5.1",
54
+ "pdm-mina>=0.3.2",
55
55
  ]
56
56
 
57
57
  [tool.pdm.build]
@@ -63,7 +63,7 @@ includes = [
63
63
  composite = [
64
64
  "isort ./src/ ./example/",
65
65
  "black ./src/ ./example/",
66
- "ruff ./src/ ./example/",
66
+ "ruff check ./src/ ./example/",
67
67
  ]
68
68
 
69
69
  [tool.pdm.version]
@@ -84,6 +84,13 @@ extra_standard_library = [
84
84
  ]
85
85
 
86
86
  [tool.ruff]
87
+ line-length = 110
88
+ target-version = "py38"
89
+ exclude = [
90
+ "exam.py",
91
+ ]
92
+
93
+ [tool.ruff.lint]
87
94
  select = [
88
95
  "E",
89
96
  "W",
@@ -100,9 +107,10 @@ ignore = [
100
107
  "C901",
101
108
  "UP037",
102
109
  ]
103
- line-length = 110
104
- target-version = "py38"
105
110
 
106
111
  [tool.pyright]
107
112
  pythonPlatform = "All"
108
113
  pythonVersion = "3.8"
114
+ typeCheckingMode = "basic"
115
+ reportShadowedImports = false
116
+ disableBytesTypePromotions = true
@@ -15,6 +15,7 @@ from .element import Image as Image
15
15
  from .element import Italic as Italic
16
16
  from .element import Link as Link
17
17
  from .element import Message as Message
18
+ from .element import Paragraph as Paragraph
18
19
  from .element import Quote as Quote
19
20
  from .element import Sharp as Sharp
20
21
  from .element import Spoiler as Spoiler
@@ -38,4 +39,4 @@ from .model import MessageObject as MessageObject
38
39
  from .model import Role as Role
39
40
  from .model import User as User
40
41
 
41
- __version__ = "0.9.2"
42
+ __version__ = "0.11.0"
@@ -0,0 +1,218 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import signal
5
+ from functools import wraps
6
+ from typing import Any, Awaitable, Callable, Iterable, Literal, TypeVar, overload
7
+
8
+ from creart import it
9
+ from launart import Launart, Service, any_completed
10
+ from loguru import logger
11
+
12
+ from satori import event
13
+ from satori.config import Config, WebhookInfo, WebsocketsInfo
14
+ from satori.const import EventType
15
+ from satori.model import Event, LoginStatus
16
+
17
+ from .account import Account as Account
18
+ from .account import ApiInfo as ApiInfo
19
+ from .network.base import BaseNetwork as BaseNetwork
20
+ from .network.webhook import WebhookNetwork
21
+ from .network.websocket import WsNetwork
22
+
23
+ TConfig = TypeVar("TConfig", bound=Config)
24
+ TE = TypeVar("TE", bound=Event, contravariant=True)
25
+
26
+ MAPPING: dict[type[Config], type[BaseNetwork]] = {
27
+ WebhookInfo: WebhookNetwork,
28
+ WebsocketsInfo: WsNetwork,
29
+ }
30
+
31
+
32
+ class App(Service):
33
+ id = "satori-python.client"
34
+ required: set[str] = set()
35
+ stages: set[str] = {"preparing", "blocking", "cleanup"}
36
+
37
+ accounts: dict[str, Account]
38
+ connections: list[BaseNetwork]
39
+ event_callbacks: list[Callable[[Account, Event], Awaitable[Any]]]
40
+ lifecycle_callbacks: list[Callable[[Account, LoginStatus], Awaitable[Any]]]
41
+
42
+ @classmethod
43
+ def register_config(cls, tc: type[TConfig], tn: type[BaseNetwork[TConfig]]):
44
+ MAPPING[tc] = tn
45
+
46
+ def __init__(self, *configs: Config):
47
+ self.accounts = {}
48
+ self.connections = []
49
+ self.event_callbacks = []
50
+ self.lifecycle_callbacks = []
51
+ super().__init__()
52
+ for config in configs:
53
+ self.apply(config)
54
+
55
+ def apply(self, config: Config):
56
+ try:
57
+ connection = MAPPING[config.__class__](self, config)
58
+ except KeyError:
59
+ raise TypeError(f"Unknown config type: {config}")
60
+ self.connections.append(connection)
61
+
62
+ def get_account(self, self_id: str) -> Account:
63
+ return self.accounts[self_id]
64
+
65
+ def register(self, callback: Callable[[Account, Event], Awaitable[Any]]):
66
+ self.event_callbacks.append(callback)
67
+
68
+ @overload
69
+ def register_on(self, event_type: Literal[EventType.FRIEND_REQUEST]) -> Callable[
70
+ [Callable[[Account, event.UserEvent], Awaitable[Any]]],
71
+ Callable[[Account, event.UserEvent], Awaitable[Any]],
72
+ ]: ...
73
+
74
+ @overload
75
+ def register_on(
76
+ self,
77
+ event_type: Literal[
78
+ EventType.GUILD_ADDED, EventType.GUILD_REMOVED, EventType.GUILD_REQUEST, EventType.GUILD_UPDATED
79
+ ],
80
+ ) -> Callable[
81
+ [Callable[[Account, event.GuildEvent], Awaitable[Any]]],
82
+ Callable[[Account, event.GuildEvent], Awaitable[Any]],
83
+ ]: ...
84
+
85
+ @overload
86
+ def register_on(
87
+ self,
88
+ event_type: Literal[
89
+ EventType.GUILD_MEMBER_ADDED,
90
+ EventType.GUILD_MEMBER_REMOVED,
91
+ EventType.GUILD_MEMBER_UPDATED,
92
+ EventType.GUILD_MEMBER_REQUEST,
93
+ ],
94
+ ) -> Callable[
95
+ [Callable[[Account, event.GuildMemberEvent], Awaitable[Any]]],
96
+ Callable[[Account, event.GuildMemberEvent], Awaitable[Any]],
97
+ ]: ...
98
+
99
+ @overload
100
+ def register_on(
101
+ self,
102
+ event_type: Literal[
103
+ EventType.GUILD_ROLE_CREATED, EventType.GUILD_ROLE_DELETED, EventType.GUILD_ROLE_UPDATED
104
+ ],
105
+ ) -> Callable[
106
+ [Callable[[Account, event.GuildRoleEvent], Awaitable[Any]]],
107
+ Callable[[Account, event.GuildRoleEvent], Awaitable[Any]],
108
+ ]: ...
109
+
110
+ @overload
111
+ def register_on(
112
+ self, event_type: Literal[EventType.LOGIN_ADDED, EventType.LOGIN_REMOVED, EventType.LOGIN_UPDATED]
113
+ ) -> Callable[
114
+ [Callable[[Account, event.LoginEvent], Awaitable[Any]]],
115
+ Callable[[Account, event.LoginEvent], Awaitable[Any]],
116
+ ]: ...
117
+
118
+ @overload
119
+ def register_on(
120
+ self,
121
+ event_type: Literal[EventType.MESSAGE_CREATED, EventType.MESSAGE_DELETED, EventType.MESSAGE_UPDATED],
122
+ ) -> Callable[
123
+ [Callable[[Account, event.MessageEvent], Awaitable[Any]]],
124
+ Callable[[Account, event.MessageEvent], Awaitable[Any]],
125
+ ]: ...
126
+
127
+ @overload
128
+ def register_on(
129
+ self, event_type: Literal[EventType.REACTION_ADDED, EventType.REACTION_REMOVED]
130
+ ) -> Callable[
131
+ [Callable[[Account, event.ReactionEvent], Awaitable[Any]]],
132
+ Callable[[Account, event.ReactionEvent], Awaitable[Any]],
133
+ ]: ...
134
+
135
+ @overload
136
+ def register_on(self, event_type: Literal[EventType.INTERACTION_BUTTON]) -> Callable[
137
+ [Callable[[Account, event.ButtonInteractionEvent], Awaitable[Any]]],
138
+ Callable[[Account, event.ButtonInteractionEvent], Awaitable[Any]],
139
+ ]: ...
140
+
141
+ @overload
142
+ def register_on(self, event_type: Literal[EventType.INTERACTION_COMMAND]) -> Callable[
143
+ [Callable[[Account, event.ArgvInteractionEvent | event.MessageEvent], Awaitable[Any]]],
144
+ Callable[[Account, event.ArgvInteractionEvent | event.MessageEvent], Awaitable[Any]],
145
+ ]: ...
146
+
147
+ @overload
148
+ def register_on(self, event_type: Literal[EventType.INTERNAL]) -> Callable[
149
+ [Callable[[Account, event.InternalEvent], Awaitable[Any]]],
150
+ Callable[[Account, event.InternalEvent], Awaitable[Any]],
151
+ ]: ...
152
+
153
+ def register_on(self, event_type: EventType):
154
+ def decorator(
155
+ func: Callable[[Account, Any], Awaitable[Any]], /
156
+ ) -> Callable[[Account, Any], Awaitable[Any]]:
157
+ @wraps(func)
158
+ async def wrapper(account: Account, event: Event) -> Any:
159
+ if event.type == event_type.value:
160
+ return await func(account, event)
161
+
162
+ self.register(wrapper)
163
+ return wrapper
164
+
165
+ return decorator
166
+
167
+ def lifecycle(self, callback: Callable[[Account, LoginStatus], Awaitable[Any]]):
168
+ self.lifecycle_callbacks.append(callback)
169
+
170
+ async def account_update(self, account: Account, state: LoginStatus):
171
+ if self.lifecycle_callbacks:
172
+ await asyncio.gather(*(callback(account, state) for callback in self.lifecycle_callbacks))
173
+
174
+ async def post(self, event: Event):
175
+ if not self.event_callbacks:
176
+ return
177
+ identity = f"{event.platform}/{event.self_id}"
178
+ if identity not in self.accounts:
179
+ logger.warning(f"Received event for unknown account: {event}")
180
+ return
181
+ account = self.accounts[identity]
182
+ await asyncio.gather(*(callback(account, event) for callback in self.event_callbacks))
183
+
184
+ async def launch(self, manager: Launart):
185
+ for conn in self.connections:
186
+ manager.add_component(conn)
187
+
188
+ async with self.stage("preparing"):
189
+ ...
190
+
191
+ async with self.stage("blocking"):
192
+ await any_completed(
193
+ manager.status.wait_for_sigexit(),
194
+ *(conn.status.wait_for("blocking-completed") for conn in self.connections),
195
+ )
196
+
197
+ async with self.stage("cleanup"):
198
+ for account in self.accounts.values():
199
+ await self.account_update(account, LoginStatus.OFFLINE)
200
+ self.accounts.clear()
201
+
202
+ def run(
203
+ self,
204
+ manager: Launart | None = None,
205
+ *,
206
+ loop: asyncio.AbstractEventLoop | None = None,
207
+ stop_signal: Iterable[signal.Signals] = (signal.SIGINT,),
208
+ ):
209
+ if manager is None:
210
+ manager = it(Launart)
211
+ manager.add_component(self)
212
+ manager.launch_blocking(loop=loop, stop_signal=stop_signal)
213
+
214
+ async def run_async(self, manager: Launart | None = None):
215
+ if manager is None:
216
+ manager = it(Launart)
217
+ manager.add_component(self)
218
+ await manager.launch()
@@ -2,11 +2,14 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  from dataclasses import dataclass
5
+ from typing import TypeVar
5
6
 
6
7
  from yarl import URL
7
8
 
8
9
  from .session import Session
9
10
 
11
+ TS = TypeVar("TS", bound="Session")
12
+
10
13
 
11
14
  @dataclass
12
15
  class ApiInfo:
@@ -25,15 +28,15 @@ class ApiInfo:
25
28
 
26
29
 
27
30
  class Account:
28
- def __init__(self, platform: str, self_id: str, config: ApiInfo):
31
+ def __init__(self, platform: str, self_id: str, config: ApiInfo, session_cls: type[Session] = Session):
29
32
  self.platform = platform
30
33
  self.self_id = self_id
31
34
  self.config = config
32
- self.session = Session(self) # type: ignore
35
+ self.session = session_cls(self) # type: ignore
33
36
  self.connected = asyncio.Event()
34
37
 
35
- def custom(self, config: ApiInfo | None = None, **kwargs):
36
- return Account(self.platform, self.self_id, config or ApiInfo(**kwargs)).session
38
+ def custom(self, config: ApiInfo | None = None, session_cls: type[TS] = Session, **kwargs) -> TS:
39
+ return Account(self.platform, self.self_id, config or ApiInfo(**kwargs), session_cls).session # type: ignore
37
40
 
38
41
  @property
39
42
  def identity(self):
@@ -1,5 +1,5 @@
1
1
  import asyncio
2
- from typing import Any, Iterable, Protocol, overload
2
+ from typing import Any, Iterable, Protocol, TypeVar, overload
3
3
 
4
4
  from yarl import URL
5
5
 
@@ -8,6 +8,8 @@ from satori.model import Channel, Event, Guild, Login, Member, MessageObject, Pa
8
8
 
9
9
  from .session import Session
10
10
 
11
+ TS = TypeVar("TS", bound="Session")
12
+
11
13
  class Api(Protocol):
12
14
  token: str | None = None
13
15
 
@@ -26,13 +28,15 @@ class Account:
26
28
  session: Session
27
29
  connected: asyncio.Event
28
30
 
29
- def __init__(self, platform: str, self_id: str, config: Api): ...
31
+ def __init__(self, platform: str, self_id: str, config: Api, session_cls: type[Session] = Session): ...
30
32
  @property
31
33
  def identity(self) -> str: ...
32
34
  @overload
33
- def custom(self, config: Api) -> Session: ...
35
+ def custom(self, config: Api, session_cls: type[TS] = Session) -> TS: ...
34
36
  @overload
35
- def custom(self, *, host: str, port: int, token: str | None = None) -> Session: ...
37
+ def custom(
38
+ self, *, session_cls: type[TS] = Session, host: str, port: int, token: str | None = None
39
+ ) -> TS: ...
36
40
  async def send(
37
41
  self,
38
42
  event: Event,
@@ -51,6 +55,7 @@ class Account:
51
55
  message: 要发送的消息
52
56
  """
53
57
  ...
58
+
54
59
  async def send_private_message(
55
60
  self,
56
61
  user_id: str,
@@ -63,6 +68,7 @@ class Account:
63
68
  message: 要发送的消息
64
69
  """
65
70
  ...
71
+
66
72
  async def update_message(
67
73
  self,
68
74
  channel_id: str,
@@ -77,6 +83,7 @@ class Account:
77
83
  message: 要更新的消息
78
84
  """
79
85
  ...
86
+
80
87
  async def message_create(
81
88
  self,
82
89
  *,
@@ -27,12 +27,10 @@ class BaseNetwork(Generic[TConfig], Service):
27
27
  self.close_signal = asyncio.Event()
28
28
  self.sequence = -1
29
29
 
30
- async def wait_for_available(self):
31
- ...
30
+ async def wait_for_available(self): ...
32
31
 
33
32
  @property
34
- def alive(self) -> bool:
35
- ...
33
+ def alive(self) -> bool: ...
36
34
 
37
35
  async def connection_closed(self):
38
36
  self.close_signal.set()
@@ -101,9 +101,11 @@ class WsNetwork(BaseNetwork[WebsocketsInfo]):
101
101
  else:
102
102
  account = Account(platform, self_id, self.config)
103
103
  logger.info(f"account registered: {account}")
104
- account.connected.set() if login[
105
- "status"
106
- ] == LoginStatus.ONLINE else account.connected.clear()
104
+ (
105
+ account.connected.set()
106
+ if login["status"] == LoginStatus.ONLINE
107
+ else account.connected.clear()
108
+ )
107
109
  self.app.accounts[identity] = account
108
110
  self.accounts[identity] = account
109
111
  await self.app.account_update(account, LoginStatus.ONLINE)
@@ -13,6 +13,7 @@ class Api(str, Enum):
13
13
  CHANNEL_CREATE = "channel.create"
14
14
  CHANNEL_UPDATE = "channel.update"
15
15
  CHANNEL_DELETE = "channel.delete"
16
+ CHANNEL_MUTE = "channel.mute"
16
17
  USER_CHANNEL_CREATE = "user.channel.create"
17
18
 
18
19
  GUILD_GET = "guild.get"
@@ -22,6 +23,7 @@ class Api(str, Enum):
22
23
  GUILD_MEMBER_LIST = "guild.member.list"
23
24
  GUILD_MEMBER_GET = "guild.member.get"
24
25
  GUILD_MEMBER_KICK = "guild.member.kick"
26
+ GUILD_MEMBER_MUTE = "guild.member.mute"
25
27
  GUILD_MEMBER_APPROVE = "guild.member.approve"
26
28
  GUILD_MEMBER_ROLE_SET = "guild.member.role.set"
27
29
  GUILD_MEMBER_ROLE_UNSET = "guild.member.role.unset"