satori-python-adapter-milky 0.4.2__tar.gz → 0.5.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 (18) hide show
  1. {satori_python_adapter_milky-0.4.2 → satori_python_adapter_milky-0.5.0}/.mina/adapter_milky.toml +2 -2
  2. {satori_python_adapter_milky-0.4.2 → satori_python_adapter_milky-0.5.0}/PKG-INFO +2 -2
  3. {satori_python_adapter_milky-0.4.2 → satori_python_adapter_milky-0.5.0}/pyproject.toml +2 -2
  4. {satori_python_adapter_milky-0.4.2 → satori_python_adapter_milky-0.5.0}/src/satori/adapters/milky/__init__.py +2 -2
  5. {satori_python_adapter_milky-0.4.2 → satori_python_adapter_milky-0.5.0}/src/satori/adapters/milky/base.py +34 -15
  6. {satori_python_adapter_milky-0.4.2 → satori_python_adapter_milky-0.5.0}/src/satori/adapters/milky/events/base.py +1 -1
  7. {satori_python_adapter_milky-0.4.2 → satori_python_adapter_milky-0.5.0}/src/satori/adapters/milky/main.py +5 -10
  8. {satori_python_adapter_milky-0.4.2 → satori_python_adapter_milky-0.5.0}/src/satori/adapters/milky/message.py +25 -7
  9. {satori_python_adapter_milky-0.4.2 → satori_python_adapter_milky-0.5.0}/src/satori/adapters/milky/sse.py +5 -10
  10. {satori_python_adapter_milky-0.4.2 → satori_python_adapter_milky-0.5.0}/src/satori/adapters/milky/webhook.py +17 -13
  11. {satori_python_adapter_milky-0.4.2 → satori_python_adapter_milky-0.5.0}/LICENSE +0 -0
  12. {satori_python_adapter_milky-0.4.2 → satori_python_adapter_milky-0.5.0}/README.md +0 -0
  13. {satori_python_adapter_milky-0.4.2 → satori_python_adapter_milky-0.5.0}/src/satori/adapters/milky/api.py +0 -0
  14. {satori_python_adapter_milky-0.4.2 → satori_python_adapter_milky-0.5.0}/src/satori/adapters/milky/events/__init__.py +0 -0
  15. {satori_python_adapter_milky-0.4.2 → satori_python_adapter_milky-0.5.0}/src/satori/adapters/milky/events/group.py +0 -0
  16. {satori_python_adapter_milky-0.4.2 → satori_python_adapter_milky-0.5.0}/src/satori/adapters/milky/events/message.py +0 -0
  17. {satori_python_adapter_milky-0.4.2 → satori_python_adapter_milky-0.5.0}/src/satori/adapters/milky/events/request.py +0 -0
  18. {satori_python_adapter_milky-0.4.2 → satori_python_adapter_milky-0.5.0}/src/satori/adapters/milky/utils.py +0 -0
@@ -1,9 +1,9 @@
1
1
  includes = ["src/satori/adapters/milky"]
2
- raw-dependencies = ["satori-python-server<1.4.0,>= 1.3.3"]
2
+ raw-dependencies = ["satori-python-server<1.4.0,>= 1.3.7"]
3
3
 
4
4
  [project]
5
5
  name = "satori-python-adapter-milky"
