qq-botpy-sdk 2.2.0__tar.gz → 2.2.1__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.
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/PKG-INFO +1 -1
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/client.py +122 -9
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/errors.py +9 -1
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/__init__.py +10 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/errors.py +37 -0
- qq_botpy_sdk-2.2.1/botpy/protocol/media_image.py +707 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/types/media_image.py +6 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/types/message.py +2 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/pyproject.toml +1 -1
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/pyproject.toml.orig +1 -1
- qq_botpy_sdk-2.2.0/botpy/protocol/media_image.py +0 -170
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/LICENSE +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/README.md +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/__init__.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/api.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/audio.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/channel.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/configuration.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/connection.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/ext/__init__.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/ext/channel_jump/__init__.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/ext/cog_apscheduler/__init__.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/ext/cog_yaml/__init__.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/ext/command_util.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/ext/convert_color/__init__.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/flags.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/forum.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/gateway.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/guild.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/http.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/interaction.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/logging.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/manage.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/message.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/middleware/__init__.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/middleware/base.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/middleware/builtin.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/middleware/commands.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/middleware/control.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/middleware/conversation.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/middleware/policy.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/middleware/typing.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/audio.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/auth.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/constants.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/events.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/formatting.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/http.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/image.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/media.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/media_utils.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/message.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/models.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/proxy.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/ratelimit.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/reconnect.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/reply.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/send_policy.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/session.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/streaming.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/target.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/text.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/transport/__init__.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/transport/base.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/transport/webhook.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/transport/webhook_verify.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/protocol/upload_cache.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/reaction.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/robot.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/storage/__init__.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/storage/adapters.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/storage/kv.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/types/__init__.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/types/announce.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/types/audio.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/types/channel.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/types/emoji.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/types/forum.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/types/gateway.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/types/group.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/types/guild.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/types/inline.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/types/interaction.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/types/menu.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/types/panel.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/types/permission.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/types/pins_message.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/types/reaction.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/types/rich_text.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/types/robot.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/types/schedule.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/types/session.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/types/user.py +0 -0
- {qq_botpy_sdk-2.2.0 → qq_botpy_sdk-2.2.1}/botpy/user.py +0 -0
|
@@ -23,7 +23,13 @@ from .protocol.events import normalize_inbound_message
|
|
|
23
23
|
from .protocol.constants import DEFAULT_API_BASE_URL
|
|
24
24
|
from .protocol.errors import TransportError
|
|
25
25
|
from .protocol.media import ChunkedMediaUploader, ProgressCallback
|
|
26
|
-
from .protocol.media_image import
|
|
26
|
+
from .protocol.media_image import (
|
|
27
|
+
MediaImageCapacityPolicy,
|
|
28
|
+
MediaImageQuotaPolicy,
|
|
29
|
+
MediaImageRegistry,
|
|
30
|
+
MediaImageUploader,
|
|
31
|
+
MediaImageUploadResult,
|
|
32
|
+
)
|
|
27
33
|
from .protocol.message import (
|
|
28
34
|
LARGE_MEDIA_THRESHOLD,
|
|
29
35
|
MAX_MEDIA_UPLOAD_SIZE,
|
|
@@ -118,6 +124,8 @@ class Client:
|
|
|
118
124
|
gateway_send_timeout: Optional[float] = 30.0,
|
|
119
125
|
proxy: Optional[ProxyConfig] = None,
|
|
120
126
|
rate_limit: Union[bool, "RateLimiter", Mapping[str, Any], None] = None,
|
|
127
|
+
media_image_quota_policy: Optional[MediaImageQuotaPolicy] = None,
|
|
128
|
+
media_image_capacity_policy: Optional[MediaImageCapacityPolicy] = None,
|
|
121
129
|
):
|
|
122
130
|
"""
|
|
123
131
|
Args:
|
|
@@ -155,6 +163,11 @@ class Client:
|
|
|
155
163
|
rate_limit: 出站请求整流配置。默认启用,按官方文档核对后的接口频率限制
|
|
156
164
|
排队限速;``False`` 关闭整流,也可以传入
|
|
157
165
|
:class:`botpy.protocol.RateLimiter` 或参数字典自定义。
|
|
166
|
+
media_image_quota_policy: 素材图片数量超限时的自动释放策略;默认整页删除
|
|
167
|
+
最旧的一批图片后重传,传 ``MediaImageQuotaPolicy(enabled=False)`` 关闭。
|
|
168
|
+
media_image_capacity_policy: 上传后的容量水位策略;默认每张图片上传成功后
|
|
169
|
+
派发后台任务检查一次,达到 80% 时删除末尾(最旧)的一张图片。
|
|
170
|
+
传 ``MediaImageCapacityPolicy(enabled=False)`` 关闭。
|
|
158
171
|
"""
|
|
159
172
|
self.intents: int = intents.value
|
|
160
173
|
self.ret_coro: bool = False
|
|
@@ -186,6 +199,15 @@ class Client:
|
|
|
186
199
|
raise ValueError("gateway_send_timeout must be a non-negative finite number or None")
|
|
187
200
|
self._markdown_support = markdown_support
|
|
188
201
|
self._gateway_send_timeout = gateway_timeout_value
|
|
202
|
+
if media_image_quota_policy is not None and not isinstance(media_image_quota_policy, MediaImageQuotaPolicy):
|
|
203
|
+
raise TypeError("media_image_quota_policy must be a MediaImageQuotaPolicy or None")
|
|
204
|
+
self._media_image_quota_policy = media_image_quota_policy or MediaImageQuotaPolicy()
|
|
205
|
+
if media_image_capacity_policy is not None and not isinstance(
|
|
206
|
+
media_image_capacity_policy, MediaImageCapacityPolicy
|
|
207
|
+
):
|
|
208
|
+
raise TypeError("media_image_capacity_policy must be a MediaImageCapacityPolicy or None")
|
|
209
|
+
self._media_image_capacity_policy = media_image_capacity_policy or MediaImageCapacityPolicy()
|
|
210
|
+
self._media_image_registry: Optional[MediaImageRegistry] = None
|
|
189
211
|
self._token_base_url = token_base_url
|
|
190
212
|
self._user_agent = user_agent
|
|
191
213
|
self._ssl = ssl
|
|
@@ -492,14 +514,26 @@ class Client:
|
|
|
492
514
|
target: ReplyTarget,
|
|
493
515
|
content: str,
|
|
494
516
|
keyboard: Optional[Mapping[str, Any]] = None,
|
|
517
|
+
*,
|
|
518
|
+
force_verify_image_resource: Optional[bool] = None,
|
|
495
519
|
):
|
|
496
|
-
"""发送原生 Markdown,可选附带内联键盘。
|
|
520
|
+
"""发送原生 Markdown,可选附带内联键盘。
|
|
521
|
+
|
|
522
|
+
``force_verify_image_resource`` 为 ``True`` 时平台会校验 Markdown 内的图片转存结果,
|
|
523
|
+
转存失败则直接返回错误、不发送消息;默认 ``False``(保持原有行为)。
|
|
524
|
+
"""
|
|
525
|
+
|
|
526
|
+
markdown: MutableMapping[str, Any] = {"content": content}
|
|
527
|
+
if force_verify_image_resource is not None:
|
|
528
|
+
if not isinstance(force_verify_image_resource, bool):
|
|
529
|
+
raise TypeError("force_verify_image_resource must be a bool")
|
|
530
|
+
markdown["force_verify_image_resource"] = force_verify_image_resource
|
|
497
531
|
|
|
498
532
|
return await Client.send(
|
|
499
533
|
self,
|
|
500
534
|
target,
|
|
501
535
|
msg_type=MessageType.MARKDOWN,
|
|
502
|
-
markdown=
|
|
536
|
+
markdown=markdown,
|
|
503
537
|
keyboard=keyboard,
|
|
504
538
|
)
|
|
505
539
|
|
|
@@ -617,17 +651,35 @@ class Client:
|
|
|
617
651
|
content_type: Optional[str] = None,
|
|
618
652
|
alt: Optional[str] = None,
|
|
619
653
|
timeout: float = 300,
|
|
654
|
+
quota_policy: Optional[MediaImageQuotaPolicy] = None,
|
|
655
|
+
capacity_policy: Optional[MediaImageCapacityPolicy] = None,
|
|
620
656
|
) -> MediaImageUploadResult:
|
|
621
657
|
"""上传素材图片并返回 ``qqbot-media://`` 引用。
|
|
622
658
|
|
|
623
659
|
素材图片与富媒体消息的 ``file_info`` 不是同一套协议:这里只负责拿到引用,
|
|
624
660
|
通过 ``result.markdown`` 或 :meth:`send_markdown` 把图片嵌进 Markdown 消息。
|
|
625
661
|
``data`` 与 ``local_path`` 必须二选一。
|
|
662
|
+
|
|
663
|
+
平台单机器人默认最多 100 张,触顶(``40035007``)时按
|
|
664
|
+
:class:`MediaImageQuotaPolicy` 从本地列表镜像的末尾(最旧)删图片再重传,
|
|
665
|
+
删掉的 ``image_id`` 记在 ``result.released_images`` 里。传
|
|
666
|
+
``quota_policy=MediaImageQuotaPolicy(enabled=False)`` 可关闭自动释放。
|
|
667
|
+
|
|
668
|
+
每次上传成功都会按 :class:`MediaImageCapacityPolicy` 派发一个后台任务检查
|
|
669
|
+
容量,达到 80% 时删除列表末尾最旧的一张图片。
|
|
626
670
|
"""
|
|
627
671
|
|
|
628
672
|
uploader = getattr(self, "_media_image_uploader", None)
|
|
629
673
|
if uploader is None:
|
|
630
|
-
|
|
674
|
+
default_policy = getattr(self, "_media_image_quota_policy", None)
|
|
675
|
+
default_capacity = getattr(self, "_media_image_capacity_policy", None)
|
|
676
|
+
uploader = MediaImageUploader(
|
|
677
|
+
self.api,
|
|
678
|
+
logger=_log,
|
|
679
|
+
quota_policy=default_policy or MediaImageQuotaPolicy(),
|
|
680
|
+
capacity_policy=default_capacity or MediaImageCapacityPolicy(),
|
|
681
|
+
registry=self._media_image_registry_obj(),
|
|
682
|
+
)
|
|
631
683
|
self._media_image_uploader = uploader
|
|
632
684
|
return await uploader.upload(
|
|
633
685
|
data=data,
|
|
@@ -636,17 +688,56 @@ class Client:
|
|
|
636
688
|
content_type=content_type,
|
|
637
689
|
alt=alt,
|
|
638
690
|
timeout=timeout,
|
|
691
|
+
quota_policy=quota_policy,
|
|
692
|
+
capacity_policy=capacity_policy,
|
|
639
693
|
)
|
|
640
694
|
|
|
695
|
+
def _schedule_media_image_sync(self) -> None:
|
|
696
|
+
"""登录成功后派发一次后台任务,同步远端素材列表到本地镜像。"""
|
|
697
|
+
|
|
698
|
+
if getattr(self, "_closed", False) or getattr(self, "api", None) is None:
|
|
699
|
+
return
|
|
700
|
+
try:
|
|
701
|
+
self._media_image_registry_obj().schedule_sync()
|
|
702
|
+
except RuntimeError:
|
|
703
|
+
# 没有运行中的事件循环时不阻塞登录流程,首次上传前会懒同步。
|
|
704
|
+
_log.debug("[botpy] 素材图片列表未能立即同步,将在首次使用时懒同步")
|
|
705
|
+
|
|
706
|
+
def _media_image_registry_obj(self) -> MediaImageRegistry:
|
|
707
|
+
"""返回本 client 共享的素材图片列表镜像(懒创建)。"""
|
|
708
|
+
|
|
709
|
+
registry = getattr(self, "_media_image_registry", None)
|
|
710
|
+
if registry is None:
|
|
711
|
+
registry = MediaImageRegistry(self.api, logger=_log)
|
|
712
|
+
self._media_image_registry = registry
|
|
713
|
+
return registry
|
|
714
|
+
|
|
715
|
+
@property
|
|
716
|
+
def media_image_registry(self) -> MediaImageRegistry:
|
|
717
|
+
"""本地素材图片列表镜像(``entries`` 为 ``image_id``,最新在前)。"""
|
|
718
|
+
|
|
719
|
+
return self._media_image_registry_obj()
|
|
720
|
+
|
|
721
|
+
async def sync_media_images(self) -> Optional[int]:
|
|
722
|
+
"""主动全量同步一次远端素材图片列表,返回同步后的有效图片总数。"""
|
|
723
|
+
|
|
724
|
+
registry = self._media_image_registry_obj()
|
|
725
|
+
uploader = getattr(self, "_media_image_uploader", None)
|
|
726
|
+
if uploader is not None and uploader.registry is not registry:
|
|
727
|
+
uploader.registry = registry
|
|
728
|
+
return await registry.sync()
|
|
729
|
+
|
|
641
730
|
async def list_media_images(self, *, cursor: Optional[str] = None) -> Mapping[str, Any]:
|
|
642
|
-
"""
|
|
731
|
+
"""分页查询当前机器人的有效素材图片(直连远端分页接口)。"""
|
|
643
732
|
|
|
644
733
|
return await self.api.get_media_images(cursor=cursor)
|
|
645
734
|
|
|
646
735
|
async def delete_media_image(self, image_id: str) -> Any:
|
|
647
|
-
"""
|
|
736
|
+
"""删除一张素材图片并释放配额(同步更新本地列表镜像)。"""
|
|
648
737
|
|
|
649
|
-
|
|
738
|
+
if not isinstance(image_id, str) or not image_id:
|
|
739
|
+
raise ValueError("image_id must be a non-empty string")
|
|
740
|
+
return await self._media_image_registry_obj().delete(image_id)
|
|
650
741
|
|
|
651
742
|
async def send_markdown_image(
|
|
652
743
|
self,
|
|
@@ -658,8 +749,15 @@ class Client:
|
|
|
658
749
|
content_type: Optional[str] = None,
|
|
659
750
|
alt: Optional[str] = None,
|
|
660
751
|
keyboard: Optional[Mapping[str, Any]] = None,
|
|
752
|
+
force_verify_image_resource: Optional[bool] = None,
|
|
753
|
+
quota_policy: Optional[MediaImageQuotaPolicy] = None,
|
|
754
|
+
capacity_policy: Optional[MediaImageCapacityPolicy] = None,
|
|
661
755
|
):
|
|
662
|
-
"""上传素材图片并把它作为 Markdown 图片消息发出去。
|
|
756
|
+
"""上传素材图片并把它作为 Markdown 图片消息发出去。
|
|
757
|
+
|
|
758
|
+
``force_verify_image_resource`` 语义与 :meth:`send_markdown` 相同;
|
|
759
|
+
``quota_policy``、``capacity_policy`` 语义与 :meth:`upload_media_image` 相同。
|
|
760
|
+
"""
|
|
663
761
|
|
|
664
762
|
uploaded = await Client.upload_media_image(
|
|
665
763
|
self,
|
|
@@ -668,8 +766,18 @@ class Client:
|
|
|
668
766
|
file_name=file_name,
|
|
669
767
|
content_type=content_type,
|
|
670
768
|
alt=alt,
|
|
769
|
+
quota_policy=quota_policy,
|
|
770
|
+
capacity_policy=capacity_policy,
|
|
771
|
+
)
|
|
772
|
+
if force_verify_image_resource is None:
|
|
773
|
+
return await Client.send_markdown(self, target, uploaded.markdown, keyboard=keyboard)
|
|
774
|
+
return await Client.send_markdown(
|
|
775
|
+
self,
|
|
776
|
+
target,
|
|
777
|
+
uploaded.markdown,
|
|
778
|
+
keyboard=keyboard,
|
|
779
|
+
force_verify_image_resource=force_verify_image_resource,
|
|
671
780
|
)
|
|
672
|
-
return await Client.send_markdown(self, target, uploaded.markdown, keyboard=keyboard)
|
|
673
781
|
|
|
674
782
|
async def _upload_media(
|
|
675
783
|
self,
|
|
@@ -1041,6 +1149,10 @@ class Client:
|
|
|
1041
1149
|
if configuration is not None:
|
|
1042
1150
|
await configuration.close()
|
|
1043
1151
|
|
|
1152
|
+
registry = getattr(self, "_media_image_registry", None)
|
|
1153
|
+
if registry is not None:
|
|
1154
|
+
await registry.aclose()
|
|
1155
|
+
|
|
1044
1156
|
await self.http.close()
|
|
1045
1157
|
upload_cache = getattr(self, "_upload_cache", None)
|
|
1046
1158
|
if upload_cache is not None:
|
|
@@ -1164,6 +1276,7 @@ class Client:
|
|
|
1164
1276
|
user = await self.http.login(token)
|
|
1165
1277
|
self._robot = Robot(user)
|
|
1166
1278
|
self._transport_state.robot = self._robot
|
|
1279
|
+
self._schedule_media_image_sync()
|
|
1167
1280
|
|
|
1168
1281
|
if self.configuration.enabled:
|
|
1169
1282
|
try:
|
|
@@ -7,7 +7,14 @@
|
|
|
7
7
|
|
|
8
8
|
from typing import Any, Optional
|
|
9
9
|
|
|
10
|
-
from .protocol.errors import
|
|
10
|
+
from .protocol.errors import (
|
|
11
|
+
ApiError,
|
|
12
|
+
BotPyError,
|
|
13
|
+
MediaImageQuotaExceededError,
|
|
14
|
+
RateLimitError,
|
|
15
|
+
TransportError,
|
|
16
|
+
UploadDailyLimitExceededError,
|
|
17
|
+
)
|
|
11
18
|
|
|
12
19
|
|
|
13
20
|
class _LegacyApiError(ApiError):
|
|
@@ -112,6 +119,7 @@ __all__ = (
|
|
|
112
119
|
"ForbiddenError",
|
|
113
120
|
"HttpErrorDict",
|
|
114
121
|
"MethodNotAllowedError",
|
|
122
|
+
"MediaImageQuotaExceededError",
|
|
115
123
|
"NotFoundError",
|
|
116
124
|
"RateLimitError",
|
|
117
125
|
"SequenceNumberError",
|
|
@@ -10,6 +10,7 @@ from .errors import (
|
|
|
10
10
|
ApiError,
|
|
11
11
|
AuthenticationError,
|
|
12
12
|
BotPyError,
|
|
13
|
+
MediaImageQuotaExceededError,
|
|
13
14
|
RateLimitError,
|
|
14
15
|
TransportError,
|
|
15
16
|
UploadDailyLimitExceededError,
|
|
@@ -28,6 +29,10 @@ from .image import (
|
|
|
28
29
|
)
|
|
29
30
|
from .media import ChunkedMediaUploader, ChunkedUploadProgress, ProgressCallback, UploadHashes
|
|
30
31
|
from .media_image import (
|
|
32
|
+
MediaImageCapacityPolicy,
|
|
33
|
+
MediaImageQuotaPolicy,
|
|
34
|
+
MediaImageQuotaRelease,
|
|
35
|
+
MediaImageRegistry,
|
|
31
36
|
MediaImageUploadApi,
|
|
32
37
|
MediaImageUploader,
|
|
33
38
|
MediaImageUploadResult,
|
|
@@ -144,6 +149,11 @@ __all__ = (
|
|
|
144
149
|
"MAX_MEDIA_UPLOAD_SIZE",
|
|
145
150
|
"MEDIA_FILE_SIZE_LIMITS",
|
|
146
151
|
"MediaFileType",
|
|
152
|
+
"MediaImageCapacityPolicy",
|
|
153
|
+
"MediaImageQuotaExceededError",
|
|
154
|
+
"MediaImageQuotaPolicy",
|
|
155
|
+
"MediaImageQuotaRelease",
|
|
156
|
+
"MediaImageRegistry",
|
|
147
157
|
"MediaImageUploadApi",
|
|
148
158
|
"MediaImageUploader",
|
|
149
159
|
"MediaImageUploadResult",
|
|
@@ -22,6 +22,22 @@ def extract_error_code(payload: Any) -> Optional[int]:
|
|
|
22
22
|
return None
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
def extract_exception_error_code(exc: BaseException) -> Optional[int]:
|
|
26
|
+
"""从异常对象上取出平台错误码。
|
|
27
|
+
|
|
28
|
+
新版异常把错误码放在 ``code`` 上,个别旧异常只保留原始响应体;两种都看。
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
code = getattr(exc, "code", None)
|
|
32
|
+
if isinstance(code, bool):
|
|
33
|
+
code = None
|
|
34
|
+
if isinstance(code, int):
|
|
35
|
+
return code
|
|
36
|
+
if isinstance(code, str) and code.lstrip("-").isdigit():
|
|
37
|
+
return int(code)
|
|
38
|
+
return extract_error_code(getattr(exc, "response", None))
|
|
39
|
+
|
|
40
|
+
|
|
25
41
|
class BotPyError(RuntimeError):
|
|
26
42
|
"""所有新版 botpy 异常的基类。"""
|
|
27
43
|
|
|
@@ -140,3 +156,24 @@ class UploadDailyLimitExceededError(BotPyError):
|
|
|
140
156
|
super().__init__(message)
|
|
141
157
|
self.file_path = file_path
|
|
142
158
|
self.file_size = file_size
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class MediaImageQuotaExceededError(BotPyError):
|
|
162
|
+
"""素材图片数量超过平台上限,且自动释放没能腾出空间。
|
|
163
|
+
|
|
164
|
+
``total`` 是清理前查询到的有效图片总数;``deleted`` 是本次清理实际删掉的数量;
|
|
165
|
+
``max_rounds`` 是策略里配置的最大释放轮数(用于定位是策略太保守还是确实删不动)。
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
def __init__(
|
|
169
|
+
self,
|
|
170
|
+
message: str,
|
|
171
|
+
*,
|
|
172
|
+
total: Optional[int] = None,
|
|
173
|
+
deleted: int = 0,
|
|
174
|
+
max_rounds: Optional[int] = None,
|
|
175
|
+
) -> None:
|
|
176
|
+
super().__init__(message)
|
|
177
|
+
self.total = total
|
|
178
|
+
self.deleted = deleted
|
|
179
|
+
self.max_rounds = max_rounds
|