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.
Files changed (21) hide show
  1. {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/PKG-INFO +3 -3
  2. {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/adapter.py +12 -10
  3. {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/bot.py +21 -19
  4. nonebot_adapter_qq-1.4.0/nonebot/adapters/qq/compat.py +23 -0
  5. {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/config.py +10 -2
  6. {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/event.py +23 -16
  7. {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/message.py +11 -9
  8. {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/models/common.py +44 -6
  9. {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/models/guild.py +23 -8
  10. {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/models/payload.py +43 -15
  11. {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/models/qq.py +5 -31
  12. {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/utils.py +4 -4
  13. {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/pyproject.toml +10 -8
  14. nonebot_adapter_qq-1.3.4/nonebot/adapters/qq/models/_transformer.py +0 -106
  15. {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/LICENSE +0 -0
  16. {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/README.md +0 -0
  17. {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/__init__.py +0 -0
  18. {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/exception.py +0 -0
  19. {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/models/__init__.py +0 -0
  20. {nonebot_adapter_qq-1.3.4 → nonebot_adapter_qq-1.4.0}/nonebot/adapters/qq/permission.py +0 -0
  21. {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.4
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.1.0,<3.0.0)
23
- Requires-Dist: pydantic (>=1.9.0,<2.0.0)
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 = parse_raw_as(PayloadType, await ws.receive())
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
- return payload.__config__.json_dumps(
397
- payload.dict(), default=payload.__json_encoder__
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.parse_obj(payload.data)
407
+ event = type_validate_python(Event, payload.data)
406
408
  event.__type__ = payload.type # type: ignore
407
409
  return event
408
- return EventClass.parse_obj(payload.data)
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": str(start_timestamp)
1300
- if start_timestamp is not None
1301
- else None,
1302
- "end_timestamp": str(end_timestamp)
1303
- if end_timestamp is not None
1304
- else None,
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": str(start_timestamp)
1343
- if start_timestamp is not None
1344
- else None,
1345
- "end_timestamp": str(end_timestamp)
1346
- if end_timestamp is not None
1347
- else None,
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": str(remind_type)
1350
- if remind_type is not None
1351
- else None,
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 Extra, Field, HttpUrl, BaseModel
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, extra=Extra.ignore):
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
- return self.data.resolved.user_id
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
- return (
428
- f"guild_{self.guild_id}_channel_{self.channel_id}"
429
- f"_{self.data.resolved.user_id}"
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": MessageMarkdown(content=markdown)
112
- if isinstance(markdown, str)
113
- else markdown
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(reference: str, ignore_error: Optional[bool] = None) -> "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 = Message()
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 = Message()
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, validator
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
- @validator("url", allow_reuse=True)
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
- at_bot_show_channel_list: Optional[bool] = None
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.generics import GenericModel
6
- from pydantic import BaseModel, validator, root_validator
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
- @root_validator(pre=True, allow_reuse=True)
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
- @validator("content", pre=True, allow_reuse=True)
385
+ @field_validator("content", model="before")
386
+ @classmethod
377
387
  def parse_content(cls, v):
378
388
  if isinstance(v, str):
379
- return RichText.parse_raw(v, content_type="json")
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
- @validator("title", pre=True, allow_reuse=True)
400
+ @field_validator("title", mode="before")
401
+ @classmethod
391
402
  def parse_title(cls, v):
392
- if isinstance(v, str) and cls.__fields__["title"].type_ is RichText:
393
- return RichText.parse_raw(v, content_type="json")
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 Extra, Field, BaseModel
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(AliasExportTransformer, BaseModel):
25
- class Config:
26
- extra = Extra.allow
27
- allow_population_by_field_name = True
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
- @classmethod
30
- def alias_generator(cls, string: str) -> str:
31
- return PAYLOAD_FIELD_ALIASES.get(string, string)
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, extra=Extra.allow):
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, extra=Extra.allow):
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, extra=Extra.allow):
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(BoolToIntTransformer, Payload):
118
+ class HTTPCallbackAck(Payload):
91
119
  opcode: Literal[Opcode.HTTP_CALLBACK_ACK] = Field(Opcode.HTTP_CALLBACK_ACK)
92
- data: bool
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, validator
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
- @validator("url", allow_reuse=True)
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__(self, obj: B, objtype: Optional[Type[B]]) -> Callable[P, Awaitable[R]]:
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.4"
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
- pydantic = "^1.9.0"
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.1.0"
29
+ ruff = "^0.2.0"
30
30
  isort = "^5.10.1"
31
- black = "^23.1.0"
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
- )