Skip to content

Commit dc341b6

Browse files
committed
fix(components): Record audio files incorrectly saved with .jpg extension
Fixed audio file extension bug (.jpg → .audio), added download_audio_by_url and save_temp_audio functions. Bug fixes: - download_audio_by_url: Added resp.raise_for_status() before reading body to prevent error pages/HTML from being saved as audio - SSL fallback (CERT_NONE): Added explicit warning log about MITM risk - Removed redundant 'except Exception as e: raise e' that stripped traceback Fixes #6853, Fixes #6509
1 parent c6f4dd1 commit dc341b6

2 files changed

Lines changed: 51 additions & 6 deletions

File tree

astrbot/core/message/components.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@
3636

3737
from astrbot.core import astrbot_config, file_token_service, logger
3838
from astrbot.core.utils.astrbot_path import get_astrbot_temp_path
39-
from astrbot.core.utils.io import download_file, download_image_by_url, file_to_base64
39+
from astrbot.core.utils.io import ( # noqa: I001
40+
download_audio_by_url,
41+
download_file,
42+
download_image_by_url,
43+
file_to_base64,
44+
)
4045

4146

4247
class ComponentType(str, Enum):
@@ -157,16 +162,16 @@ async def convert_to_file_path(self) -> str:
157162
if self.file.startswith("file:///"):
158163
return self.file[8:]
159164
if self.file.startswith("http"):
160-
file_path = await download_image_by_url(self.file)
165+
file_path = await download_audio_by_url(self.file)
161166
return os.path.abspath(file_path)
162167
if self.file.startswith("base64://"):
163168
bs64_data = self.file.removeprefix("base64://")
164-
image_bytes = base64.b64decode(bs64_data)
169+
audio_bytes = base64.b64decode(bs64_data)
165170
file_path = os.path.join(
166-
get_astrbot_temp_path(), f"recordseg_{uuid.uuid4()}.jpg"
171+
get_astrbot_temp_path(), f"recordseg_{uuid.uuid4()}.audio"
167172
)
168173
with open(file_path, "wb") as f:
169-
f.write(image_bytes)
174+
f.write(audio_bytes)
170175
return os.path.abspath(file_path)
171176
if os.path.exists(self.file):
172177
return os.path.abspath(self.file)
@@ -185,7 +190,7 @@ async def convert_to_base64(self) -> str:
185190
if self.file.startswith("file:///"):
186191
bs64_data = file_to_base64(self.file[8:])
187192
elif self.file.startswith("http"):
188-
file_path = await download_image_by_url(self.file)
193+
file_path = await download_audio_by_url(self.file)
189194
bs64_data = file_to_base64(file_path)
190195
elif self.file.startswith("base64://"):
191196
bs64_data = self.file

astrbot/core/utils/io.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,46 @@
1919
logger = logging.getLogger("astrbot")
2020

2121

22+
def save_temp_audio(audio_data: bytes) -> str:
23+
"""Save audio data to a temporary file with a proper extension."""
24+
temp_dir = get_astrbot_temp_path()
25+
timestamp = f"{int(time.time())}_{uuid.uuid4().hex[:8]}"
26+
p = os.path.join(temp_dir, f"recordseg_{timestamp}.audio")
27+
with open(p, "wb") as f:
28+
f.write(audio_data)
29+
return p
30+
31+
32+
async def download_audio_by_url(url: str) -> str:
33+
"""Download audio from URL. Returns local file path."""
34+
try:
35+
ssl_context = ssl.create_default_context(cafile=certifi.where())
36+
connector = aiohttp.TCPConnector(ssl=ssl_context)
37+
async with aiohttp.ClientSession(
38+
trust_env=True,
39+
connector=connector,
40+
) as session:
41+
async with session.get(url) as resp:
42+
resp.raise_for_status()
43+
data = await resp.read()
44+
return save_temp_audio(data)
45+
except (aiohttp.ClientConnectorSSLError, aiohttp.ClientConnectorCertificateError):
46+
logger.warning(
47+
f"SSL certificate verification failed for {url}. "
48+
"Disabling SSL verification (CERT_NONE) as a fallback. "
49+
"This is insecure and exposes the application to man-in-the-middle attacks. "
50+
"Please investigate and resolve certificate issues."
51+
)
52+
ssl_context = ssl.create_default_context()
53+
ssl_context.check_hostname = False
54+
ssl_context.verify_mode = ssl.CERT_NONE
55+
async with aiohttp.ClientSession() as session:
56+
async with session.get(url, ssl=ssl_context) as resp:
57+
resp.raise_for_status()
58+
data = await resp.read()
59+
return save_temp_audio(data)
60+
61+
2262
def on_error(func, path, exc_info) -> None:
2363
"""A callback of the rmtree function."""
2464
import stat

0 commit comments

Comments
 (0)