vibego 0.2.54__py3-none-any.whl → 0.2.55__py3-none-any.whl
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.
Potentially problematic release.
This version of vibego might be problematic. Click here for more details.
- bot.py +470 -25
- {vibego-0.2.54.dist-info → vibego-0.2.55.dist-info}/METADATA +1 -1
- {vibego-0.2.54.dist-info → vibego-0.2.55.dist-info}/RECORD +7 -7
- vibego_cli/__init__.py +1 -1
- {vibego-0.2.54.dist-info → vibego-0.2.55.dist-info}/WHEEL +0 -0
- {vibego-0.2.54.dist-info → vibego-0.2.55.dist-info}/entry_points.txt +0 -0
- {vibego-0.2.54.dist-info → vibego-0.2.55.dist-info}/top_level.txt +0 -0
bot.py
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
-
import asyncio, os, sys, time, uuid, shlex, subprocess, socket, re, json, shutil, hashlib, html
|
|
10
|
+
import asyncio, os, sys, time, uuid, shlex, subprocess, socket, re, json, shutil, hashlib, html, mimetypes
|
|
11
11
|
from datetime import datetime, UTC
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
from typing import Any, Dict, Optional, Sequence, Tuple, List, Callable, Awaitable, Literal
|
|
@@ -108,6 +108,19 @@ def _env_int(name: str, default: int) -> int:
|
|
|
108
108
|
worker_log.warning("环境变量 %s=%r 解析为整数失败,已使用默认值 %s", name, raw, default)
|
|
109
109
|
return default
|
|
110
110
|
|
|
111
|
+
|
|
112
|
+
def _env_float(name: str, default: float) -> float:
|
|
113
|
+
"""读取浮点型环境变量,解析失败时回退默认值。"""
|
|
114
|
+
|
|
115
|
+
raw = os.environ.get(name)
|
|
116
|
+
if raw is None or not raw.strip():
|
|
117
|
+
return default
|
|
118
|
+
try:
|
|
119
|
+
return float(raw.strip())
|
|
120
|
+
except ValueError:
|
|
121
|
+
worker_log.warning("环境变量 %s=%r 解析为浮点数失败,已使用默认值 %s", name, raw, default)
|
|
122
|
+
return default
|
|
123
|
+
|
|
111
124
|
_PARSE_MODE_CANDIDATES: Dict[str, Optional[ParseMode]] = {
|
|
112
125
|
"": None,
|
|
113
126
|
"none": None,
|
|
@@ -1290,6 +1303,439 @@ PROJECT_SLUG = (PROJECT_NAME or "default").replace("/", "-") or "default"
|
|
|
1290
1303
|
TASK_DB_PATH = DATA_ROOT / f"{PROJECT_SLUG}.db"
|
|
1291
1304
|
TASK_SERVICE = TaskService(TASK_DB_PATH, PROJECT_SLUG)
|
|
1292
1305
|
|
|
1306
|
+
ATTACHMENT_STORAGE_ROOT = (DATA_ROOT / "telegram").expanduser()
|
|
1307
|
+
ATTACHMENT_STORAGE_ROOT.mkdir(parents=True, exist_ok=True)
|
|
1308
|
+
_ATTACHMENT_TOTAL_MB = max(_env_int("TELEGRAM_ATTACHMENT_MAX_TOTAL_MB", 512), 16)
|
|
1309
|
+
ATTACHMENT_TOTAL_LIMIT_BYTES = _ATTACHMENT_TOTAL_MB * 1024 * 1024
|
|
1310
|
+
MEDIA_GROUP_AGGREGATION_DELAY = max(_env_float("TELEGRAM_MEDIA_GROUP_DELAY", 0.8), 0.1)
|
|
1311
|
+
|
|
1312
|
+
|
|
1313
|
+
@dataclass
|
|
1314
|
+
class TelegramSavedAttachment:
|
|
1315
|
+
"""记录单个附件的落地信息,便于提示模型读取。"""
|
|
1316
|
+
|
|
1317
|
+
kind: str
|
|
1318
|
+
display_name: str
|
|
1319
|
+
mime_type: str
|
|
1320
|
+
absolute_path: Path
|
|
1321
|
+
relative_path: str
|
|
1322
|
+
|
|
1323
|
+
|
|
1324
|
+
@dataclass
|
|
1325
|
+
class PendingMediaGroupState:
|
|
1326
|
+
"""聚合 Telegram 媒体组的临时缓存。"""
|
|
1327
|
+
|
|
1328
|
+
chat_id: int
|
|
1329
|
+
origin_message: Message
|
|
1330
|
+
attachment_dir: Path
|
|
1331
|
+
attachments: list[TelegramSavedAttachment]
|
|
1332
|
+
captions: list[str]
|
|
1333
|
+
finalize_task: Optional[asyncio.Task] = None
|
|
1334
|
+
|
|
1335
|
+
|
|
1336
|
+
MEDIA_GROUP_STATE: dict[str, PendingMediaGroupState] = {}
|
|
1337
|
+
MEDIA_GROUP_LOCK = asyncio.Lock()
|
|
1338
|
+
|
|
1339
|
+
ATTACHMENT_USAGE_HINT = (
|
|
1340
|
+
"请按需读取附件:图片可使用 Codex 的 view_image 功能或 Claude Code 的文件引用能力;"
|
|
1341
|
+
"文本/日志可直接通过 @<路径> 打开;若需其他处理请说明。"
|
|
1342
|
+
)
|
|
1343
|
+
|
|
1344
|
+
_FS_SAFE_PATTERN = re.compile(r"[^A-Za-z0-9._-]")
|
|
1345
|
+
|
|
1346
|
+
|
|
1347
|
+
def _sanitize_fs_component(value: str, fallback: str) -> str:
|
|
1348
|
+
"""清理路径片段中的特殊字符,避免越权访问。"""
|
|
1349
|
+
|
|
1350
|
+
stripped = (value or "").strip()
|
|
1351
|
+
cleaned = _FS_SAFE_PATTERN.sub("_", stripped)
|
|
1352
|
+
cleaned = cleaned.strip("._")
|
|
1353
|
+
return cleaned or fallback
|
|
1354
|
+
|
|
1355
|
+
|
|
1356
|
+
def _format_relative_path(path: Path) -> str:
|
|
1357
|
+
"""将绝对路径转换为模型更易识别的相对路径。"""
|
|
1358
|
+
|
|
1359
|
+
try:
|
|
1360
|
+
rel = path.relative_to(ROOT_DIR_PATH)
|
|
1361
|
+
rel_str = rel.as_posix()
|
|
1362
|
+
if not rel_str.startswith("."):
|
|
1363
|
+
return f"./{rel_str}"
|
|
1364
|
+
return rel_str
|
|
1365
|
+
except ValueError:
|
|
1366
|
+
return path.resolve().as_posix()
|
|
1367
|
+
|
|
1368
|
+
|
|
1369
|
+
def _directory_size(path: Path) -> int:
|
|
1370
|
+
"""计算目录占用的总字节数。"""
|
|
1371
|
+
|
|
1372
|
+
total = 0
|
|
1373
|
+
if not path.exists():
|
|
1374
|
+
return 0
|
|
1375
|
+
for entry in path.rglob("*"):
|
|
1376
|
+
try:
|
|
1377
|
+
if entry.is_file():
|
|
1378
|
+
total += entry.stat().st_size
|
|
1379
|
+
except FileNotFoundError:
|
|
1380
|
+
continue
|
|
1381
|
+
return total
|
|
1382
|
+
|
|
1383
|
+
|
|
1384
|
+
def _cleanup_attachment_storage() -> None:
|
|
1385
|
+
"""控制附件目录容量,避免磁盘被占满。"""
|
|
1386
|
+
|
|
1387
|
+
if ATTACHMENT_TOTAL_LIMIT_BYTES <= 0:
|
|
1388
|
+
return
|
|
1389
|
+
total = _directory_size(ATTACHMENT_STORAGE_ROOT)
|
|
1390
|
+
if total <= ATTACHMENT_TOTAL_LIMIT_BYTES:
|
|
1391
|
+
return
|
|
1392
|
+
candidates = sorted(
|
|
1393
|
+
(p for p in ATTACHMENT_STORAGE_ROOT.iterdir() if p.is_dir()),
|
|
1394
|
+
key=lambda item: item.stat().st_mtime,
|
|
1395
|
+
)
|
|
1396
|
+
for folder in candidates:
|
|
1397
|
+
try:
|
|
1398
|
+
shutil.rmtree(folder, ignore_errors=True)
|
|
1399
|
+
except Exception as exc: # noqa: BLE001
|
|
1400
|
+
worker_log.warning(
|
|
1401
|
+
"清理旧附件目录失败:%s",
|
|
1402
|
+
exc,
|
|
1403
|
+
extra=_session_extra(path=folder),
|
|
1404
|
+
)
|
|
1405
|
+
if _directory_size(ATTACHMENT_STORAGE_ROOT) <= ATTACHMENT_TOTAL_LIMIT_BYTES:
|
|
1406
|
+
break
|
|
1407
|
+
|
|
1408
|
+
|
|
1409
|
+
def _guess_extension(mime_type: Optional[str], fallback: str = ".bin") -> str:
|
|
1410
|
+
"""根据 MIME 类型推断扩展名。"""
|
|
1411
|
+
|
|
1412
|
+
if mime_type:
|
|
1413
|
+
guessed = mimetypes.guess_extension(mime_type, strict=False)
|
|
1414
|
+
if guessed:
|
|
1415
|
+
return guessed
|
|
1416
|
+
return fallback
|
|
1417
|
+
|
|
1418
|
+
|
|
1419
|
+
def _attachment_dir_for_message(message: Message, media_group_id: Optional[str] = None) -> Path:
|
|
1420
|
+
"""为当前消息(或媒体组)生成稳定的附件目录。"""
|
|
1421
|
+
|
|
1422
|
+
chat_part = _sanitize_fs_component(str(message.chat.id), "chat")
|
|
1423
|
+
base = ATTACHMENT_STORAGE_ROOT / chat_part
|
|
1424
|
+
if media_group_id:
|
|
1425
|
+
group_part = _sanitize_fs_component(media_group_id, str(message.message_id))
|
|
1426
|
+
target = base / f"group_{group_part}"
|
|
1427
|
+
else:
|
|
1428
|
+
timestamp = int((message.date or datetime.now(UTC)).timestamp())
|
|
1429
|
+
target = base / f"msg_{timestamp}_{message.message_id}"
|
|
1430
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
1431
|
+
return target
|
|
1432
|
+
|
|
1433
|
+
|
|
1434
|
+
async def _download_telegram_file(
|
|
1435
|
+
message: Message,
|
|
1436
|
+
*,
|
|
1437
|
+
file_id: str,
|
|
1438
|
+
file_name_hint: str,
|
|
1439
|
+
mime_type: Optional[str],
|
|
1440
|
+
target_dir: Path,
|
|
1441
|
+
) -> Path:
|
|
1442
|
+
"""从 Telegram 下载文件并返回本地路径。"""
|
|
1443
|
+
|
|
1444
|
+
bot = message.bot or current_bot()
|
|
1445
|
+
telegram_file = await bot.get_file(file_id)
|
|
1446
|
+
stem = _sanitize_fs_component(Path(file_name_hint).stem, "file")
|
|
1447
|
+
extension = Path(file_name_hint).suffix or _guess_extension(mime_type, ".bin")
|
|
1448
|
+
if not extension.startswith("."):
|
|
1449
|
+
extension = f".{extension}"
|
|
1450
|
+
filename = f"{stem}{extension}"
|
|
1451
|
+
destination = target_dir / filename
|
|
1452
|
+
counter = 1
|
|
1453
|
+
while destination.exists():
|
|
1454
|
+
destination = target_dir / f"{stem}_{counter}{extension}"
|
|
1455
|
+
counter += 1
|
|
1456
|
+
await bot.download_file(telegram_file.file_path, destination=destination)
|
|
1457
|
+
return destination
|
|
1458
|
+
|
|
1459
|
+
|
|
1460
|
+
async def _collect_saved_attachments(message: Message, target_dir: Path) -> list[TelegramSavedAttachment]:
|
|
1461
|
+
"""下载消息中的所有附件,并返回保存记录。"""
|
|
1462
|
+
|
|
1463
|
+
saved: list[TelegramSavedAttachment] = []
|
|
1464
|
+
|
|
1465
|
+
if message.photo:
|
|
1466
|
+
photo = message.photo[-1]
|
|
1467
|
+
path = await _download_telegram_file(
|
|
1468
|
+
message,
|
|
1469
|
+
file_id=photo.file_id,
|
|
1470
|
+
file_name_hint=f"photo_{photo.file_unique_id}.jpg",
|
|
1471
|
+
mime_type="image/jpeg",
|
|
1472
|
+
target_dir=target_dir,
|
|
1473
|
+
)
|
|
1474
|
+
saved.append(
|
|
1475
|
+
TelegramSavedAttachment(
|
|
1476
|
+
kind="photo",
|
|
1477
|
+
display_name=path.name,
|
|
1478
|
+
mime_type="image/jpeg",
|
|
1479
|
+
absolute_path=path,
|
|
1480
|
+
relative_path=_format_relative_path(path),
|
|
1481
|
+
)
|
|
1482
|
+
)
|
|
1483
|
+
|
|
1484
|
+
document = message.document
|
|
1485
|
+
if document:
|
|
1486
|
+
file_name = document.file_name or f"document_{document.file_unique_id}"
|
|
1487
|
+
path = await _download_telegram_file(
|
|
1488
|
+
message,
|
|
1489
|
+
file_id=document.file_id,
|
|
1490
|
+
file_name_hint=file_name,
|
|
1491
|
+
mime_type=document.mime_type or "application/octet-stream",
|
|
1492
|
+
target_dir=target_dir,
|
|
1493
|
+
)
|
|
1494
|
+
saved.append(
|
|
1495
|
+
TelegramSavedAttachment(
|
|
1496
|
+
kind="document",
|
|
1497
|
+
display_name=file_name,
|
|
1498
|
+
mime_type=document.mime_type or "application/octet-stream",
|
|
1499
|
+
absolute_path=path,
|
|
1500
|
+
relative_path=_format_relative_path(path),
|
|
1501
|
+
)
|
|
1502
|
+
)
|
|
1503
|
+
|
|
1504
|
+
video = message.video
|
|
1505
|
+
if video:
|
|
1506
|
+
file_name = video.file_name or f"video_{video.file_unique_id}"
|
|
1507
|
+
path = await _download_telegram_file(
|
|
1508
|
+
message,
|
|
1509
|
+
file_id=video.file_id,
|
|
1510
|
+
file_name_hint=file_name,
|
|
1511
|
+
mime_type=video.mime_type or "video/mp4",
|
|
1512
|
+
target_dir=target_dir,
|
|
1513
|
+
)
|
|
1514
|
+
saved.append(
|
|
1515
|
+
TelegramSavedAttachment(
|
|
1516
|
+
kind="video",
|
|
1517
|
+
display_name=file_name,
|
|
1518
|
+
mime_type=video.mime_type or "video/mp4",
|
|
1519
|
+
absolute_path=path,
|
|
1520
|
+
relative_path=_format_relative_path(path),
|
|
1521
|
+
)
|
|
1522
|
+
)
|
|
1523
|
+
|
|
1524
|
+
audio = message.audio
|
|
1525
|
+
if audio:
|
|
1526
|
+
file_name = audio.file_name or f"audio_{audio.file_unique_id}"
|
|
1527
|
+
path = await _download_telegram_file(
|
|
1528
|
+
message,
|
|
1529
|
+
file_id=audio.file_id,
|
|
1530
|
+
file_name_hint=file_name,
|
|
1531
|
+
mime_type=audio.mime_type or "audio/mpeg",
|
|
1532
|
+
target_dir=target_dir,
|
|
1533
|
+
)
|
|
1534
|
+
saved.append(
|
|
1535
|
+
TelegramSavedAttachment(
|
|
1536
|
+
kind="audio",
|
|
1537
|
+
display_name=file_name,
|
|
1538
|
+
mime_type=audio.mime_type or "audio/mpeg",
|
|
1539
|
+
absolute_path=path,
|
|
1540
|
+
relative_path=_format_relative_path(path),
|
|
1541
|
+
)
|
|
1542
|
+
)
|
|
1543
|
+
|
|
1544
|
+
voice = message.voice
|
|
1545
|
+
if voice:
|
|
1546
|
+
file_name = f"voice_{voice.file_unique_id}.ogg"
|
|
1547
|
+
path = await _download_telegram_file(
|
|
1548
|
+
message,
|
|
1549
|
+
file_id=voice.file_id,
|
|
1550
|
+
file_name_hint=file_name,
|
|
1551
|
+
mime_type=voice.mime_type or "audio/ogg",
|
|
1552
|
+
target_dir=target_dir,
|
|
1553
|
+
)
|
|
1554
|
+
saved.append(
|
|
1555
|
+
TelegramSavedAttachment(
|
|
1556
|
+
kind="voice",
|
|
1557
|
+
display_name=file_name,
|
|
1558
|
+
mime_type=voice.mime_type or "audio/ogg",
|
|
1559
|
+
absolute_path=path,
|
|
1560
|
+
relative_path=_format_relative_path(path),
|
|
1561
|
+
)
|
|
1562
|
+
)
|
|
1563
|
+
|
|
1564
|
+
animation = message.animation
|
|
1565
|
+
if animation:
|
|
1566
|
+
file_name = animation.file_name or f"animation_{animation.file_unique_id}"
|
|
1567
|
+
path = await _download_telegram_file(
|
|
1568
|
+
message,
|
|
1569
|
+
file_id=animation.file_id,
|
|
1570
|
+
file_name_hint=file_name,
|
|
1571
|
+
mime_type=animation.mime_type or "video/mp4",
|
|
1572
|
+
target_dir=target_dir,
|
|
1573
|
+
)
|
|
1574
|
+
saved.append(
|
|
1575
|
+
TelegramSavedAttachment(
|
|
1576
|
+
kind="animation",
|
|
1577
|
+
display_name=file_name,
|
|
1578
|
+
mime_type=animation.mime_type or "video/mp4",
|
|
1579
|
+
absolute_path=path,
|
|
1580
|
+
relative_path=_format_relative_path(path),
|
|
1581
|
+
)
|
|
1582
|
+
)
|
|
1583
|
+
|
|
1584
|
+
video_note = message.video_note
|
|
1585
|
+
if video_note:
|
|
1586
|
+
file_name = f"video_note_{video_note.file_unique_id}.mp4"
|
|
1587
|
+
path = await _download_telegram_file(
|
|
1588
|
+
message,
|
|
1589
|
+
file_id=video_note.file_id,
|
|
1590
|
+
file_name_hint=file_name,
|
|
1591
|
+
mime_type=video_note.mime_type or "video/mp4",
|
|
1592
|
+
target_dir=target_dir,
|
|
1593
|
+
)
|
|
1594
|
+
saved.append(
|
|
1595
|
+
TelegramSavedAttachment(
|
|
1596
|
+
kind="video_note",
|
|
1597
|
+
display_name=file_name,
|
|
1598
|
+
mime_type=video_note.mime_type or "video/mp4",
|
|
1599
|
+
absolute_path=path,
|
|
1600
|
+
relative_path=_format_relative_path(path),
|
|
1601
|
+
)
|
|
1602
|
+
)
|
|
1603
|
+
|
|
1604
|
+
if saved:
|
|
1605
|
+
_cleanup_attachment_storage()
|
|
1606
|
+
return saved
|
|
1607
|
+
|
|
1608
|
+
|
|
1609
|
+
def _build_prompt_with_attachments(
|
|
1610
|
+
text_part: Optional[str],
|
|
1611
|
+
attachments: Sequence[TelegramSavedAttachment],
|
|
1612
|
+
) -> str:
|
|
1613
|
+
"""将文字与附件描述拼接成模型可读的提示。"""
|
|
1614
|
+
|
|
1615
|
+
sections: list[str] = []
|
|
1616
|
+
base_text = (text_part or "").strip()
|
|
1617
|
+
if base_text:
|
|
1618
|
+
sections.append(base_text)
|
|
1619
|
+
if attachments:
|
|
1620
|
+
lines = ["附件列表(文件位于项目工作目录,可直接读取):"]
|
|
1621
|
+
for idx, item in enumerate(attachments, 1):
|
|
1622
|
+
lines.append(
|
|
1623
|
+
f"{idx}. {item.display_name}({item.mime_type})→ {item.relative_path}"
|
|
1624
|
+
)
|
|
1625
|
+
lines.append("")
|
|
1626
|
+
lines.append(ATTACHMENT_USAGE_HINT)
|
|
1627
|
+
sections.append("\n".join(lines))
|
|
1628
|
+
if not sections:
|
|
1629
|
+
fallback = [
|
|
1630
|
+
"收到一条仅包含附件的消息,没有额外文字说明。",
|
|
1631
|
+
"请直接阅读列出的附件并给出观察结果或结论。",
|
|
1632
|
+
]
|
|
1633
|
+
sections.append("\n".join(fallback))
|
|
1634
|
+
return "\n\n".join(sections).strip()
|
|
1635
|
+
|
|
1636
|
+
|
|
1637
|
+
async def _finalize_media_group_after_delay(media_group_id: str) -> None:
|
|
1638
|
+
"""在短暂延迟后合并媒体组消息,确保 Telegram 全部照片到齐。"""
|
|
1639
|
+
|
|
1640
|
+
try:
|
|
1641
|
+
await asyncio.sleep(MEDIA_GROUP_AGGREGATION_DELAY)
|
|
1642
|
+
except asyncio.CancelledError:
|
|
1643
|
+
return
|
|
1644
|
+
|
|
1645
|
+
async with MEDIA_GROUP_LOCK:
|
|
1646
|
+
state = MEDIA_GROUP_STATE.pop(media_group_id, None)
|
|
1647
|
+
|
|
1648
|
+
if state is None:
|
|
1649
|
+
return
|
|
1650
|
+
|
|
1651
|
+
text_block = "\n".join(state.captions).strip()
|
|
1652
|
+
prompt = _build_prompt_with_attachments(text_block, state.attachments)
|
|
1653
|
+
try:
|
|
1654
|
+
await _handle_prompt_dispatch(state.origin_message, prompt)
|
|
1655
|
+
except Exception as exc: # noqa: BLE001
|
|
1656
|
+
worker_log.exception(
|
|
1657
|
+
"媒体组消息推送模型失败:%s",
|
|
1658
|
+
exc,
|
|
1659
|
+
extra=_session_extra(media_group=media_group_id),
|
|
1660
|
+
)
|
|
1661
|
+
|
|
1662
|
+
|
|
1663
|
+
async def _enqueue_media_group_message(message: Message, text_part: Optional[str]) -> None:
|
|
1664
|
+
"""收集媒体组中的每一条消息,统一延迟推送。"""
|
|
1665
|
+
|
|
1666
|
+
media_group_id = message.media_group_id
|
|
1667
|
+
if not media_group_id:
|
|
1668
|
+
return
|
|
1669
|
+
|
|
1670
|
+
async with MEDIA_GROUP_LOCK:
|
|
1671
|
+
state = MEDIA_GROUP_STATE.get(media_group_id)
|
|
1672
|
+
if state is None:
|
|
1673
|
+
attachment_dir = _attachment_dir_for_message(message, media_group_id=media_group_id)
|
|
1674
|
+
state = PendingMediaGroupState(
|
|
1675
|
+
chat_id=message.chat.id,
|
|
1676
|
+
origin_message=message,
|
|
1677
|
+
attachment_dir=attachment_dir,
|
|
1678
|
+
attachments=[],
|
|
1679
|
+
captions=[],
|
|
1680
|
+
)
|
|
1681
|
+
MEDIA_GROUP_STATE[media_group_id] = state
|
|
1682
|
+
else:
|
|
1683
|
+
attachment_dir = state.attachment_dir
|
|
1684
|
+
|
|
1685
|
+
attachments = await _collect_saved_attachments(message, attachment_dir)
|
|
1686
|
+
caption = (text_part or "").strip()
|
|
1687
|
+
|
|
1688
|
+
async with MEDIA_GROUP_LOCK:
|
|
1689
|
+
state = MEDIA_GROUP_STATE.get(media_group_id)
|
|
1690
|
+
if state is None:
|
|
1691
|
+
# 若期间被清理,重新创建并继续积累,避免丢失后续内容。
|
|
1692
|
+
state = PendingMediaGroupState(
|
|
1693
|
+
chat_id=message.chat.id,
|
|
1694
|
+
origin_message=message,
|
|
1695
|
+
attachment_dir=attachment_dir,
|
|
1696
|
+
attachments=[],
|
|
1697
|
+
captions=[],
|
|
1698
|
+
)
|
|
1699
|
+
MEDIA_GROUP_STATE[media_group_id] = state
|
|
1700
|
+
state.attachments.extend(attachments)
|
|
1701
|
+
if caption:
|
|
1702
|
+
state.captions.append(caption)
|
|
1703
|
+
# 使用首条消息作为引用对象,便于 Telegram 回复。
|
|
1704
|
+
if state.origin_message.message_id > message.message_id:
|
|
1705
|
+
state.origin_message = message
|
|
1706
|
+
if state.finalize_task and not state.finalize_task.done():
|
|
1707
|
+
state.finalize_task.cancel()
|
|
1708
|
+
state.finalize_task = asyncio.create_task(_finalize_media_group_after_delay(media_group_id))
|
|
1709
|
+
|
|
1710
|
+
|
|
1711
|
+
async def _handle_prompt_dispatch(message: Message, prompt: str) -> None:
|
|
1712
|
+
"""统一封装向模型推送提示词的流程。"""
|
|
1713
|
+
|
|
1714
|
+
if ENV_ISSUES:
|
|
1715
|
+
message_text = _format_env_issue_message()
|
|
1716
|
+
worker_log.warning(
|
|
1717
|
+
"拒绝处理消息,环境异常: %s",
|
|
1718
|
+
message_text,
|
|
1719
|
+
extra={**_session_extra(), "chat": message.chat.id},
|
|
1720
|
+
)
|
|
1721
|
+
await message.answer(message_text)
|
|
1722
|
+
return
|
|
1723
|
+
|
|
1724
|
+
bot = current_bot()
|
|
1725
|
+
await bot.send_chat_action(message.chat.id, "typing")
|
|
1726
|
+
|
|
1727
|
+
if MODE == "A":
|
|
1728
|
+
if not AGENT_CMD:
|
|
1729
|
+
await message.answer("AGENT_CMD 未配置(.env)")
|
|
1730
|
+
return
|
|
1731
|
+
rc, out = run_subprocess_capture(AGENT_CMD, input_text=prompt)
|
|
1732
|
+
out = out or ""
|
|
1733
|
+
out = out + ("" if rc == 0 else f"\n(exit={rc})")
|
|
1734
|
+
await reply_large_text(message.chat.id, out)
|
|
1735
|
+
return
|
|
1736
|
+
|
|
1737
|
+
await _dispatch_prompt_to_model(message.chat.id, prompt, reply_to=message)
|
|
1738
|
+
|
|
1293
1739
|
BOT_COMMANDS: list[tuple[str, str]] = [
|
|
1294
1740
|
("help", "查看全部命令"),
|
|
1295
1741
|
("tasks", "任务命令清单"),
|
|
@@ -7390,6 +7836,28 @@ async def on_edit_new_value(message: Message, state: FSMContext) -> None:
|
|
|
7390
7836
|
await _answer_with_markdown(message, f"任务已更新:\n{detail_text}", reply_markup=markup)
|
|
7391
7837
|
|
|
7392
7838
|
|
|
7839
|
+
@router.message(
|
|
7840
|
+
F.photo | F.document | F.video | F.audio | F.voice | F.animation | F.video_note
|
|
7841
|
+
)
|
|
7842
|
+
async def on_media_message(message: Message) -> None:
|
|
7843
|
+
"""处理带附件的普通消息,将附件下载并拼接提示词。"""
|
|
7844
|
+
|
|
7845
|
+
_auto_record_chat_id(message.chat.id)
|
|
7846
|
+
text_part = (message.caption or message.text or "").strip()
|
|
7847
|
+
|
|
7848
|
+
if message.media_group_id:
|
|
7849
|
+
await _enqueue_media_group_message(message, text_part)
|
|
7850
|
+
return
|
|
7851
|
+
|
|
7852
|
+
attachment_dir = _attachment_dir_for_message(message)
|
|
7853
|
+
attachments = await _collect_saved_attachments(message, attachment_dir)
|
|
7854
|
+
if not attachments and not text_part:
|
|
7855
|
+
await message.answer("未检测到可处理的附件或文字内容。")
|
|
7856
|
+
return
|
|
7857
|
+
prompt = _build_prompt_with_attachments(text_part, attachments)
|
|
7858
|
+
await _handle_prompt_dispatch(message, prompt)
|
|
7859
|
+
|
|
7860
|
+
|
|
7393
7861
|
@router.message(CommandStart())
|
|
7394
7862
|
async def on_start(m: Message):
|
|
7395
7863
|
# 首次收到消息时自动记录 chat_id 到 state 文件
|
|
@@ -7422,30 +7890,7 @@ async def on_text(m: Message):
|
|
|
7422
7890
|
return
|
|
7423
7891
|
if prompt.startswith("/"):
|
|
7424
7892
|
return
|
|
7425
|
-
|
|
7426
|
-
if ENV_ISSUES:
|
|
7427
|
-
message = _format_env_issue_message()
|
|
7428
|
-
worker_log.warning(
|
|
7429
|
-
"拒绝处理消息,环境异常: %s",
|
|
7430
|
-
message,
|
|
7431
|
-
extra={**_session_extra(), "chat": m.chat.id},
|
|
7432
|
-
)
|
|
7433
|
-
await m.answer(message)
|
|
7434
|
-
return
|
|
7435
|
-
|
|
7436
|
-
bot = current_bot()
|
|
7437
|
-
await bot.send_chat_action(m.chat.id, "typing") # “正在输入”提示
|
|
7438
|
-
|
|
7439
|
-
if MODE == "A":
|
|
7440
|
-
if not AGENT_CMD:
|
|
7441
|
-
return await m.answer("AGENT_CMD 未配置(.env)")
|
|
7442
|
-
rc, out = run_subprocess_capture(AGENT_CMD, input_text=prompt)
|
|
7443
|
-
out = out or ""
|
|
7444
|
-
out = out + ("" if rc == 0 else f"\n(exit={rc})")
|
|
7445
|
-
await reply_large_text(m.chat.id, out)
|
|
7446
|
-
|
|
7447
|
-
else:
|
|
7448
|
-
await _dispatch_prompt_to_model(m.chat.id, prompt, reply_to=m)
|
|
7893
|
+
await _handle_prompt_dispatch(m, prompt)
|
|
7449
7894
|
|
|
7450
7895
|
|
|
7451
7896
|
async def ensure_telegram_connectivity(bot: Bot, timeout: float = 30.0):
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
bot.py,sha256=
|
|
1
|
+
bot.py,sha256=hjebHpgW7tR9fNxkdbgNLFNYaz4MNgtsq_Sci2VljPM,289835
|
|
2
2
|
logging_setup.py,sha256=gvxHi8mUwK3IhXJrsGNTDo-DR6ngkyav1X-tvlBF_IE,4613
|
|
3
3
|
master.py,sha256=Jwxf6I94jOADzb9Xio1wb-tWy5wgQ9PlmdpKW4mhQMg,117114
|
|
4
4
|
project_repository.py,sha256=UcthtSGOJK0cTE5bQCneo3xkomRG-kyc1N1QVqxeHIs,17577
|
|
@@ -426,14 +426,14 @@ tasks/constants.py,sha256=tS1kZxBIUm3JJUMHm25XI-KHNUZl5NhbbuzjzL_rF-c,299
|
|
|
426
426
|
tasks/fsm.py,sha256=rKXXLEieQQU4r2z_CZUvn1_70FXiZXBBugF40gpe_tQ,1476
|
|
427
427
|
tasks/models.py,sha256=N_qqRBo9xMSV0vbn4k6bLBXT8C_dp_oTFUxvdx16ZQM,2459
|
|
428
428
|
tasks/service.py,sha256=w_S_aWiVqRXzXEpimLDsuCCCX2lB5uDkff9aKThBw9c,41916
|
|
429
|
-
vibego_cli/__init__.py,sha256=
|
|
429
|
+
vibego_cli/__init__.py,sha256=asbT5tFazlPcS692PW0b6GBg_fWkYpf-mZn02hiG-p4,311
|
|
430
430
|
vibego_cli/__main__.py,sha256=qqTrYmRRLe4361fMzbI3-CqpZ7AhTofIHmfp4ykrrBY,158
|
|
431
431
|
vibego_cli/config.py,sha256=VxkPJMq01tA3h3cOkH-z_tiP7pMgfSGGicRvUnCWkhI,3054
|
|
432
432
|
vibego_cli/deps.py,sha256=1nRXI7Dd-S1hYE8DligzK5fIluQWETRUj4_OKL0DikQ,1419
|
|
433
433
|
vibego_cli/main.py,sha256=X__NXwZnIDIFbdKSTbNyZgZHKcPlN0DQz9sqTI1aQ9E,12158
|
|
434
434
|
vibego_cli/data/worker_requirements.txt,sha256=QSt30DSSSHtfucTFPpc7twk9kLS5rVLNTcvDiagxrZg,62
|
|
435
|
-
vibego-0.2.
|
|
436
|
-
vibego-0.2.
|
|
437
|
-
vibego-0.2.
|
|
438
|
-
vibego-0.2.
|
|
439
|
-
vibego-0.2.
|
|
435
|
+
vibego-0.2.55.dist-info/METADATA,sha256=McMxELuZfQ_ByCmOD3gRCEIqec4IHYK1pPNjMpn8xxM,10519
|
|
436
|
+
vibego-0.2.55.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
437
|
+
vibego-0.2.55.dist-info/entry_points.txt,sha256=Lsy_zm-dlyxt8-9DL9blBReIwU2k22c8-kifr46ND1M,48
|
|
438
|
+
vibego-0.2.55.dist-info/top_level.txt,sha256=R56CT3nW5H5v3ce0l3QDN4-C4qxTrNWzRTwrxnkDX4U,69
|
|
439
|
+
vibego-0.2.55.dist-info/RECORD,,
|
vibego_cli/__init__.py
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|