Добавить канальный режим агента-кодера

This commit is contained in:
AidarKC
2026-05-24 09:25:25 +03:00
parent a83ec2c971
commit 35565845ca
7 changed files with 140 additions and 16 deletions
+101 -12
View File
@@ -109,7 +109,7 @@ class TelegramApi:
return result
def get_updates(self, offset: int | None, timeout_sec: int) -> list[dict[str, Any]]:
payload: dict[str, Any] = {"timeout": timeout_sec, "allowed_updates": ["message"]}
payload: dict[str, Any] = {"timeout": timeout_sec, "allowed_updates": ["message", "channel_post"]}
if offset is not None:
payload["offset"] = offset
result = self.call("getUpdates", payload=payload, timeout=timeout_sec + 15)
@@ -133,6 +133,7 @@ class BotConfig:
self.root_dir = root_dir
self.telegram_bot_token = self._required(env, "TELEGRAM_BOT_TOKEN")
self.allowed_username = normalize_username(env.get("ALLOWED_TELEGRAM_USERNAME", "AidarKC"))
self.allowed_channel_username = normalize_username(env.get("ALLOWED_TELEGRAM_CHANNEL_USERNAME", "shine_writing"))
self.bot_username = env.get("BOT_USERNAME", "aidar_su_bot")
self.openai_api_key = env.get("OPENAI_API_KEY", "").strip()
self.openai_transcribe_model = env.get("OPENAI_TRANSCRIBE_MODEL", "gpt-4o-mini-transcribe")
@@ -351,13 +352,22 @@ class ShinePyBotService:
def _handle_update(self, update: dict[str, Any]) -> None:
message = update.get("message")
update_type = "message"
if not isinstance(message, dict):
return
message = update.get("channel_post")
update_type = "channel_post"
if not isinstance(message, dict):
return
chat = message.get("chat") or {}
chat_id = chat.get("id")
message_id = message.get("message_id")
chat_type = str(chat.get("type") or "")
chat_username = normalize_username(chat.get("username"))
chat_title = str(chat.get("title") or "")
sender = message.get("from") or {}
username = normalize_username(sender.get("username"))
author_signature = str(message.get("author_signature") or "").strip()
author_username = username or normalize_username(author_signature)
if not isinstance(chat_id, int) or not isinstance(message_id, int):
return
@@ -365,47 +375,119 @@ class ShinePyBotService:
if self._mark_processed_update(update_key):
return
if username != self.cfg.allowed_username:
is_channel_post = update_type == "channel_post" or chat_type == "channel"
is_allowed_channel = (
not is_channel_post
or not self.cfg.allowed_channel_username
or chat_username == self.cfg.allowed_channel_username
)
if is_channel_post and not is_allowed_channel:
return
text = (message.get("text") or message.get("caption") or "").strip()
history_path = self._current_history_file()
if author_username != self.cfg.allowed_username:
if is_channel_post:
self._append_history(history_path, "channel_context_message", {
"chatId": chat_id,
"messageId": message_id,
"chatUsername": chat_username,
"chatTitle": chat_title,
"username": username,
"authorSignature": author_signature,
"text": text,
"hasVoice": bool(message.get("voice")),
"hasAudio": bool(message.get("audio")),
})
return
text = (message.get("text") or "").strip()
if not text:
if message.get("voice"):
self._enqueue_voice_job(chat_id, message_id, username, message["voice"].get("file_id"))
self._enqueue_voice_job(
chat_id,
message_id,
author_username,
message["voice"].get("file_id"),
update_type=update_type,
chat_username=chat_username,
chat_title=chat_title,
author_signature=author_signature,
)
return
if message.get("audio"):
self._enqueue_voice_job(chat_id, message_id, username, message["audio"].get("file_id"))
self._enqueue_voice_job(
chat_id,
message_id,
author_username,
message["audio"].get("file_id"),
update_type=update_type,
chat_username=chat_username,
chat_title=chat_title,
author_signature=author_signature,
)
return
self._safe_send(chat_id, "Поддерживаются текст, voice и audio.", reply_to=message_id)
return
if text.startswith("/"):
self._handle_command(chat_id, message_id, username, text)
self._handle_command(chat_id, message_id, author_username, text)
return
history_path = self._current_history_file()
self._append_history(history_path, "incoming_text", {
"chatId": chat_id, "messageId": message_id, "username": username, "text": text
"chatId": chat_id,
"messageId": message_id,
"updateType": update_type,
"chatUsername": chat_username,
"chatTitle": chat_title,
"username": author_username,
"authorSignature": author_signature,
"text": text,
})
job = self._build_job_base(chat_id, message_id, username, str(history_path))
job = self._build_job_base(chat_id, message_id, author_username, str(history_path))
job["type"] = "text"
job["text"] = text
job["update_type"] = update_type
job["chat_username"] = chat_username
job["chat_title"] = chat_title
job["author_signature"] = author_signature
with self.queue_lock:
self.queue.append(job)
self._persist_queue()
self._safe_send(chat_id, f"Принял задачу #{job['num']}", reply_to=message_id)
def _enqueue_voice_job(self, chat_id: int, message_id: int, username: str, file_id: str | None) -> None:
def _enqueue_voice_job(
self,
chat_id: int,
message_id: int,
username: str,
file_id: str | None,
*,
update_type: str = "message",
chat_username: str = "",
chat_title: str = "",
author_signature: str = "",
) -> None:
if not file_id:
self._safe_send(chat_id, "Не удалось прочитать file_id голосового.", reply_to=message_id)
return
history_path = self._current_history_file()
self._append_history(history_path, "incoming_voice", {
"chatId": chat_id, "messageId": message_id, "username": username, "fileId": file_id
"chatId": chat_id,
"messageId": message_id,
"updateType": update_type,
"chatUsername": chat_username,
"chatTitle": chat_title,
"username": username,
"authorSignature": author_signature,
"fileId": file_id,
})
job = self._build_job_base(chat_id, message_id, username, str(history_path))
job["type"] = "voice"
job["telegram_file_id"] = file_id
job["update_type"] = update_type
job["chat_username"] = chat_username
job["chat_title"] = chat_title
job["author_signature"] = author_signature
with self.queue_lock:
self.queue.append(job)
self._persist_queue()
@@ -424,6 +506,10 @@ class ShinePyBotService:
"chat_id": chat_id,
"message_id": message_id,
"username": username,
"update_type": "message",
"chat_username": "",
"chat_title": "",
"author_signature": "",
"text": "",
"telegram_file_id": "",
"history_file": history_file,
@@ -630,7 +716,10 @@ class ShinePyBotService:
return (
"Пришло сообщение в Telegram.\n"
f"Тип: {job.get('type')}\n"
f"Источник Telegram: {job.get('update_type', 'message')}\n"
f"Канал/чат: @{job.get('chat_username') or ''} {job.get('chat_title') or ''}\n"
f"Username отправителя: @{job.get('username')}\n"
f"Подпись автора в Telegram: {job.get('author_signature') or ''}\n"
"Текст для обработки:\n"
f"{job.get('text')}\n\n"
f"История диалога (JSONL): {job.get('history_file')}\n"