feat: add source channel context history configuration and original sent timestamp

This commit is contained in:
mamad
2026-08-28 21:39:02 +03:30
parent c86fd32726
commit 462ad3be93
8 changed files with 279 additions and 18 deletions
+83 -3
View File
@@ -62,6 +62,9 @@ def get_source_fetch_buttons(channel_id: int, source_id: Optional[int] = None):
]
]
if source_id is not None:
rows.append([
Button.inline("📜 تنظیم پیام‌های زمینه (Context)", data=f"src_ctx_menu:{source_id}"),
])
rows.append([
Button.inline("📁 تعیین دسته‌بندی", data=f"src_cat:{source_id}"),
Button.inline("🗑 حذف این کانال مبدا", data=f"del_src:{source_id}")
@@ -852,20 +855,56 @@ class AdminBotService:
if auto_targets else "🛑 <b>ارسال خودکار:</b> غیرفعال"
)
collected = await self.repo.count_posts_from_source(source.channel_id)
ctx_count = getattr(source, "context_message_count", 0)
ctx_label = f"🟢 {ctx_count} پیام اخیر" if ctx_count > 0 else "⚪️ غیرفعال (فقط پست جاری)"
card = (
f"📢 <b>{source.title or 'کانال مبدا'}</b>\n\n"
f"• 🆔 <b>شناسه کانال:</b> <code>{source.channel_id}</code>\n"
f"• 🔗 <b>یوزرنیم:</b> @{source.username or 'ندارد'}\n"
f"• 📁 <b>دسته‌بندی:</b> <b>{cat_name}</b>\n"
f"• 📜 <b>پیام‌های زمینه (Context):</b> <b>{ctx_label}</b>\n"
f"• 📥 <b>پست‌های دریافت‌شده:</b> <b>{collected}</b>\n"
f"{auto_line}\n\n"
"<i>👇 برای دریافت پست‌های گذشته یکی از گزینه‌ها را انتخاب کنید:</i>"
"<i>👇 برای تنظیمات زمینه یا دریافت پست‌های گذشته یکی از گزینه‌ها را انتخاب کنید:</i>"
)
buttons = get_source_fetch_buttons(source.channel_id, source.id)
buttons.append([Button.inline("🔙 بازگشت به لیست کانال‌های مبدا", data="list_src")])
return card, buttons
async def _render_source_context_menu(self, source_id: int):
source = await self.repo.get_source_by_id(source_id)
if not source:
return "❌ کانال مبدا یافت نشد.", []
current_cnt = getattr(source, "context_message_count", 0)
status_text = f"<b>{current_cnt} پیام اخیر</b>" if current_cnt > 0 else "<b>غیرفعال (۰ پیام)</b>"
text = (
f"📜 <b>تنظیم تعداد پیام‌های زمینه (Context History)</b>\n"
f"📢 کانال مبدا: <b>{source.title or source.channel_id}</b>\n\n"
f"با فعال‌سازی این قابلیت، هنگام بازنویسی هر پست ورودی توسط هوش مصنوعی، تعداد مشخصی از پیام‌های قبلی این کانال نیز جهت درک پیوستگی موضوعی و خط داستانی در اختیار مدل قرار می‌گیرد.\n\n"
f"• وضعیت کنونی: {status_text}\n\n"
f"<i>تعداد پیام مورد نظر را انتخاب کنید:</i>"
)
buttons = [
[
Button.inline("0️⃣ خاموش (۰)", data=f"src_ctx_set:{source.id}:0"),
Button.inline("1️⃣ ۱ پیام", data=f"src_ctx_set:{source.id}:1"),
Button.inline("2️⃣ ۲ پیام", data=f"src_ctx_set:{source.id}:2"),
],
[
Button.inline("3️⃣ ۳ پیام", data=f"src_ctx_set:{source.id}:3"),
Button.inline("5️⃣ ۵ پیام", data=f"src_ctx_set:{source.id}:5"),
Button.inline("🔟 ۱۰ پیام", data=f"src_ctx_set:{source.id}:10"),
],
[
Button.inline("🔙 بازگشت به کانال مبدا", data=f"src_view:{source.id}")
]
]
return text, buttons
async def _render_auto_sources(self, target_id: int):
"""Toggle screen listing every source with its on/off state for this target."""
@@ -1012,6 +1051,15 @@ class AdminBotService:
if not targets:
return 0
source = await self.repo.get_source_by_channel_id(post.source_channel_id)
context_posts = []
if source and getattr(source, "context_message_count", 0) > 0:
context_posts = await self.repo.get_recent_source_posts(
source_channel_id=post.source_channel_id,
limit=source.context_message_count,
exclude_post_id=post.id
)
routed = 0
ai_rejected_reasons = []
for target in targets:
@@ -1019,7 +1067,11 @@ class AdminBotService:
text = post.raw_text or ""
if self.ai_processor:
rewrite_res = await self.ai_processor.rewrite_for_target(
text, target, has_media=bool(post.media_path), image_path=post.media_path
text,
target,
has_media=bool(post.media_path),
image_path=post.media_path,
context_posts=context_posts or None
)
if getattr(rewrite_res, "is_rejected", False):
reason = getattr(rewrite_res, "rejection_reason", "رد شده توسط هوش مصنوعی")
@@ -2202,6 +2254,21 @@ class AdminBotService:
await event.edit(card, parse_mode="html", buttons=buttons or None)
await event.answer()
elif data.startswith("src_ctx_menu:"):
src_id = int(data.split(":")[1])
text, buttons = await self._render_source_context_menu(src_id)
await event.edit(text, parse_mode="html", buttons=buttons)
await event.answer()
elif data.startswith("src_ctx_set:"):
_, src_id_str, cnt_str = data.split(":")
src_id = int(src_id_str)
cnt = int(cnt_str)
await self.repo.update_source_context_count(src_id, cnt)
card, buttons = await self._render_source_config(src_id)
await event.edit(card, parse_mode="html", buttons=buttons or None)
await event.answer(f"✅ تعداد پیام‌های زمینه روی {cnt} تنظیم شد.")
elif data.startswith("trg_view:"):
target_id = int(data.split(":")[1])
card, buttons = await self._render_target_config(target_id)
@@ -2387,8 +2454,21 @@ class AdminBotService:
except Exception:
pass
source = await self.repo.get_source_by_channel_id(post.source_channel_id)
context_posts = []
if source and getattr(source, "context_message_count", 0) > 0:
context_posts = await self.repo.get_recent_source_posts(
source_channel_id=post.source_channel_id,
limit=source.context_message_count,
exclude_post_id=post.id
)
rewrite_res = await self.ai_processor.rewrite_for_target(
post.raw_text or "", target, has_media=bool(post.media_path), image_path=post.media_path
post.raw_text or "",
target,
has_media=bool(post.media_path),
image_path=post.media_path,
context_posts=context_posts or None
)
rewritten_text = str(rewrite_res)
cache_key = f"{post_id}:{target_id}"
+33 -5
View File
@@ -78,11 +78,12 @@ CRITICAL RULES:
1. Completely REMOVE all original channel usernames (e.g. @source_channel), sponsor tags, author watermarks, and source links.
2. __LANGUAGE_INSTRUCTION__
3. Rewrite and format the post to fully embody the target personality with appropriate emojis and clear paragraph spacing.
4. If a custom footer/tag is provided below, append it cleanly at the very end of the post:
4. If recent channel context posts are provided in the user prompt, use them to understand recent narrative flow, story progression, and context. Focus your rewritten output specifically on transforming the NEW incoming post.
5. If a custom footer/tag is provided below, append it cleanly at the very end of the post:
__CUSTOM_FOOTER__
5. TELEGRAM CHARACTER LIMIT CONSTRAINT:
6. TELEGRAM CHARACTER LIMIT CONSTRAINT:
__LENGTH_LIMIT_RULE__
6. POST EVALUATION & REJECTION CRITERIA:
7. POST EVALUATION & REJECTION CRITERIA:
- Default decision is "accept". You should adapt and rewrite normal posts even if their original tone or subject is diverse.
- You should ONLY reject a post if it is:
* Pure spam, unrelated scam/gambling/phishing ads
@@ -249,8 +250,9 @@ class AIProcessor:
target: TargetChannel,
has_media: bool = False,
image_path: Optional[str] = None,
context_posts: Optional[List[Post]] = None,
) -> RewriteResult:
"""Rewrite raw text according to target channel's language, personality, custom prompt commands, length limits, and optional image."""
"""Rewrite raw text according to target channel's language, personality, custom prompt commands, length limits, context history, and optional image."""
if not raw_text and not image_path:
return RewriteResult(decision="reject", rejection_reason="متن پیام و تصویر هر دو خالی هستند", rewritten_text="")
@@ -269,7 +271,33 @@ class AIProcessor:
has_media=has_media,
)
user_prompt_text = f"متن اصلی پست برای بازنویسی و تبدیل به لحن و استایل کانال مقصد:\n\n{raw_text}" if raw_text else "لطفاً با توجه به تصویر پیوست، یک متن جذاب و مناسب برای کانال بنویسید."
context_block = ""
if context_posts:
context_entries = []
for idx, cp in enumerate(context_posts, start=1):
post_date = cp.source_created_at or cp.created_at or "اخیر"
clean_snippet = (cp.raw_text or "").strip()
if len(clean_snippet) > 300:
clean_snippet = clean_snippet[:300] + "..."
context_entries.append(f"[{idx}] (تاریخ/زمان: {post_date}):\n{clean_snippet}")
context_block = (
"📜 پیام‌ها و پست‌های قبلی/اخیر این کانال (جهت اطلاع از خط داستانی و پیوستگی موضوع):\n"
+ "\n\n".join(context_entries)
+ "\n\n━━━━━━━━━━━━━━━━━━━━\n"
)
if raw_text:
user_prompt_text = (
f"{context_block}"
f"📥 پست جدید ورودی برای بازنویسی و انتشار در کانال مقصد:\n\n{raw_text}\n\n"
f"راهنما: با توجه به پیام‌های اخیر فوق، پست جدید را طوری بازنویسی کنید که با روند موضوعات همخوانی داشته و لحن کانال مقصد را به بهترین شکل بازتاب دهد."
)
else:
user_prompt_text = (
f"{context_block}"
f"📥 پست جدید ورودی شامل تصویر است. لطفاً با توجه به تصویر پیوست و زمینه پیام‌های اخیر، یک متن جذاب و متناسب برای کانال مقصد بنویسید."
)
try:
res = await self.llm.generate_json(
+2
View File
@@ -256,6 +256,7 @@ class CollectorService:
is_duplicate=is_duplicate,
duplicate_of_id=duplicate_of_id,
similarity_reason=similarity_reason,
source_created_at=getattr(event.message, "date", None),
)
if post_id:
@@ -371,6 +372,7 @@ class CollectorService:
is_duplicate=is_duplicate,
duplicate_of_id=duplicate_of_id,
similarity_reason=similarity_reason,
source_created_at=getattr(message, "date", None),
)
if post_id: