nonebot-adapter-qq 1.3.4__tar.gz → 1.4.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.
- {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/PKG-INFO +3 -3
- {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/adapter.py +12 -10
- {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/bot.py +21 -19
- nonebot_adapter_qq-1.4.0/nonebot/adapters/qq/compat.py +23 -0
- {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/config.py +10 -2
- {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/event.py +23 -16
- {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/message.py +11 -9
- {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/models/common.py +44 -6
- {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/models/guild.py +23 -8
- {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/models/payload.py +43 -15
- {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/models/qq.py +5 -31
- {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/utils.py +4 -4
- {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/pyproject.toml +10 -8
- nonebot_adapter_qq-1.3.4/nonebot/adapters/qq/models/_transformer.py +0 -106
- {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/LICENSE +0 -0
- {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/README.md +0 -0
- {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/__init__.py +0 -0
- {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/exception.py +0 -0
- {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/models/__init__.py +0 -0
- {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/permission.py +0 -0
- {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/store.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: nonebot-adapter-qq
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.4.0
|
4
4
|
Summary: QQ adapter for nonebot2
|
5
5
|
Home-page: https://github.com/nonebot/adapter-qq
|
6
6
|
License: MIT
|
@@ -19,8 +19,8 @@ Classifier: Programming Language :: Python :: 3.9
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.10
|
20
20
|
Classifier: Programming Language :: Python :: 3.11
|
21
21
|
Classifier: Programming Language :: Python :: 3.12
|
22
|
-
Requires-Dist: nonebot2 (>=2.
|
23
|
-
Requires-Dist: pydantic (>=1.
|
22
|
+
Requires-Dist: nonebot2 (>=2.2.0,<3.0.0)
|
23
|
+
Requires-Dist: pydantic (>=1.10.0,<3.0.0,!=2.5.0,!=2.5.1)
|
24
24
|
Requires-Dist: typing-extensions (>=4.4.0,<5.0.0)
|
25
25
|
Requires-Dist: yarl (>=1.9.0,<2.0.0)
|
26
26
|
Project-URL: Documentation, https://github.com/nonebot/adapter-qq#readme
|
@@ -1,11 +1,12 @@
|
|
1
1
|
import sys
|
2
|
+
import json
|
2
3
|
import asyncio
|
3
4
|
from typing_extensions import override
|
4
5
|
from typing import Any, List, Tuple, Literal, Optional
|
5
6
|
|
6
|
-
from pydantic import parse_raw_as
|
7
7
|
from nonebot.utils import escape_tag
|
8
8
|
from nonebot.exception import WebSocketClosed
|
9
|
+
from nonebot.compat import PYDANTIC_V2, type_validate_python
|
9
10
|
from nonebot.drivers import (
|
10
11
|
URL,
|
11
12
|
Driver,
|
@@ -376,36 +377,37 @@ class Adapter(BaseAdapter):
|
|
376
377
|
)
|
377
378
|
|
378
379
|
def get_auth_base(self) -> URL:
|
379
|
-
return URL(self.qq_config.qq_auth_base)
|
380
|
+
return URL(str(self.qq_config.qq_auth_base))
|
380
381
|
|
381
382
|
def get_api_base(self) -> URL:
|
382
383
|
if self.qq_config.qq_is_sandbox:
|
383
|
-
return URL(self.qq_config.qq_sandbox_api_base)
|
384
|
+
return URL(str(self.qq_config.qq_sandbox_api_base))
|
384
385
|
else:
|
385
|
-
return URL(self.qq_config.qq_api_base)
|
386
|
+
return URL(str(self.qq_config.qq_api_base))
|
386
387
|
|
387
388
|
@staticmethod
|
388
389
|
async def receive_payload(bot: Bot, ws: WebSocket) -> Payload:
|
389
|
-
payload =
|
390
|
+
payload = type_validate_python(PayloadType, json.loads(await ws.receive()))
|
390
391
|
if isinstance(payload, Dispatch):
|
391
392
|
bot.on_dispatch(payload)
|
392
393
|
return payload
|
393
394
|
|
394
395
|
@staticmethod
|
395
396
|
def payload_to_json(payload: Payload) -> str:
|
396
|
-
|
397
|
-
payload.
|
398
|
-
|
397
|
+
if PYDANTIC_V2:
|
398
|
+
return payload.model_dump_json(by_alias=True)
|
399
|
+
|
400
|
+
return payload.json(by_alias=True)
|
399
401
|
|
400
402
|
@staticmethod
|
401
403
|
def payload_to_event(payload: Dispatch) -> Event:
|
402
404
|
EventClass = EVENT_CLASSES.get(payload.type, None)
|
403
405
|
if EventClass is None:
|
404
406
|
log("WARNING", f"Unknown payload type: {payload.type}")
|
405
|
-
event = Event
|
407
|
+
event = type_validate_python(Event, payload.data)
|
406
408
|
event.__type__ = payload.type # type: ignore
|
407
409
|
return event
|
408
|
-
return EventClass
|
410
|
+
return type_validate_python(EventClass, payload.data)
|
409
411
|
|
410
412
|
@override
|
411
413
|
async def _call_api(self, bot: Bot, api: str, **data: Any) -> Any:
|
@@ -1296,12 +1296,14 @@ class Bot(BaseBot):
|
|
1296
1296
|
{
|
1297
1297
|
"name": name,
|
1298
1298
|
"description": description,
|
1299
|
-
"start_timestamp":
|
1300
|
-
|
1301
|
-
|
1302
|
-
|
1303
|
-
|
1304
|
-
|
1299
|
+
"start_timestamp": (
|
1300
|
+
str(start_timestamp)
|
1301
|
+
if start_timestamp is not None
|
1302
|
+
else None
|
1303
|
+
),
|
1304
|
+
"end_timestamp": (
|
1305
|
+
str(end_timestamp) if end_timestamp is not None else None
|
1306
|
+
),
|
1305
1307
|
"jump_channel_id": jump_channel_id,
|
1306
1308
|
"remind_type": str(remind_type),
|
1307
1309
|
}
|
@@ -1339,16 +1341,18 @@ class Bot(BaseBot):
|
|
1339
1341
|
{
|
1340
1342
|
"name": name,
|
1341
1343
|
"description": description,
|
1342
|
-
"start_timestamp":
|
1343
|
-
|
1344
|
-
|
1345
|
-
|
1346
|
-
|
1347
|
-
|
1344
|
+
"start_timestamp": (
|
1345
|
+
str(start_timestamp)
|
1346
|
+
if start_timestamp is not None
|
1347
|
+
else None
|
1348
|
+
),
|
1349
|
+
"end_timestamp": (
|
1350
|
+
str(end_timestamp) if end_timestamp is not None else None
|
1351
|
+
),
|
1348
1352
|
"jump_channel_id": jump_channel_id,
|
1349
|
-
"remind_type":
|
1350
|
-
|
1351
|
-
|
1353
|
+
"remind_type": (
|
1354
|
+
str(remind_type) if remind_type is not None else None
|
1355
|
+
),
|
1352
1356
|
}
|
1353
1357
|
)
|
1354
1358
|
},
|
@@ -1489,8 +1493,7 @@ class Bot(BaseBot):
|
|
1489
1493
|
title: str,
|
1490
1494
|
content: str,
|
1491
1495
|
format: Literal[1, 2, 3],
|
1492
|
-
) -> PutThreadReturn:
|
1493
|
-
...
|
1496
|
+
) -> PutThreadReturn: ...
|
1494
1497
|
|
1495
1498
|
@overload
|
1496
1499
|
async def put_thread(
|
@@ -1500,8 +1503,7 @@ class Bot(BaseBot):
|
|
1500
1503
|
title: str,
|
1501
1504
|
content: RichText,
|
1502
1505
|
format: Literal[4],
|
1503
|
-
) -> PutThreadReturn:
|
1504
|
-
...
|
1506
|
+
) -> PutThreadReturn: ...
|
1505
1507
|
|
1506
1508
|
@API
|
1507
1509
|
async def put_thread(
|
@@ -0,0 +1,23 @@
|
|
1
|
+
from typing import Literal, overload
|
2
|
+
|
3
|
+
from nonebot.compat import PYDANTIC_V2
|
4
|
+
|
5
|
+
__all__ = ("model_validator", "field_validator")
|
6
|
+
|
7
|
+
if PYDANTIC_V2:
|
8
|
+
from pydantic import field_validator as field_validator
|
9
|
+
from pydantic import model_validator as model_validator
|
10
|
+
else:
|
11
|
+
from pydantic import validator, root_validator
|
12
|
+
|
13
|
+
@overload
|
14
|
+
def model_validator(*, mode: Literal["before"]): ...
|
15
|
+
|
16
|
+
@overload
|
17
|
+
def model_validator(*, mode: Literal["after"]): ...
|
18
|
+
|
19
|
+
def model_validator(*, mode: Literal["before", "after"]):
|
20
|
+
return root_validator(pre=mode == "before", allow_reuse=True)
|
21
|
+
|
22
|
+
def field_validator(__field, *fields, mode: Literal["before", "after"] = "after"):
|
23
|
+
return validator(__field, *fields, pre=mode == "before", allow_reuse=True)
|
@@ -1,6 +1,7 @@
|
|
1
1
|
from typing import List, Tuple, Optional
|
2
2
|
|
3
|
-
from pydantic import
|
3
|
+
from pydantic import Field, HttpUrl, BaseModel
|
4
|
+
from nonebot.compat import PYDANTIC_V2, ConfigDict
|
4
5
|
|
5
6
|
|
6
7
|
class Intents(BaseModel):
|
@@ -18,6 +19,13 @@ class Intents(BaseModel):
|
|
18
19
|
audio_action: bool = False
|
19
20
|
at_messages: bool = True
|
20
21
|
|
22
|
+
if PYDANTIC_V2:
|
23
|
+
model_config: ConfigDict = ConfigDict(extra="forbid")
|
24
|
+
else:
|
25
|
+
|
26
|
+
class Config:
|
27
|
+
extra = "forbid"
|
28
|
+
|
21
29
|
def to_int(self):
|
22
30
|
return (
|
23
31
|
self.guilds << 0
|
@@ -54,7 +62,7 @@ class BotInfo(BaseModel):
|
|
54
62
|
return self.intent.is_group_enabled
|
55
63
|
|
56
64
|
|
57
|
-
class Config(BaseModel
|
65
|
+
class Config(BaseModel):
|
58
66
|
qq_is_sandbox: bool = False
|
59
67
|
qq_api_base: HttpUrl = Field("https://api.sgroup.qq.com/")
|
60
68
|
qq_sandbox_api_base: HttpUrl = Field("https://sandbox.api.sgroup.qq.com")
|
@@ -1,7 +1,7 @@
|
|
1
1
|
from enum import Enum
|
2
2
|
from datetime import datetime
|
3
3
|
from typing_extensions import override
|
4
|
-
from typing import Dict, Type, Tuple, TypeVar, Optional
|
4
|
+
from typing import Dict, Type, Tuple, TypeVar, Optional, cast
|
5
5
|
|
6
6
|
from nonebot.utils import escape_tag
|
7
7
|
|
@@ -420,19 +420,30 @@ class InteractionCreateEvent(NoticeEvent, ButtonInteraction):
|
|
420
420
|
|
421
421
|
@override
|
422
422
|
def get_user_id(self) -> str:
|
423
|
-
|
423
|
+
if self.chat_type == 0:
|
424
|
+
return cast(str, self.data.resolved.user_id)
|
425
|
+
elif self.chat_type == 1:
|
426
|
+
return cast(str, self.group_member_openid)
|
427
|
+
elif self.chat_type == 2:
|
428
|
+
return cast(str, self.user_openid)
|
429
|
+
raise ValueError(f"Unknown chat_type: {self.chat_type}")
|
424
430
|
|
425
431
|
@override
|
426
432
|
def get_session_id(self) -> str:
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
433
|
+
if self.chat_type == 0:
|
434
|
+
return (
|
435
|
+
f"guild_{self.guild_id}_channel_{self.channel_id}"
|
436
|
+
f"_{self.data.resolved.user_id}"
|
437
|
+
)
|
438
|
+
elif self.chat_type == 1:
|
439
|
+
return f"group_{self.group_openid}_{self.group_member_openid}"
|
440
|
+
elif self.chat_type == 2:
|
441
|
+
return f"friend_{self.user_openid}"
|
442
|
+
raise ValueError(f"Unknown chat_type: {self.chat_type}")
|
431
443
|
|
432
444
|
|
433
445
|
# Message Audit Event
|
434
|
-
class MessageAuditEvent(NoticeEvent, MessageAudited):
|
435
|
-
...
|
446
|
+
class MessageAuditEvent(NoticeEvent, MessageAudited): ...
|
436
447
|
|
437
448
|
|
438
449
|
@register_event_class
|
@@ -467,8 +478,7 @@ class MessageReactionRemoveEvent(MessageReactionEvent):
|
|
467
478
|
|
468
479
|
|
469
480
|
# Audio Event
|
470
|
-
class AudioEvent(NoticeEvent, AudioAction):
|
471
|
-
...
|
481
|
+
class AudioEvent(NoticeEvent, AudioAction): ...
|
472
482
|
|
473
483
|
|
474
484
|
@register_event_class
|
@@ -502,8 +512,7 @@ class ForumEvent(NoticeEvent, ForumSourceInfo):
|
|
502
512
|
return f"guild_{self.guild_id}_channel_{self.channel_id}_{self.author_id}"
|
503
513
|
|
504
514
|
|
505
|
-
class ForumThreadEvent(ForumEvent, Thread[RichText]):
|
506
|
-
...
|
515
|
+
class ForumThreadEvent(ForumEvent, Thread[RichText]): ...
|
507
516
|
|
508
517
|
|
509
518
|
@register_event_class
|
@@ -521,8 +530,7 @@ class ForumThreadDeleteEvent(ForumThreadEvent):
|
|
521
530
|
__type__ = EventType.FORUM_THREAD_DELETE
|
522
531
|
|
523
532
|
|
524
|
-
class ForumPostEvent(ForumEvent, Post):
|
525
|
-
...
|
533
|
+
class ForumPostEvent(ForumEvent, Post): ...
|
526
534
|
|
527
535
|
|
528
536
|
@register_event_class
|
@@ -535,8 +543,7 @@ class ForumPostDeleteEvent(ForumPostEvent):
|
|
535
543
|
__type__ = EventType.FORUM_POST_DELETE
|
536
544
|
|
537
545
|
|
538
|
-
class ForumReplyEvent(ForumEvent, Reply):
|
539
|
-
...
|
546
|
+
class ForumReplyEvent(ForumEvent, Reply): ...
|
540
547
|
|
541
548
|
|
542
549
|
@register_event_class
|
@@ -108,9 +108,11 @@ class MessageSegment(BaseMessageSegment["Message"]):
|
|
108
108
|
return Markdown(
|
109
109
|
"markdown",
|
110
110
|
data={
|
111
|
-
"markdown":
|
112
|
-
|
113
|
-
|
111
|
+
"markdown": (
|
112
|
+
MessageMarkdown(content=markdown)
|
113
|
+
if isinstance(markdown, str)
|
114
|
+
else markdown
|
115
|
+
)
|
114
116
|
},
|
115
117
|
)
|
116
118
|
|
@@ -120,13 +122,13 @@ class MessageSegment(BaseMessageSegment["Message"]):
|
|
120
122
|
|
121
123
|
@overload
|
122
124
|
@staticmethod
|
123
|
-
def reference(reference: MessageReference) -> "Reference":
|
124
|
-
...
|
125
|
+
def reference(reference: MessageReference) -> "Reference": ...
|
125
126
|
|
126
127
|
@overload
|
127
128
|
@staticmethod
|
128
|
-
def reference(
|
129
|
-
|
129
|
+
def reference(
|
130
|
+
reference: str, ignore_error: Optional[bool] = None
|
131
|
+
) -> "Reference": ...
|
130
132
|
|
131
133
|
@staticmethod
|
132
134
|
def reference(
|
@@ -381,7 +383,7 @@ class Message(BaseMessage[MessageSegment]):
|
|
381
383
|
|
382
384
|
@classmethod
|
383
385
|
def from_guild_message(cls, message: GuildMessage) -> Self:
|
384
|
-
msg =
|
386
|
+
msg = cls()
|
385
387
|
if message.mention_everyone:
|
386
388
|
msg.append(MessageSegment.mention_everyone())
|
387
389
|
if message.content:
|
@@ -398,7 +400,7 @@ class Message(BaseMessage[MessageSegment]):
|
|
398
400
|
|
399
401
|
@classmethod
|
400
402
|
def from_qq_message(cls, message: QQMessage) -> Self:
|
401
|
-
msg =
|
403
|
+
msg = cls()
|
402
404
|
if message.content:
|
403
405
|
msg.extend(Message(message.content))
|
404
406
|
if message.attachments:
|
@@ -1,14 +1,17 @@
|
|
1
|
-
from typing import List, Optional
|
2
1
|
from urllib.parse import urlparse
|
2
|
+
from typing import List, Literal, Optional
|
3
3
|
|
4
|
-
from pydantic import BaseModel
|
4
|
+
from pydantic import BaseModel
|
5
|
+
|
6
|
+
from nonebot.adapters.qq.compat import field_validator
|
5
7
|
|
6
8
|
|
7
9
|
# Message Attachment
|
8
10
|
class MessageAttachment(BaseModel):
|
9
11
|
url: str
|
10
12
|
|
11
|
-
@
|
13
|
+
@field_validator("url", mode="after")
|
14
|
+
@classmethod
|
12
15
|
def check_url(cls, v: str):
|
13
16
|
if v and not urlparse(v).hostname:
|
14
17
|
return f"https://{v}"
|
@@ -82,10 +85,13 @@ class Permission(BaseModel):
|
|
82
85
|
class Action(BaseModel):
|
83
86
|
type: Optional[int] = None
|
84
87
|
permission: Optional[Permission] = None
|
85
|
-
click_limit: Optional[int] = None
|
86
|
-
unsupport_tips: Optional[str] = None
|
87
88
|
data: Optional[str] = None
|
88
|
-
|
89
|
+
reply: Optional[bool] = None
|
90
|
+
enter: Optional[bool] = None
|
91
|
+
anchor: Optional[int] = None
|
92
|
+
unsupport_tips: Optional[str] = None
|
93
|
+
click_limit: Optional[int] = None # deprecated
|
94
|
+
at_bot_show_channel_list: Optional[bool] = None # deprecated
|
89
95
|
|
90
96
|
|
91
97
|
class RenderData(BaseModel):
|
@@ -113,6 +119,35 @@ class MessageKeyboard(BaseModel):
|
|
113
119
|
content: Optional[InlineKeyboard] = None
|
114
120
|
|
115
121
|
|
122
|
+
# Interaction Event
|
123
|
+
class ButtonInteractionContent(BaseModel):
|
124
|
+
user_id: Optional[str] = None
|
125
|
+
message_id: Optional[str] = None
|
126
|
+
feature_id: Optional[str] = None
|
127
|
+
button_id: Optional[str] = None
|
128
|
+
button_data: Optional[str] = None
|
129
|
+
|
130
|
+
|
131
|
+
class ButtonInteractionData(BaseModel):
|
132
|
+
resolved: ButtonInteractionContent
|
133
|
+
|
134
|
+
|
135
|
+
class ButtonInteraction(BaseModel):
|
136
|
+
id: str
|
137
|
+
type: Literal[11, 12]
|
138
|
+
version: int
|
139
|
+
timestamp: str
|
140
|
+
scene: str
|
141
|
+
chat_type: int
|
142
|
+
guild_id: Optional[str] = None
|
143
|
+
channel_id: Optional[str] = None
|
144
|
+
user_openid: Optional[str] = None
|
145
|
+
group_openid: Optional[str] = None
|
146
|
+
group_member_openid: Optional[str] = None
|
147
|
+
application_id: str
|
148
|
+
data: ButtonInteractionData
|
149
|
+
|
150
|
+
|
116
151
|
__all__ = [
|
117
152
|
"MessageAttachment",
|
118
153
|
"MessageEmbedThumbnail",
|
@@ -132,4 +167,7 @@ __all__ = [
|
|
132
167
|
"InlineKeyboardRow",
|
133
168
|
"InlineKeyboard",
|
134
169
|
"MessageKeyboard",
|
170
|
+
"ButtonInteractionContent",
|
171
|
+
"ButtonInteractionData",
|
172
|
+
"ButtonInteraction",
|
135
173
|
]
|
@@ -1,12 +1,20 @@
|
|
1
|
+
import json
|
1
2
|
from enum import IntEnum
|
2
3
|
from datetime import datetime
|
3
4
|
from typing import List, Union, Generic, TypeVar, Optional
|
4
5
|
|
5
|
-
from pydantic
|
6
|
-
from
|
6
|
+
from pydantic import BaseModel
|
7
|
+
from nonebot.compat import PYDANTIC_V2, model_fields, type_validate_python
|
8
|
+
|
9
|
+
from nonebot.adapters.qq.compat import field_validator, model_validator
|
7
10
|
|
8
11
|
from .common import MessageArk, MessageEmbed, MessageReference, MessageAttachment
|
9
12
|
|
13
|
+
if PYDANTIC_V2:
|
14
|
+
GenericModel = BaseModel
|
15
|
+
else:
|
16
|
+
from pydantic.generics import GenericModel
|
17
|
+
|
10
18
|
T = TypeVar("T")
|
11
19
|
|
12
20
|
|
@@ -329,7 +337,8 @@ class Elem(BaseModel):
|
|
329
337
|
video: Optional[VideoElem] = None
|
330
338
|
url: Optional[URLElem] = None
|
331
339
|
|
332
|
-
@
|
340
|
+
@model_validator(mode="before")
|
341
|
+
@classmethod
|
333
342
|
def infer_type(cls, values: dict):
|
334
343
|
if values.get("type") is not None:
|
335
344
|
return values
|
@@ -373,10 +382,11 @@ class ThreadObjectInfo(BaseModel):
|
|
373
382
|
content: RichText
|
374
383
|
date_time: datetime
|
375
384
|
|
376
|
-
@
|
385
|
+
@field_validator("content", model="before")
|
386
|
+
@classmethod
|
377
387
|
def parse_content(cls, v):
|
378
388
|
if isinstance(v, str):
|
379
|
-
return RichText.
|
389
|
+
return type_validate_python(RichText, json.loads(v))
|
380
390
|
return v
|
381
391
|
|
382
392
|
|
@@ -387,10 +397,15 @@ class ThreadInfo(ThreadObjectInfo, GenericModel, Generic[_T_Title]):
|
|
387
397
|
# 事件推送拿到的title实际上是RichText的JSON字符串,而API调用返回的title是普通文本
|
388
398
|
title: _T_Title
|
389
399
|
|
390
|
-
@
|
400
|
+
@field_validator("title", mode="before")
|
401
|
+
@classmethod
|
391
402
|
def parse_title(cls, v):
|
392
|
-
if
|
393
|
-
|
403
|
+
if (
|
404
|
+
isinstance(v, str)
|
405
|
+
and next(f for f in model_fields(cls) if f.name == "title").annotation
|
406
|
+
is RichText
|
407
|
+
):
|
408
|
+
return type_validate_python(RichText, json.loads(v))
|
394
409
|
return v
|
395
410
|
|
396
411
|
|
@@ -2,9 +2,8 @@ from enum import IntEnum
|
|
2
2
|
from typing import Tuple, Union
|
3
3
|
from typing_extensions import Literal, Annotated
|
4
4
|
|
5
|
-
from pydantic import
|
6
|
-
|
7
|
-
from ._transformer import BoolToIntTransformer, AliasExportTransformer
|
5
|
+
from pydantic import Field, BaseModel
|
6
|
+
from nonebot.compat import PYDANTIC_V2, ConfigDict
|
8
7
|
|
9
8
|
PAYLOAD_FIELD_ALIASES = {"opcode": "op", "data": "d", "sequence": "s", "type": "t"}
|
10
9
|
|
@@ -21,14 +20,22 @@ class Opcode(IntEnum):
|
|
21
20
|
HTTP_CALLBACK_ACK = 12
|
22
21
|
|
23
22
|
|
24
|
-
class Payload(
|
25
|
-
|
26
|
-
|
27
|
-
|
23
|
+
class Payload(BaseModel):
|
24
|
+
if PYDANTIC_V2:
|
25
|
+
model_config: ConfigDict = ConfigDict(
|
26
|
+
extra="allow",
|
27
|
+
populate_by_name=True,
|
28
|
+
alias_generator=lambda x: PAYLOAD_FIELD_ALIASES.get(x, x),
|
29
|
+
)
|
30
|
+
else:
|
31
|
+
|
32
|
+
class Config:
|
33
|
+
extra = "allow"
|
34
|
+
allow_population_by_field_name = True
|
28
35
|
|
29
|
-
|
30
|
-
|
31
|
-
|
36
|
+
@classmethod
|
37
|
+
def alias_generator(cls, string: str) -> str:
|
38
|
+
return PAYLOAD_FIELD_ALIASES.get(string, string)
|
32
39
|
|
33
40
|
|
34
41
|
class Dispatch(Payload):
|
@@ -43,23 +50,37 @@ class Heartbeat(Payload):
|
|
43
50
|
data: int
|
44
51
|
|
45
52
|
|
46
|
-
class IdentifyData(BaseModel
|
53
|
+
class IdentifyData(BaseModel):
|
47
54
|
token: str
|
48
55
|
intents: int
|
49
56
|
shard: Tuple[int, int]
|
50
57
|
properties: dict
|
51
58
|
|
59
|
+
if PYDANTIC_V2:
|
60
|
+
model_config: ConfigDict = ConfigDict(extra="allow")
|
61
|
+
else:
|
62
|
+
|
63
|
+
class Config:
|
64
|
+
extra = "allow"
|
65
|
+
|
52
66
|
|
53
67
|
class Identify(Payload):
|
54
68
|
opcode: Literal[Opcode.IDENTIFY] = Field(Opcode.IDENTIFY)
|
55
69
|
data: IdentifyData
|
56
70
|
|
57
71
|
|
58
|
-
class ResumeData(BaseModel
|
72
|
+
class ResumeData(BaseModel):
|
59
73
|
token: str
|
60
74
|
session_id: str
|
61
75
|
seq: int
|
62
76
|
|
77
|
+
if PYDANTIC_V2:
|
78
|
+
model_config: ConfigDict = ConfigDict(extra="allow")
|
79
|
+
else:
|
80
|
+
|
81
|
+
class Config:
|
82
|
+
extra = "allow"
|
83
|
+
|
63
84
|
|
64
85
|
class Resume(Payload):
|
65
86
|
opcode: Literal[Opcode.RESUME] = Field(Opcode.RESUME)
|
@@ -74,9 +95,16 @@ class InvalidSession(Payload):
|
|
74
95
|
opcode: Literal[Opcode.INVALID_SESSION] = Field(Opcode.INVALID_SESSION)
|
75
96
|
|
76
97
|
|
77
|
-
class HelloData(BaseModel
|
98
|
+
class HelloData(BaseModel):
|
78
99
|
heartbeat_interval: int
|
79
100
|
|
101
|
+
if PYDANTIC_V2:
|
102
|
+
model_config: ConfigDict = ConfigDict(extra="allow")
|
103
|
+
else:
|
104
|
+
|
105
|
+
class Config:
|
106
|
+
extra = "allow"
|
107
|
+
|
80
108
|
|
81
109
|
class Hello(Payload):
|
82
110
|
opcode: Literal[Opcode.HELLO] = Field(Opcode.HELLO)
|
@@ -87,9 +115,9 @@ class HeartbeatAck(Payload):
|
|
87
115
|
opcode: Literal[Opcode.HEARTBEAT_ACK] = Field(Opcode.HEARTBEAT_ACK)
|
88
116
|
|
89
117
|
|
90
|
-
class HTTPCallbackAck(
|
118
|
+
class HTTPCallbackAck(Payload):
|
91
119
|
opcode: Literal[Opcode.HTTP_CALLBACK_ACK] = Field(Opcode.HTTP_CALLBACK_ACK)
|
92
|
-
data:
|
120
|
+
data: int
|
93
121
|
|
94
122
|
|
95
123
|
PayloadType = Union[
|
@@ -1,8 +1,10 @@
|
|
1
1
|
from datetime import datetime
|
2
|
+
from typing import List, Optional
|
2
3
|
from urllib.parse import urlparse
|
3
|
-
from typing import List, Literal, Optional
|
4
4
|
|
5
|
-
from pydantic import BaseModel
|
5
|
+
from pydantic import BaseModel
|
6
|
+
|
7
|
+
from nonebot.adapters.qq.compat import field_validator
|
6
8
|
|
7
9
|
|
8
10
|
class FriendAuthor(BaseModel):
|
@@ -23,7 +25,7 @@ class Attachment(BaseModel):
|
|
23
25
|
size: Optional[str] = None
|
24
26
|
url: Optional[str] = None
|
25
27
|
|
26
|
-
@
|
28
|
+
@field_validator("url", mode="after")
|
27
29
|
def check_url(cls, v: str):
|
28
30
|
if v and not urlparse(v).hostname:
|
29
31
|
return f"https://{v}"
|
@@ -73,31 +75,6 @@ class PostGroupMembersReturn(BaseModel):
|
|
73
75
|
next_index: Optional[int] = None
|
74
76
|
|
75
77
|
|
76
|
-
# Interaction Event
|
77
|
-
class ButtonInteractionContent(BaseModel):
|
78
|
-
user_id: str
|
79
|
-
message_id: str
|
80
|
-
button_id: str
|
81
|
-
button_data: str
|
82
|
-
|
83
|
-
|
84
|
-
class ButtonInteractionData(BaseModel):
|
85
|
-
resolved: ButtonInteractionContent
|
86
|
-
|
87
|
-
|
88
|
-
class ButtonInteraction(BaseModel):
|
89
|
-
id: str
|
90
|
-
type: Literal[11]
|
91
|
-
version: int
|
92
|
-
timestamp: str
|
93
|
-
chat_type: int
|
94
|
-
guild_id: Optional[str] = None
|
95
|
-
channel_id: Optional[str] = None
|
96
|
-
group_open_id: Optional[str] = None
|
97
|
-
application_id: str
|
98
|
-
data: ButtonInteractionData
|
99
|
-
|
100
|
-
|
101
78
|
__all__ = [
|
102
79
|
"FriendAuthor",
|
103
80
|
"GroupMemberAuthor",
|
@@ -110,7 +87,4 @@ __all__ = [
|
|
110
87
|
"GroupMember",
|
111
88
|
"PostGroupMembersReturn",
|
112
89
|
"PostGroupFilesReturn",
|
113
|
-
"ButtonInteractionContent",
|
114
|
-
"ButtonInteractionData",
|
115
|
-
"ButtonInteraction",
|
116
90
|
]
|
@@ -45,12 +45,12 @@ class API(Generic[B, P, R]):
|
|
45
45
|
self.name = name
|
46
46
|
|
47
47
|
@overload
|
48
|
-
def __get__(self, obj: None, objtype: Type[B]) -> "API[B, P, R]":
|
49
|
-
...
|
48
|
+
def __get__(self, obj: None, objtype: Type[B]) -> "API[B, P, R]": ...
|
50
49
|
|
51
50
|
@overload
|
52
|
-
def __get__(
|
53
|
-
|
51
|
+
def __get__(
|
52
|
+
self, obj: B, objtype: Optional[Type[B]]
|
53
|
+
) -> Callable[P, Awaitable[R]]: ...
|
54
54
|
|
55
55
|
def __get__(
|
56
56
|
self, obj: Optional[B], objtype: Optional[Type[B]] = None
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "nonebot-adapter-qq"
|
3
|
-
version = "1.
|
3
|
+
version = "1.4.0"
|
4
4
|
description = "QQ adapter for nonebot2"
|
5
5
|
authors = ["yanyongyu <yyy@nonebot.dev>"]
|
6
6
|
license = "MIT"
|
@@ -21,14 +21,14 @@ packages = [{ include = "nonebot" }]
|
|
21
21
|
[tool.poetry.dependencies]
|
22
22
|
python = "^3.8"
|
23
23
|
yarl = "^1.9.0"
|
24
|
-
|
25
|
-
nonebot2 = "^2.1.0"
|
24
|
+
nonebot2 = "^2.2.0"
|
26
25
|
typing-extensions = ">=4.4.0, <5.0.0"
|
26
|
+
pydantic = ">=1.10.0,<3.0.0,!=2.5.0,!=2.5.1"
|
27
27
|
|
28
28
|
[tool.poetry.group.dev.dependencies]
|
29
|
-
ruff = "^0.
|
29
|
+
ruff = "^0.2.0"
|
30
30
|
isort = "^5.10.1"
|
31
|
-
black = "^
|
31
|
+
black = "^24.0.0"
|
32
32
|
nonemoji = "^0.1.3"
|
33
33
|
pre-commit = "^3.3.0"
|
34
34
|
|
@@ -47,15 +47,17 @@ force_sort_within_sections = true
|
|
47
47
|
extra_standard_library = ["typing_extensions"]
|
48
48
|
|
49
49
|
[tool.ruff]
|
50
|
-
select = ["E", "W", "F", "UP", "C", "T", "Q"]
|
51
|
-
ignore = ["E402", "F403", "F405", "C901", "UP037"]
|
52
|
-
|
53
50
|
line-length = 88
|
54
51
|
target-version = "py38"
|
55
52
|
|
53
|
+
[tool.ruff.lint]
|
54
|
+
select = ["E", "W", "F", "UP", "C", "T", "Q"]
|
55
|
+
ignore = ["E402", "F403", "F405", "C901", "UP037"]
|
56
|
+
|
56
57
|
[tool.pyright]
|
57
58
|
pythonPlatform = "All"
|
58
59
|
pythonVersion = "3.8"
|
60
|
+
defineConstant = { PYDANTIC_V2 = true }
|
59
61
|
|
60
62
|
[build-system]
|
61
63
|
requires = ["poetry-core>=1.0.0"]
|
@@ -1,106 +0,0 @@
|
|
1
|
-
from typing import TYPE_CHECKING, Any, Union, Optional
|
2
|
-
|
3
|
-
from pydantic import BaseModel
|
4
|
-
|
5
|
-
if TYPE_CHECKING:
|
6
|
-
from pydantic.typing import DictStrAny, MappingIntStrAny, AbstractSetIntStr
|
7
|
-
|
8
|
-
|
9
|
-
class ExcludeNoneTransformer(BaseModel):
|
10
|
-
def dict(
|
11
|
-
self,
|
12
|
-
*,
|
13
|
-
include: Union["AbstractSetIntStr", "MappingIntStrAny", None] = None,
|
14
|
-
exclude: Union["AbstractSetIntStr", "MappingIntStrAny", None] = None,
|
15
|
-
by_alias: bool = False,
|
16
|
-
skip_defaults: Optional[bool] = None,
|
17
|
-
exclude_unset: bool = False,
|
18
|
-
exclude_defaults: bool = False,
|
19
|
-
**kwargs: Any,
|
20
|
-
) -> "DictStrAny":
|
21
|
-
return super().dict(
|
22
|
-
include=include,
|
23
|
-
exclude=exclude,
|
24
|
-
by_alias=by_alias,
|
25
|
-
skip_defaults=skip_defaults,
|
26
|
-
exclude_unset=exclude_unset,
|
27
|
-
exclude_defaults=exclude_defaults,
|
28
|
-
exclude_none=True,
|
29
|
-
)
|
30
|
-
|
31
|
-
|
32
|
-
class BoolToIntTransformer(BaseModel):
|
33
|
-
def dict(
|
34
|
-
self,
|
35
|
-
*,
|
36
|
-
include: Union["AbstractSetIntStr", "MappingIntStrAny", None] = None,
|
37
|
-
exclude: Union["AbstractSetIntStr", "MappingIntStrAny", None] = None,
|
38
|
-
by_alias: bool = False,
|
39
|
-
skip_defaults: Optional[bool] = None,
|
40
|
-
exclude_unset: bool = False,
|
41
|
-
exclude_defaults: bool = False,
|
42
|
-
exclude_none: bool = False,
|
43
|
-
) -> "DictStrAny":
|
44
|
-
data = super().dict(
|
45
|
-
include=include,
|
46
|
-
exclude=exclude,
|
47
|
-
by_alias=by_alias,
|
48
|
-
skip_defaults=skip_defaults,
|
49
|
-
exclude_unset=exclude_unset,
|
50
|
-
exclude_defaults=exclude_defaults,
|
51
|
-
exclude_none=exclude_none,
|
52
|
-
)
|
53
|
-
for key, value in data.items():
|
54
|
-
if isinstance(value, bool):
|
55
|
-
data[key] = int(value)
|
56
|
-
return data
|
57
|
-
|
58
|
-
|
59
|
-
class IntToStrTransformer(BaseModel):
|
60
|
-
def dict(
|
61
|
-
self,
|
62
|
-
*,
|
63
|
-
include: Union["AbstractSetIntStr", "MappingIntStrAny", None] = None,
|
64
|
-
exclude: Union["AbstractSetIntStr", "MappingIntStrAny", None] = None,
|
65
|
-
by_alias: bool = False,
|
66
|
-
skip_defaults: Optional[bool] = None,
|
67
|
-
exclude_unset: bool = False,
|
68
|
-
exclude_defaults: bool = False,
|
69
|
-
exclude_none: bool = False,
|
70
|
-
) -> "DictStrAny":
|
71
|
-
data = super().dict(
|
72
|
-
include=include,
|
73
|
-
exclude=exclude,
|
74
|
-
by_alias=by_alias,
|
75
|
-
skip_defaults=skip_defaults,
|
76
|
-
exclude_unset=exclude_unset,
|
77
|
-
exclude_defaults=exclude_defaults,
|
78
|
-
exclude_none=exclude_none,
|
79
|
-
)
|
80
|
-
for key, value in data.items():
|
81
|
-
if isinstance(value, int):
|
82
|
-
data[key] = str(value)
|
83
|
-
return data
|
84
|
-
|
85
|
-
|
86
|
-
class AliasExportTransformer(BaseModel):
|
87
|
-
def dict(
|
88
|
-
self,
|
89
|
-
*,
|
90
|
-
include: Union["AbstractSetIntStr", "MappingIntStrAny", None] = None,
|
91
|
-
exclude: Union["AbstractSetIntStr", "MappingIntStrAny", None] = None,
|
92
|
-
skip_defaults: Optional[bool] = None,
|
93
|
-
exclude_unset: bool = False,
|
94
|
-
exclude_defaults: bool = False,
|
95
|
-
exclude_none: bool = False,
|
96
|
-
**kwargs: Any,
|
97
|
-
) -> "DictStrAny":
|
98
|
-
return super().dict(
|
99
|
-
include=include,
|
100
|
-
exclude=exclude,
|
101
|
-
by_alias=True,
|
102
|
-
skip_defaults=skip_defaults,
|
103
|
-
exclude_unset=exclude_unset,
|
104
|
-
exclude_defaults=exclude_defaults,
|
105
|
-
exclude_none=exclude_none,
|
106
|
-
)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/models/__init__.py
RENAMED
File without changes
|
File without changes
|
File without changes
|