feat(workflow): implement target channel personalities, raw review cards, on-demand AI rewrites and multi-target dispatch

This commit is contained in:
mamad
2026-08-27 22:11:33 +03:30
parent 4f5570850e
commit 26cd91a48f
10 changed files with 695 additions and 699 deletions
+28 -23
View File
@@ -6,9 +6,8 @@ from telethon.errors import SessionPasswordNeededError
from telethon.tl.types import MessageMediaPhoto, MessageMediaDocument
from db.repository import Repository
from core.dedup import compute_content_hash, compute_file_hash
from services.ai_processor import AIProcessor
from core.queue import RedisQueue
from core.metrics import COLLECTED_POSTS_TOTAL
from core.metrics import COLLECTED_POSTS_TOTAL, SOURCE_ACTIVITY_TOTAL
from core.proxy import get_telegram_proxy
logger = logging.getLogger(__name__)
@@ -20,7 +19,7 @@ class CollectorService:
def __init__(
self,
repo: Repository,
ai_processor: AIProcessor,
on_post_received: Optional[Callable[[int], Awaitable[None]]] = None,
queue: Optional[RedisQueue] = None,
api_id: Optional[int] = None,
api_hash: Optional[str] = None,
@@ -28,7 +27,7 @@ class CollectorService:
session_name: Optional[str] = None,
):
self.repo = repo
self.ai_processor = ai_processor
self.on_post_received = on_post_received
self.queue = queue
self.api_id = api_id or int(os.getenv("API_ID", "0"))
self.api_hash = api_hash or os.getenv("API_HASH", "")
@@ -56,14 +55,14 @@ class CollectorService:
sent = await self.client.send_code_request(self.phone)
self.phone_code_hash = sent.phone_code_hash
await notify_fn(
f"🔐 <b>Collector Userbot Login Required</b>\n\n"
f"A login code was sent to phone <code>{self.phone}</code>.\n\n"
f"Please reply with: <code>/code &lt;your_code&gt;</code>\n"
f"(Or <code>/password &lt;2fa_password&gt;</code> if 2FA is enabled)."
f"🔐 <b>نیاز به ورود ربات جمع‌آوری‌کننده</b>\n\n"
f"کد تایید تلگرام به شماره <code>{self.phone}</code> ارسال شد.\n\n"
f"لطفا با دستور زیر پاسخ دهید:\n"
f"<code>/code 12345</code>"
)
except Exception as e:
logger.error(f"Failed to send login code request: {e}")
await notify_fn(f"Failed to request login code: {e}")
await notify_fn(f"خطا در ارسال کد ورود: {e}")
return False
async def submit_code(self, code: str) -> str:
@@ -75,20 +74,20 @@ class CollectorService:
await self.client.sign_in(phone=self.phone, code=code, phone_code_hash=self.phone_code_hash)
me = await self.client.get_me()
self._register_handlers()
return f"Logged in successfully as <b>{me.first_name}</b> (@{me.username or 'none'}). Collector is now active!"
return f"ورود موفقیت‌آمیز بود! حساب فعال: <b>{me.first_name}</b> (@{me.username or 'ندارد'})."
except SessionPasswordNeededError:
return "🔐 <b>Two-Factor Authentication (2FA) is enabled.</b> Please send: <code>/password &lt;your_2fa_password&gt;</code>"
return "🔐 <b>رمز دو مرحله‌ای فعال است.</b> لطفا با این دستور رمز را وارد کنید: <code>/password رمز_عبور</code>"
except Exception as e:
return f"Login failed: {e}"
return f"خطا در ورود: {e}"
async def submit_password(self, password: str) -> str:
try:
await self.client.sign_in(password=password)
me = await self.client.get_me()
self._register_handlers()
return f"2FA Verified! Logged in as <b>{me.first_name}</b> (@{me.username or 'none'}). Collector is now active!"
return f"تایید دو مرحله‌ای موفق بود! حساب فعال: <b>{me.first_name}</b>."
except Exception as e:
return f"2FA verification failed: {e}"
return f"خطا در تایید رمز دو مرحله‌ای: {e}"
def _register_handlers(self):
if self._handlers_registered:
@@ -141,11 +140,13 @@ class CollectorService:
if post_id:
COLLECTED_POSTS_TOTAL.labels(source_channel_id=str(chat_id)).inc()
logger.info(f"Collected new post ID {post_id} from channel {chat_id}")
SOURCE_ACTIVITY_TOTAL.labels(channel_id=str(chat_id), title=source.title or 'Unknown').inc()
logger.info(f"Collected raw post ID {post_id} from source channel {chat_id}")
if self.queue:
await self.queue.push(post_id)
else:
await self.ai_processor.process_post(post_id)
elif self.on_post_received:
await self.on_post_received(post_id)
except Exception as e:
logger.error(f"Error handling message from {event.chat_id}: {e}", exc_info=True)
@@ -158,9 +159,11 @@ class CollectorService:
"""Scrape historical messages from a source channel."""
if not self.client.is_connected() or not await self.client.is_user_authorized():
if progress_callback:
await progress_callback("Collector Userbot is not authorized. Please log in first.")
await progress_callback("ربات متصل نیست. لطفا ابتدا لاگین کنید.")
return 0
source = await self.repo.get_source_by_channel_id(channel_id)
source_title = source.title if source else str(channel_id)
collected_count = 0
skipped_count = 0
@@ -210,23 +213,25 @@ class CollectorService:
if post_id:
collected_count += 1
COLLECTED_POSTS_TOTAL.labels(source_channel_id=str(channel_id)).inc()
logger.info(f"Backfilled historical post ID {post_id} from {channel_id}")
SOURCE_ACTIVITY_TOTAL.labels(channel_id=str(channel_id), title=source_title).inc()
logger.info(f"Backfilled raw post ID {post_id} from {channel_id}")
if self.queue:
await self.queue.push(post_id)
else:
await self.ai_processor.process_post(post_id)
elif self.on_post_received:
await self.on_post_received(post_id)
else:
skipped_count += 1
if progress_callback:
await progress_callback(
f"Scraped <b>{collected_count}</b> new posts from <code>{channel_id}</code> and queued in Redis! (Skipped {skipped_count} existing/empty)."
f"تعداد <b>{collected_count}</b> پست جدید از <code>{channel_id}</code> دریافت و در کانال ادمین قرار گرفت! (رد شده تکراری: {skipped_count})."
)
return collected_count
except Exception as e:
logger.error(f"Error scraping history from {channel_id}: {e}", exc_info=True)
if progress_callback:
await progress_callback(f"Error scraping channel <code>{channel_id}</code>: {e}")
await progress_callback(f"خطا در دریافت پست‌های کانال <code>{channel_id}</code>: {e}")
return collected_count
async def stop(self):