6
- version = "0.4.2"
6
+ version = "0.5.0"
7
7
  authors = [
8
8
  {name = "RF-Tar-Railt", email = "rf_tar_railt@qq.com"}
9
9
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: satori-python-adapter-milky
3
- Version: 0.4.2
3
+ Version: 0.5.0
4
4
  Summary: Satori Protocol SDK for python, adapter for Milky
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,7 +17,7 @@ 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: <4.0,>=3.10
20
- Requires-Dist: satori-python-server<1.4.0,>=1.3.3
20
+ Requires-Dist: satori-python-server<1.4.0,>=1.3.7
21
21
  Description-Content-Type: text/markdown
22
22
 
23
23
  # satori-python
@@ -1,11 +1,11 @@
1
1
  [project]
2
2
  name = "satori-python-adapter-milky"
3
- version = "0.4.2"
3
+ version = "0.5.0"
4
4
  authors = [
5
5
  { name = "RF-Tar-Railt", email = "rf_tar_railt@qq.com" },
6
6
  ]
7
7
  dependencies = [
8
- "satori-python-server<1.4.0,>= 1.3.3",
8
+ "satori-python-server<1.4.0,>= 1.3.7",
9
9
  ]
10
10
  description = "Satori Protocol SDK for python, adapter for Milky"
11
11
  readme = "README.md"
@@ -1,5 +1,5 @@
1
+ from .base import MilkyBaseConfig as MilkyBaseConfig
1
2
  from .main import MilkyWebsocketAdapter as MilkyWebsocketAdapter
2
3
  from .sse import MilkySSEAdapter as MilkySSEAdapter
3
4
  from .webhook import MilkyWebhookAdapter as MilkyWebhookAdapter
4
-
5
- __all__ = ["MilkyWebsocketAdapter", "MilkySSEAdapter", "MilkyWebhookAdapter"]
5
+ from .webhook import MilkyWebhookConfig as MilkyWebhookConfig
@@ -1,7 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from abc import ABC
4
+ from dataclasses import dataclass, field
4
5
  from datetime import datetime
6
+ from typing_extensions import TypeVar
5
7
 
6
8
  import aiohttp
7
9
  from launart.status import Phase
@@ -23,24 +25,37 @@ from .utils import decode_login_user
23
25
  DEFAULT_FEATURES = ["guild.plain", "reaction"]
24
26
 
25
27
 
26
- class MilkyBaseAdapter(BaseAdapter, ABC):
28
+ @dataclass
29
+ class MilkyBaseConfig:
30
+ endpoint: str
31
+ """连接 Milky 协议端的路径"""
32
+ token: str | None = None
33
+ """Milky 协议的访问令牌, 默认为空"""
34
+ token_in_query: bool = False
35
+ """是否将 token 放在查询参数中, 默认为 False"""
36
+ headers: dict[str, str] = field(default_factory=dict)
37
+ """连接时使用的自定义请求头, 默认为空字典"""
38
+
39
+
40
+ TMilkyBaseConfig = TypeVar("TMilkyBaseConfig", bound=MilkyBaseConfig, default=MilkyBaseConfig)
41
+
42
+
43
+ class MilkyBaseAdapter(BaseAdapter[TMilkyBaseConfig], ABC):
27
44
  """Base adapter for Milky protocol with common functionality."""
28
45
 
29
46
  session: aiohttp.ClientSession | None
30
47
 
31
48
  def __init__(
32
49
  self,
33
- endpoint: str | URL,
34
- *,
35
- token: str | None = None,
36
- headers: dict[str, str] | None = None,
50
+ config: TMilkyBaseConfig,
37
51
  ):
38
52
  super().__init__()
39
- self.base_url = URL(str(endpoint))
53
+ self.config = config
54
+ self.base_url = URL(self.config.endpoint)
40
55
  base_path = self.base_url.path.rstrip("/")
41
56
  self.api_base = self.base_url.with_path(f"{base_path}/api")
42
- self.token = token
43
- self.headers = headers.copy() if headers else {}
57
+ self.token = config.token
58
+ self.headers = config.headers.copy()
44
59
  self.session = None
45
60
  self.logins: dict[str, Login] = {}
46
61
  self.features: list[str] = list(DEFAULT_FEATURES)
@@ -110,18 +125,22 @@ class MilkyBaseAdapter(BaseAdapter, ABC):
110
125
  return
111
126
  login = self.logins[self_id]
112
127
  handler = event_handlers.get(event_type)
128
+ event = None
113
129
  if handler:
114
- event = await handler(login, self, payload)
115
- else:
130
+ try:
131
+ event = await handler(login, self, payload)
132
+ except Exception as e:
133
+ logger.exception(f"failed to handle event {event_type}: {e!r}")
134
+ event = None
135
+ if not event:
116
136
  event = Event(
117
137
  EventType.INTERNAL,
118
138
  datetime.fromtimestamp(payload.get("time", datetime.now().timestamp())),
119
139
  login,
120
140
  )
121
- if event:
122
- event._type = event_type
123
- event._data = payload.get("data", {})
124
- await self.server.post(event)
141
+ event._type = event_type
142
+ event._data = payload.get("data", {})
143
+ await self.server.post(event)
125
144
 
126
145
  async def refresh_login(self):
127
146
  try:
@@ -151,4 +170,4 @@ class MilkyBaseAdapter(BaseAdapter, ABC):
151
170
  return self.logins[self_id]
152
171
 
153
172
 
154
- __all__ = ["MilkyBaseAdapter", "DEFAULT_FEATURES"]
173
+ __all__ = ["MilkyBaseAdapter", "DEFAULT_FEATURES", "MilkyBaseConfig"]
@@ -34,5 +34,5 @@ async def bot_offline(login, net, raw):
34
34
  user=login.user,
35
35
  features=login.features.copy(),
36
36
  ),
37
- message=MessageObject("", raw["data"]["reason"])
37
+ message=MessageObject("", raw["data"]["reason"]),
38
38
  )
@@ -5,11 +5,10 @@ import asyncio
5
5
  import aiohttp
6
6
  from launart import Launart, any_completed
