Skip to content

Commit a4bdde7

Browse files
committed
fix: repair dev test baseline after master sync
1 parent 96018e6 commit a4bdde7

68 files changed

Lines changed: 588 additions & 3674 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

astrbot/core/agent/runners/coze/coze_agent_runner.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
ProviderRequest,
1818
)
1919
from astrbot.core.provider.provider import Provider
20+
from astrbot.core.utils.media_utils import MediaResolver, describe_media_ref
2021

2122
from .coze_api_client import CozeAPIClient
2223

@@ -354,8 +355,11 @@ async def _download_and_upload_image(
354355
return file_id
355356

356357
try:
357-
image_data = await self.api_client.download_image(image_url)
358-
file_id = await self.api_client.upload_file(image_data)
358+
image_bytes = await MediaResolver(
359+
image_url,
360+
media_type="image",
361+
).to_bytes()
362+
file_id = await self.api_client.upload_file(image_bytes)
359363

360364
if session_id:
361365
self.file_id_cache[session_id][cache_key] = file_id
@@ -364,7 +368,9 @@ async def _download_and_upload_image(
364368
return file_id
365369

366370
except Exception as e:
367-
logger.error(f"处理图片失败 {image_url}: {e!s}")
371+
logger.error(
372+
f"处理图片失败 {describe_media_ref(image_url)}: {e!s}",
373+
)
368374
raise Exception(f"处理图片失败: {e!s}") from e
369375

370376
@override

astrbot/core/agent/runners/dify/dify_agent_runner.py

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import base64
21
import os
32
from typing import Any, override
43

@@ -18,6 +17,7 @@
1817
from astrbot.core.provider.provider import Provider
1918
from astrbot.core.utils.astrbot_path import get_astrbot_temp_path
2019
from astrbot.core.utils.io import download_file
20+
from astrbot.core.utils.media_utils import MediaResolver
2121

2222

2323
class DifyAgentRunner(BaseAgentRunner[TContext]):
@@ -111,6 +111,40 @@ async def step_until_done(self, max_step: int):
111111
async for resp in self.step():
112112
yield resp
113113

114+
async def _upload_image_for_dify(
115+
self,
116+
image_url: str,
117+
session_id: str,
118+
) -> dict[str, str] | None:
119+
image_data = await MediaResolver(
120+
image_url,
121+
media_type="image",
122+
).to_base64_data(strict=True)
123+
if image_data is None:
124+
logger.warning("Dify 图片预处理结果为空,将忽略。")
125+
return None
126+
127+
image_extension = image_data.mime_type.split("/", 1)[-1] or "png"
128+
if image_extension == "jpeg":
129+
image_extension = "jpg"
130+
131+
file_response = await self.api_client.file_upload(
132+
file_data=image_data.to_bytes(),
133+
user=session_id,
134+
mime_type=image_data.mime_type,
135+
file_name=f"image.{image_extension}",
136+
)
137+
logger.debug(f"Dify 上传图片响应:{file_response}")
138+
if "id" not in file_response:
139+
logger.warning(f"上传图片后得到未知的 Dify 响应:{file_response},图片将忽略。")
140+
return None
141+
142+
return {
143+
"type": "image",
144+
"transfer_method": "local_file",
145+
"upload_file_id": file_response["id"],
146+
}
147+
114148
async def _execute_dify_request(self):
115149
"""执行 Dify 请求的核心逻辑"""
116150
prompt = self.req.prompt or ""
@@ -129,31 +163,16 @@ async def _execute_dify_request(self):
129163
# 处理图片上传
130164
files_payload = []
131165
for image_url in image_urls:
132-
# image_url is a base64 string
133166
try:
134-
image_data = base64.b64decode(image_url)
135-
file_response = await self.api_client.file_upload(
136-
file_data=image_data,
137-
user=session_id,
138-
mime_type="image/png",
139-
file_name="image.png",
140-
)
141-
logger.debug(f"Dify 上传图片响应:{file_response}")
142-
if "id" not in file_response:
143-
logger.warning(
144-
f"上传图片后得到未知的 Dify 响应:{file_response},图片将忽略。",
145-
)
146-
continue
147-
files_payload.append(
148-
{
149-
"type": "image",
150-
"transfer_method": "local_file",
151-
"upload_file_id": file_response["id"],
152-
},
167+
image_payload = await self._upload_image_for_dify(
168+
image_url,
169+
session_id,
153170
)
154171
except Exception as e:
155172
logger.warning(f"上传图片失败:{e}")
156173
continue
174+
if image_payload:
175+
files_payload.append(image_payload)
157176

158177
# 获得会话变量
159178
payload_vars = self.variables.copy()

astrbot/core/pipeline/content_safety_check/stage.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,15 @@ async def initialize(self, ctx: PipelineContext) -> None:
2525
async def process(
2626
self,
2727
event: AstrMessageEvent,
28+
check_text: str | None = None,
2829
) -> AsyncGenerator[None, None]:
29-
async for item in self.process_text(event, event.get_message_str()):
30+
async for item in self.process_text(event, check_text):
3031
yield item
3132

3233
async def process_text(
3334
self,
3435
event: AstrMessageEvent,
35-
check_text: str,
36+
check_text: str | None,
3637
) -> AsyncGenerator[None, None]:
3738
"""检查内容安全"""
3839
if check_text is None:

astrbot/core/pipeline/preprocess_stage/stage.py

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
import asyncio
22
import random
33
import traceback
4+
from pathlib import Path
45

56
from astrbot.core import logger
67
from astrbot.core.message.components import Image, Plain, Record, Reply
78
from astrbot.core.pipeline.context import PipelineContext
89
from astrbot.core.pipeline.stage import Stage, register_stage
910
from astrbot.core.platform.astr_message_event import AstrMessageEvent
10-
from astrbot.core.utils.media_utils import ensure_wav
11+
from astrbot.core.utils.astrbot_path import get_astrbot_temp_path
12+
from astrbot.core.utils.media_utils import (
13+
describe_media_ref,
14+
ensure_jpeg,
15+
ensure_wav,
16+
file_uri_to_path,
17+
is_file_uri,
18+
)
1119

1220

1321
@register_stage
@@ -20,6 +28,17 @@ async def initialize(self, ctx: PipelineContext) -> None:
2028
self.stt_settings: dict = self.config.get("provider_stt_settings", {})
2129
self.platform_settings: dict = self.config.get("platform_settings", {})
2230

31+
@staticmethod
32+
def _track_temp_media(event: AstrMessageEvent, media_path: str) -> None:
33+
"""Track files owned by this event when they live under AstrBot temp."""
34+
try:
35+
path = Path(media_path).resolve()
36+
temp_dir = Path(get_astrbot_temp_path()).resolve()
37+
path.relative_to(temp_dir)
38+
except (OSError, ValueError):
39+
return
40+
event.track_temporary_local_file(str(path))
41+
2342
async def process(
2443
self,
2544
event: AstrMessageEvent,
@@ -59,7 +78,11 @@ async def process(
5978
from_ = from_.removesuffix("/")
6079
to_ = to_.removesuffix("/")
6180

62-
url = component.url.removeprefix("file://")
81+
url = (
82+
file_uri_to_path(component.url)
83+
if is_file_uri(component.url)
84+
else component.url
85+
)
6386
if url.startswith(from_):
6487
component.url = url.replace(from_, to_, 1)
6588
logger.debug(f"Path mapping: {url} -> {component.url}")
@@ -71,14 +94,31 @@ async def process(
7194
if isinstance(component, Record):
7295
try:
7396
original_path = await component.convert_to_file_path()
97+
self._track_temp_media(event, original_path)
7498
record_path = await ensure_wav(original_path)
75-
if record_path != original_path:
76-
event.track_temporary_local_file(record_path)
99+
self._track_temp_media(event, record_path)
77100
component.file = record_path
78101
component.path = record_path
79102
message_chain[idx] = component
80103
except Exception as e:
81104
logger.warning(f"Voice processing failed: {e}")
105+
elif isinstance(component, Image):
106+
try:
107+
original_path = await component.convert_to_file_path()
108+
self._track_temp_media(event, original_path)
109+
image_path = await ensure_jpeg(original_path)
110+
self._track_temp_media(event, image_path)
111+
component.file = image_path
112+
component.path = image_path
113+
component.url = image_path
114+
message_chain[idx] = component
115+
except Exception as e:
116+
media_ref = component.url or component.file
117+
logger.warning(
118+
"Image processing failed for %s: %s",
119+
describe_media_ref(media_ref),
120+
e,
121+
)
82122

83123
# Also process Record components inside Reply chains (wav conversion)
84124
for component in event.get_messages():
@@ -87,16 +127,33 @@ async def process(
87127
if isinstance(reply_comp, Record):
88128
try:
89129
original_path = await reply_comp.convert_to_file_path()
130+
self._track_temp_media(event, original_path)
90131
record_path = await ensure_wav(original_path)
91-
if record_path != original_path:
92-
event.track_temporary_local_file(record_path)
132+
self._track_temp_media(event, record_path)
93133
reply_comp.file = record_path
94134
reply_comp.path = record_path
95135
component.chain[idx] = reply_comp
96136
except Exception as e:
97137
logger.warning(
98138
f"Voice processing in reply chain failed: {e}"
99139
)
140+
elif isinstance(reply_comp, Image):
141+
try:
142+
original_path = await reply_comp.convert_to_file_path()
143+
self._track_temp_media(event, original_path)
144+
image_path = await ensure_jpeg(original_path)
145+
self._track_temp_media(event, image_path)
146+
reply_comp.file = image_path
147+
reply_comp.path = image_path
148+
reply_comp.url = image_path
149+
component.chain[idx] = reply_comp
150+
except Exception as e:
151+
media_ref = reply_comp.url or reply_comp.file
152+
logger.warning(
153+
"Image processing in reply chain failed for %s: %s",
154+
describe_media_ref(media_ref),
155+
e,
156+
)
100157

101158
# STT
102159
if self.stt_settings.get("enable", False):

astrbot/core/platform/astr_message_event.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -293,14 +293,12 @@ async def send_streaming(
293293
)
294294
self._has_send_oper = True
295295

296-
@abc.abstractmethod
297296
async def send_typing(self) -> None:
298297
"""发送输入中状态。
299298
300299
默认实现为空,由具体平台按需重写。
301300
"""
302301

303-
@abc.abstractmethod
304302
async def stop_typing(self) -> None:
305303
"""停止输入中状态。
306304
@@ -507,11 +505,14 @@ async def react(self, emoji: str) -> None:
507505
"""
508506
await self.send(MessageChain([Plain(emoji)]))
509507

510-
@abc.abstractmethod
511508
async def get_group(self, group_id: str | None = None, **kwargs) -> Group | None:
512509
"""获取一个群聊的数据, 如果不填写 group_id: 如果是私聊消息,返回 None。如果是群聊消息,返回当前群聊的数据。
513510
514511
适配情况:
515512
516513
- aiocqhttp(OneBotv11)
517514
"""
515+
group = self.message_obj.group
516+
if group is None or (group_id is not None and group_id != group.group_id):
517+
return None
518+
return group

astrbot/core/platform/sources/discord/discord_platform_adapter.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from astrbot import logger
1111
from astrbot.api.event import MessageChain
12-
from astrbot.api.message_components import At, File, Image, Plain
12+
from astrbot.api.message_components import At, File, Image, Plain, Record
1313
from astrbot.api.platform import (
1414
AstrBotMessage,
1515
MessageMember,
@@ -26,6 +26,7 @@
2626
StarHandlerMetadata,
2727
star_handlers_registry,
2828
)
29+
from astrbot.core.utils.media_utils import MediaResolver
2930

3031
from .client import DiscordBotClient
3132
from .discord_platform_event import DiscordPlatformEvent
@@ -256,6 +257,12 @@ def _convert_message_to_abm(self, data: dict) -> AstrBotMessage:
256257
message_chain.append(
257258
Image(file=attachment.url, filename=attachment.filename),
258259
)
260+
elif attachment.content_type and attachment.content_type.startswith(
261+
"audio/",
262+
):
263+
message_chain.append(
264+
Record(file=attachment.url, url=attachment.url),
265+
)
259266
else:
260267
message_chain.append(
261268
File(name=attachment.filename, url=attachment.url),
@@ -270,7 +277,20 @@ def _convert_message_to_abm(self, data: dict) -> AstrBotMessage:
270277
async def convert_message(self, data: dict) -> AstrBotMessage:
271278
"""将平台消息转换成 AstrBotMessage"""
272279
# 由于 on_interaction 已被禁用,我们只处理普通消息
273-
return self._convert_message_to_abm(data)
280+
abm = self._convert_message_to_abm(data)
281+
for component in abm.message:
282+
if isinstance(component, Record):
283+
audio_ref = component.url or component.file
284+
if audio_ref:
285+
path_wav = await MediaResolver(
286+
audio_ref,
287+
media_type="audio",
288+
default_suffix=".wav",
289+
).to_path(target_format="wav")
290+
component.file = path_wav
291+
component.url = path_wav
292+
component.path = path_wav
293+
return abm
274294

275295
async def handle_msg(self, message: AstrBotMessage, followup_webhook=None) -> None:
276296
"""处理消息"""
@@ -433,7 +453,7 @@ async def _collect_and_register_commands(self) -> None:
433453
except HTTPException as exc:
434454
if getattr(exc, "code", None) == 30034:
435455
logger.warning(
436-
"[Discord] 跳过指令同步:已达到 Discord 每日 application command create 限额。",
456+
"[Discord] 跳过指令同步:已达到 Discord 每日 application command create 限额(code=30034)。",
437457
)
438458
return
439459
raise

0 commit comments

Comments
 (0)