ozellik: telegram uzerinden drive yukleme ve clean_chat komutunu ekle
This commit is contained in:
@@ -176,6 +176,9 @@ Bu yaklaşım belge tabanlı RAG akışına daha uygun olduğu için doğrudan D
|
|||||||
- `/otomasyon_baslat <id>`
|
- `/otomasyon_baslat <id>`
|
||||||
- `/otomasyon_sil <id>`
|
- `/otomasyon_sil <id>`
|
||||||
- `/notlarima_ekle`
|
- `/notlarima_ekle`
|
||||||
|
- `/clean_chat`
|
||||||
|
|
||||||
|
`/clean_chat` yalnızca Telegram konuşma ekranını temizlemeye çalışır; veritabanındaki memory, audit log, profil veya second brain kayıtlarını silmez.
|
||||||
|
|
||||||
## ⏱️ Otomasyonlar
|
## ⏱️ Otomasyonlar
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from telegram import BotCommand, InputMediaPhoto, Update
|
from telegram import BotCommand, InputMediaPhoto, Update
|
||||||
from telegram.constants import ChatAction
|
from telegram.constants import ChatAction
|
||||||
from telegram.ext import Application, CommandHandler, ContextTypes, MessageHandler, filters
|
from telegram.ext import Application, CommandHandler, ContextTypes, MessageHandler, filters
|
||||||
|
|
||||||
|
from app.db import AuditLogORM
|
||||||
from app.orchestrator import WiseClawOrchestrator
|
from app.orchestrator import WiseClawOrchestrator
|
||||||
|
from app.telegram.auth import is_authorized
|
||||||
|
from app.tools.registry import build_tools
|
||||||
|
|
||||||
|
|
||||||
class TelegramBotService:
|
class TelegramBotService:
|
||||||
@@ -75,6 +79,7 @@ class TelegramBotService:
|
|||||||
return
|
return
|
||||||
self.application = Application.builder().token(self.token).build()
|
self.application = Application.builder().token(self.token).build()
|
||||||
self.application.add_handler(CommandHandler("start", self._on_start))
|
self.application.add_handler(CommandHandler("start", self._on_start))
|
||||||
|
self.application.add_handler(CommandHandler("clean_chat", self._on_clean_chat))
|
||||||
self.application.add_handler(CommandHandler("tanisalim", self._on_command_passthrough))
|
self.application.add_handler(CommandHandler("tanisalim", self._on_command_passthrough))
|
||||||
self.application.add_handler(CommandHandler("profilim", self._on_command_passthrough))
|
self.application.add_handler(CommandHandler("profilim", self._on_command_passthrough))
|
||||||
self.application.add_handler(CommandHandler("tercihlerim", self._on_command_passthrough))
|
self.application.add_handler(CommandHandler("tercihlerim", self._on_command_passthrough))
|
||||||
@@ -85,6 +90,7 @@ class TelegramBotService:
|
|||||||
self.application.add_handler(CommandHandler("otomasyon_baslat", self._on_command_passthrough))
|
self.application.add_handler(CommandHandler("otomasyon_baslat", self._on_command_passthrough))
|
||||||
self.application.add_handler(CommandHandler("otomasyon_sil", self._on_command_passthrough))
|
self.application.add_handler(CommandHandler("otomasyon_sil", self._on_command_passthrough))
|
||||||
self.application.add_handler(CommandHandler("notlarima_ekle", self._on_command_passthrough))
|
self.application.add_handler(CommandHandler("notlarima_ekle", self._on_command_passthrough))
|
||||||
|
self.application.add_handler(MessageHandler(filters.Document.ALL | filters.PHOTO, self._on_attachment))
|
||||||
self.application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, self._on_text))
|
self.application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, self._on_text))
|
||||||
await self.application.initialize()
|
await self.application.initialize()
|
||||||
await self.application.bot.set_my_commands(self._telegram_commands())
|
await self.application.bot.set_my_commands(self._telegram_commands())
|
||||||
@@ -110,6 +116,8 @@ class TelegramBotService:
|
|||||||
async def _on_text(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
async def _on_text(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
if update.message is None or update.effective_user is None or update.message.text is None:
|
if update.message is None or update.effective_user is None or update.message.text is None:
|
||||||
return
|
return
|
||||||
|
if await self._maybe_handle_drive_upload_from_reply(update, context):
|
||||||
|
return
|
||||||
typing_task = asyncio.create_task(self._send_typing(update.effective_chat.id, context))
|
typing_task = asyncio.create_task(self._send_typing(update.effective_chat.id, context))
|
||||||
try:
|
try:
|
||||||
reply = await self.process_message_payload(update.effective_user.id, update.message.text)
|
reply = await self.process_message_payload(update.effective_user.id, update.message.text)
|
||||||
@@ -127,6 +135,36 @@ class TelegramBotService:
|
|||||||
for chunk in self._chunk_message(text_reply):
|
for chunk in self._chunk_message(text_reply):
|
||||||
await update.message.reply_text(chunk)
|
await update.message.reply_text(chunk)
|
||||||
|
|
||||||
|
async def _on_attachment(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
|
if update.message is None or update.effective_user is None:
|
||||||
|
return
|
||||||
|
if not self._message_has_supported_attachment(update.message):
|
||||||
|
return
|
||||||
|
if self._looks_like_drive_upload_request(update.message.caption or ""):
|
||||||
|
await self._handle_drive_upload(update, context, update.message)
|
||||||
|
return
|
||||||
|
await update.message.reply_text(
|
||||||
|
"Dosyayi aldim. Google Drive'a yuklemek icin bu mesaja reply yapip `Bunu Google Drive'a yukle` yazabilirsin.",
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _on_clean_chat(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
|
if update.message is None or update.effective_chat is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
chat_id = update.effective_chat.id
|
||||||
|
latest_message_id = update.message.message_id
|
||||||
|
consecutive_failures = 0
|
||||||
|
|
||||||
|
for message_id in range(latest_message_id, 0, -1):
|
||||||
|
try:
|
||||||
|
await context.bot.delete_message(chat_id=chat_id, message_id=message_id)
|
||||||
|
consecutive_failures = 0
|
||||||
|
except Exception:
|
||||||
|
consecutive_failures += 1
|
||||||
|
if consecutive_failures >= 50:
|
||||||
|
break
|
||||||
|
continue
|
||||||
|
|
||||||
async def _on_command_passthrough(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
async def _on_command_passthrough(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
del context
|
del context
|
||||||
if update.message is None or update.effective_user is None or update.message.text is None:
|
if update.message is None or update.effective_user is None or update.message.text is None:
|
||||||
@@ -147,6 +185,119 @@ class TelegramBotService:
|
|||||||
await context.bot.send_chat_action(chat_id=chat_id, action=ChatAction.TYPING)
|
await context.bot.send_chat_action(chat_id=chat_id, action=ChatAction.TYPING)
|
||||||
await asyncio.sleep(4)
|
await asyncio.sleep(4)
|
||||||
|
|
||||||
|
async def _maybe_handle_drive_upload_from_reply(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> bool:
|
||||||
|
if update.message is None or update.effective_user is None or update.message.text is None:
|
||||||
|
return False
|
||||||
|
if not self._looks_like_drive_upload_request(update.message.text):
|
||||||
|
return False
|
||||||
|
reply_target = update.message.reply_to_message
|
||||||
|
if reply_target is None or not self._message_has_supported_attachment(reply_target):
|
||||||
|
await update.message.reply_text(
|
||||||
|
"Google Drive'a yuklemek icin once dosya veya fotograf gonder, sonra o mesaja reply yapip `Bunu Google Drive'a yukle` yaz.",
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
await self._handle_drive_upload(update, context, reply_target)
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def _handle_drive_upload(self, update: Update, context: ContextTypes.DEFAULT_TYPE, source_message: Any) -> None:
|
||||||
|
if update.message is None or update.effective_user is None or update.effective_chat is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
with self.orchestrator_factory() as session:
|
||||||
|
if not is_authorized(session, update.effective_user.id):
|
||||||
|
await update.message.reply_text("This Telegram user is not authorized for WiseClaw.")
|
||||||
|
return
|
||||||
|
|
||||||
|
orchestrator = WiseClawOrchestrator(session)
|
||||||
|
runtime = orchestrator.get_runtime_settings()
|
||||||
|
tools = build_tools(runtime, Path(__file__).resolve().parents[2], session)
|
||||||
|
drive_tool = tools.get("google_drive")
|
||||||
|
if drive_tool is None:
|
||||||
|
await update.message.reply_text("Google Drive araci etkin degil.")
|
||||||
|
return
|
||||||
|
|
||||||
|
temp_file = None
|
||||||
|
try:
|
||||||
|
attachment = await self._download_attachment(context, update.effective_chat.id, source_message)
|
||||||
|
if attachment is None:
|
||||||
|
await update.message.reply_text("Bu mesajdan yuklenebilir bir dosya cikarilamadi.")
|
||||||
|
return
|
||||||
|
temp_file = attachment["local_path"]
|
||||||
|
result = await drive_tool.run(
|
||||||
|
{
|
||||||
|
"action": "upload",
|
||||||
|
"local_path": attachment["local_path"],
|
||||||
|
"filename": attachment["filename"],
|
||||||
|
"mime_type": attachment["mime_type"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
session.add(
|
||||||
|
AuditLogORM(
|
||||||
|
category="tool",
|
||||||
|
message=f"tool:google_drive:{json.dumps({'action': 'upload', 'filename': attachment['filename']}, ensure_ascii=False)}",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if result.get("status") != "ok":
|
||||||
|
message = str(result.get("message", "Google Drive upload failed."))
|
||||||
|
await update.message.reply_text(f"Dosyayi Google Drive'a yukleyemedim: {message}")
|
||||||
|
return
|
||||||
|
|
||||||
|
file_info = result.get("file", {})
|
||||||
|
if not isinstance(file_info, dict):
|
||||||
|
file_info = {}
|
||||||
|
link = str(file_info.get("web_view_link") or file_info.get("web_content_link") or "").strip()
|
||||||
|
file_id = str(file_info.get("id", "")).strip()
|
||||||
|
name = str(file_info.get("name", attachment["filename"])).strip()
|
||||||
|
response_lines = [f"Dosya Google Drive'a yuklendi: {name}"]
|
||||||
|
if link:
|
||||||
|
response_lines.append(f"Link: {link}")
|
||||||
|
if file_id:
|
||||||
|
response_lines.append(f"Dosya ID: {file_id}")
|
||||||
|
await update.message.reply_text("\n".join(response_lines))
|
||||||
|
finally:
|
||||||
|
if temp_file:
|
||||||
|
with suppress(OSError):
|
||||||
|
Path(temp_file).unlink()
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
async def _download_attachment(self, context: ContextTypes.DEFAULT_TYPE, chat_id: int, message: Any) -> dict[str, str] | None:
|
||||||
|
if getattr(message, "document", None) is not None:
|
||||||
|
document = message.document
|
||||||
|
tg_file = await context.bot.get_file(document.file_id)
|
||||||
|
filename = document.file_name or f"telegram_document_{message.message_id}"
|
||||||
|
mime_type = document.mime_type or "application/octet-stream"
|
||||||
|
elif getattr(message, "photo", None):
|
||||||
|
photo = message.photo[-1]
|
||||||
|
tg_file = await context.bot.get_file(photo.file_id)
|
||||||
|
filename = f"telegram_photo_{message.message_id}.jpg"
|
||||||
|
mime_type = "image/jpeg"
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
temp_dir = Path(__file__).resolve().parents[2] / "tmp" / "telegram_uploads"
|
||||||
|
temp_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
safe_name = self._sanitize_filename(filename)
|
||||||
|
local_path = temp_dir / f"{chat_id}_{message.message_id}_{safe_name}"
|
||||||
|
await tg_file.download_to_drive(custom_path=str(local_path))
|
||||||
|
return {
|
||||||
|
"local_path": str(local_path),
|
||||||
|
"filename": filename,
|
||||||
|
"mime_type": mime_type,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _looks_like_drive_upload_request(self, text: str) -> bool:
|
||||||
|
normalized = text.casefold()
|
||||||
|
references_drive = "drive" in normalized or "google drive" in normalized
|
||||||
|
upload_intent = any(term in normalized for term in ("yukle", "yükle", "gonder", "gönder", "upload"))
|
||||||
|
return references_drive and upload_intent
|
||||||
|
|
||||||
|
def _message_has_supported_attachment(self, message: Any) -> bool:
|
||||||
|
return bool(getattr(message, "document", None) is not None or getattr(message, "photo", None))
|
||||||
|
|
||||||
|
def _sanitize_filename(self, filename: str) -> str:
|
||||||
|
cleaned = "".join(char if char.isalnum() or char in {"-", "_", "."} else "_" for char in filename.strip())
|
||||||
|
return cleaned or "attachment.bin"
|
||||||
|
|
||||||
def _chunk_message(self, text: str) -> list[str]:
|
def _chunk_message(self, text: str) -> list[str]:
|
||||||
if len(text) <= self.MAX_MESSAGE_LEN:
|
if len(text) <= self.MAX_MESSAGE_LEN:
|
||||||
return [text]
|
return [text]
|
||||||
@@ -170,6 +321,7 @@ class TelegramBotService:
|
|||||||
BotCommand("profilim", "Kayitli profil ozetimi goster (wc)"),
|
BotCommand("profilim", "Kayitli profil ozetimi goster (wc)"),
|
||||||
BotCommand("tercihlerim", "Kayitli iletisim tercihlerini goster (wc)"),
|
BotCommand("tercihlerim", "Kayitli iletisim tercihlerini goster (wc)"),
|
||||||
BotCommand("tanisalim_sifirla", "Tanisma profilini sifirla (wc)"),
|
BotCommand("tanisalim_sifirla", "Tanisma profilini sifirla (wc)"),
|
||||||
|
BotCommand("clean_chat", "Telegram ekranindaki mesajlari temizle (wc)"),
|
||||||
BotCommand("otomasyon_ekle", "Yeni otomasyon wizard'ini baslat (wc)"),
|
BotCommand("otomasyon_ekle", "Yeni otomasyon wizard'ini baslat (wc)"),
|
||||||
BotCommand("otomasyonlar", "Otomasyon listesini goster (wc)"),
|
BotCommand("otomasyonlar", "Otomasyon listesini goster (wc)"),
|
||||||
BotCommand("otomasyon_durdur", "Bir otomasyonu durdur: /otomasyon_durdur <id> (wc)"),
|
BotCommand("otomasyon_durdur", "Bir otomasyonu durdur: /otomasyon_durdur <id> (wc)"),
|
||||||
|
|||||||
Reference in New Issue
Block a user