7
7
  from loguru import logger
8
- from yarl import URL
9
8
 
10
9
  from satori.utils import decode
11
10
 
12
- from .base import MilkyBaseAdapter
11
+ from .base import MilkyBaseAdapter, MilkyBaseConfig
13
12
 
14
13
 
15
14
  class MilkyWebsocketAdapter(MilkyBaseAdapter):
@@ -18,18 +17,14 @@ class MilkyWebsocketAdapter(MilkyBaseAdapter):
18
17
 
19
18
  def __init__(
20
19
  self,
21
- endpoint: str | URL,
22
- *,
23
- token: str | None = None,
24
- token_in_query: bool = False,
25
- headers: dict[str, str] | None = None,
20
+ config: MilkyBaseConfig,
26
21
  ):
27
- super().__init__(endpoint, token=token, headers=headers)
22
+ super().__init__(config)
28
23
  base_path = self.base_url.path.rstrip("/")
29
24
  ws_scheme = "wss" if self.base_url.scheme == "https" else "ws"
30
25
  self.event_url = self.base_url.with_scheme(ws_scheme).with_path(f"{base_path}/event")
31
- if token_in_query and token:
32
- self.event_url = self.event_url.update_query(access_token=token)
26
+ if config.token_in_query and self.token:
27
+ self.event_url = self.event_url.update_query(access_token=self.token)
33
28
  self.connection = None
34
29
  self.close_signal = asyncio.Event()
35
30
 
@@ -6,6 +6,8 @@ from dataclasses import dataclass, field
6
6
  from datetime import datetime
7
7
  from typing import Any, Literal
8
8
 
9
+ from loguru import logger
10
+
9
11
  from satori.element import Custom, E, Element
10
12
  from satori.model import Channel, ChannelType, Login, MessageObject, User
11
13
  from satori.parser import Element as RawElement
@@ -138,7 +140,10 @@ class MilkyMessageEncoder:
138
140
  if match := _BASE64_RE.match(uri):
139
141
  uri = f"base64://{uri[len(match.group(0)) :]}"
140
142
  self.segments.append(
141
- {"type": "image", "data": {"uri": uri, "sub_type": attrs.get("sub_type", "normal")}}
143
+ {
144
+ "type": "image",
145
+ "data": {"uri": uri, "sub_type": attrs.get("sub_type", attrs.get("subTyoe", "normal"))},
146
+ }
142
147
  )
143
148
  case "audio":
144
149
  uri = attrs.get("src") or attrs.get("url")
@@ -169,7 +174,9 @@ class MilkyMessageEncoder:
169
174
  await self._send_file(attrs)
170
175
  case "milky:light_app":
171
176
  await self.flush()
172
- self.segments.append({"type": "light_app", "data": {"json_payload": attrs["json_payload"]}})
177
+ self.segments.append(
178
+ {"type": "light_app", "data": {"json_payload": attrs.get("json_payload", attrs["jsonPayload"])}}
179
+ )
173
180
  case "author":
174
181
  self.stack[0].author.update(attrs)
175
182
  case "quote":
@@ -196,7 +203,7 @@ class MilkyMessageEncoder:
196
203
  if "id" in attrs:
197
204
  payload["id"] = int(attrs["id"])
198
205
  if "user_id" in attrs:
199
- payload["id"] = int(attrs["user_id"])
206
+ payload["id"] = int(attrs.get("user_id", attrs["userId"]))
200
207
  if "time" in attrs:
201
208
  payload["time"] = int(attrs["time"])
202
209
  self.stack[0].author.update(payload)
@@ -389,7 +396,15 @@ async def _decode_segments(net: MilkyNetwork, payload: dict, segments: Sequence[
389
396
  case "forward":
390
397
  forward_id = data.get("forward_id")
391
398
  if forward_id is not None:
392
- messages = (await net.call_api("get_forwarded_messages", {"forward_id": forward_id}))["messages"]
399
+ try:
400
+ messages = (await net.call_api("get_forwarded_messages", {"forward_id": forward_id}))[
401
+ "messages"
402
+ ]
403
+ except Exception as e:
404
+ logger.error(
405
+ f"Failed to fetch forwarded messages for forward_id {forward_id}: {payload}", exc_info=e
406
+ )
407
+ messages = []
393
408
  forward_elements = []
394
409
  for msg in messages:
395
410
  sender_name = msg.get("sender_name")
@@ -405,7 +420,10 @@ async def _decode_segments(net: MilkyNetwork, payload: dict, segments: Sequence[
405
420
  "message_seq": msg["message_seq"],
406
421
  },
407
422
  )
408
- except Exception:
423
+ except Exception as e:
424
+ logger.error(
425
+ f"Failed to fetch message {msg['message_seq']} for forward: {payload}", exc_info=e
426
+ )
409
427
  pass
410
428
  if response:
411
429
  origin = await decode_message(net, response["message"])
@@ -417,7 +435,6 @@ async def _decode_segments(net: MilkyNetwork, payload: dict, segments: Sequence[
417
435
  )
418
436
  )
419
437
  content.extend(origin.message)
420
-
421
438
  else:
422
439
  content = await _decode_segments(net, payload, msg.get("segments", []))
423
440
  content.insert(0, E.author(str(msg.get("sender_id")), sender_name, avatar))
@@ -446,7 +463,8 @@ async def _decode_reply(net: MilkyNetwork, payload: dict, data: dict, message_se
446
463
  "message_seq": message_seq,
447
464
  },
448
465
  )
449
- except Exception:
466
+ except Exception as e:
467
+ logger.error(f"Failed to fetch message {message_seq} for reply: {payload}", exc_info=e)
450
468
  return None
451
469
  if not response or "message" not in response:
452
470
  return None
@@ -5,28 +5,23 @@ import asyncio
5
5
  import aiohttp
6
6
  from launart import Launart, any_completed
7
7
  from loguru import logger
8
- from yarl import URL
9
8
 
10
9
  from satori.utils import decode
11
10
 
12
- from .base import MilkyBaseAdapter
11
+ from .base import MilkyBaseAdapter, MilkyBaseConfig
13
12
 
14
13
 
15
14
  class MilkySSEAdapter(MilkyBaseAdapter):
16
15
 
17
16
  def __init__(
18
17
  self,
19
- endpoint: str | URL,
20
- *,
21
- token: str | None = None,
22
- token_in_query: bool = False,
23
- headers: dict[str, str] | None = None,
18
+ config: MilkyBaseConfig,
24
19
  ):
25
- super().__init__(endpoint, token=token, headers=headers)
20
+ super().__init__(config)
26
21
  base_path = self.base_url.path.rstrip("/")
27
22
  self.event_url = self.base_url.with_path(f"{base_path}/event")
28
- if token_in_query and token:
29
- self.event_url = self.event_url.update_query(access_token=token)
23
+ if config.token_in_query and self.token:
24
+ self.event_url = self.event_url.update_query(access_token=self.token)
30
25
  self.close_signal = asyncio.Event()
31
26
 
32
27
  async def launch(self, manager: Launart):
@@ -1,30 +1,34 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from dataclasses import dataclass
4
+
3
5
  import aiohttp
4
6
  from launart import Launart
5
7
  from loguru import logger
6
8
  from starlette.requests import Request as StarletteRequest
7
9
  from starlette.responses import JSONResponse, Response
8
10
  from starlette.routing import Route
9
- from yarl import URL
10
11
 
11
- from .base import MilkyBaseAdapter
12
+ from .base import MilkyBaseAdapter, MilkyBaseConfig
13
+
14
+
15
+ @dataclass
16
+ class MilkyWebhookConfig(MilkyBaseConfig):
17
+ path: str = "/milky"
18
+ """Webhook 适配器于 Server 的路径, 默认为 /milky"""
19
+ self_token: str | None = None
20
+ """Webhook 适配器的自定义访问令牌, 默认为 None, 若未设置则使用 Milky 协议的 token"""
12
21
 
13
22
 
14
- class MilkyWebhookAdapter(MilkyBaseAdapter):
23
+ class MilkyWebhookAdapter(MilkyBaseAdapter[MilkyWebhookConfig]):
15
24
 
16
25
  def __init__(
17
26
  self,
18
- endpoint: str | URL,
19
- *,
20
- token: str | None = None,
21
- headers: dict[str, str] | None = None,
22
- path: str = "/milky",
23
- self_token: str | None = None,
27
+ config: MilkyWebhookConfig,
24
28
  ):
25
- super().__init__(endpoint, token=token, headers=headers)
26
- self.webhook_token = self_token if self_token is not None else token
27
- self.webhook_paths = self._normalize_webhook_paths(path)
29
+ super().__init__(config)
30
+ self.webhook_token = config.self_token if config.self_token is not None else config.token
31
+ self.webhook_paths = self._normalize_webhook_paths(config.path)
28
32
 
29
33
  async def launch(self, manager: Launart):
30
34
  async with self.stage("preparing"):
@@ -79,4 +83,4 @@ class MilkyWebhookAdapter(MilkyBaseAdapter):
79
83
  return Response(status_code=204)
80
84
 
81
85
 
82
- __all__ = ["MilkyWebhookAdapter"]
86
+ __all__ = ["MilkyWebhookAdapter", "MilkyWebhookConfig"]