# ============================================ # utils/html.py — أدوات تنسيق HTML لتيليجرام # ============================================ # لماذا HTML وليس MarkdownV2؟ # - HTML أقل أخطاءً: نحتاج escape لثلاثة رموز فقط (&, <, >) # - يدعم
و
# - أوضح في القراءة داخل الكود # ============================================ def esc(text: str) -> str: """ Escape رموز HTML الخاصة الثلاثة. يجب استدعاؤها على أي نص يأتي من مصدر خارجي قبل إدراجه في رسالة HTML. """ if not text: return "" return ( str(text) .replace("&", "&") .replace("<", "<") .replace(">", ">") ) def b(text: str) -> str: """نص عريض""" return f"{esc(text)}" def i(text: str) -> str: """نص مائل""" return f"{esc(text)}" def u(text: str) -> str: """نص تحته خط""" return f"{esc(text)}" def code(text: str) -> str: """نص كـ code""" return f"{esc(text)}" def link(label: str, url: str) -> str: """رابط مخفي""" return f'{esc(label)}' def blockquote(text: str, expandable: bool = False) -> str: """ blockquote عادي أو قابل للتوسيع. expandable=True: يُظهر زر "اقرأ المزيد" إذا كان الحجم كبيراً. """ if expandable: return f"
{text}
" return f"
{text}
" def row(*cells: str, sep: str = " | ") -> str: """صف بيانات: المفتاح | القيمة""" return sep.join(str(c) for c in cells if c) # ════════════════════════════════════════ # بناء رسائل اللعبة # ════════════════════════════════════════ def build_game_info_message(details: dict, game_link_in_lib: str | None = None) -> str: """ بناء رسالة تفاصيل اللعبة الكاملة بتنسيق HTML. الترتيب: 1. رابط الصورة (كرابط نصي → يولّد Preview تلقائياً) 2. اسم اللعبة (عريض) 3. معلومات أساسية داخل blockquote 4. اللغات داخل blockquote expandable 5. التصنيفات والخصائص داخل blockquote expandable 6. القصة (3 أسطر) """ parts = [] # ── رابط الصورة فوق الرسالة ── cover_url = details.get("cover_url", "") if cover_url: parts.append(cover_url) parts.append("") # ── اسم اللعبة ── parts.append(f"🎮 {esc(details.get('name', ''))}") parts.append("") # ── المعلومات الأساسية ── basic_lines = [] if details.get("release_date"): basic_lines.append(f"• تاريخ الإصدار: {esc(details['release_date'])}") if details.get("developer"): basic_lines.append(f"• المطور: {esc(details['developer'])}") if details.get("publisher") and details["publisher"] != details.get("developer"): basic_lines.append(f"• الناشر: {esc(details['publisher'])}") if details.get("platforms_str"): basic_lines.append(f"• المنصات: {esc(details['platforms_str'])}") # التقييمات ur = details.get("user_rating", "") uc = details.get("user_rating_count", 0) cr = details.get("crit_rating", "") cc = details.get("crit_rating_count", 0) rating_line_parts = [] if ur: rating_line_parts.append(f"{esc(ur)} ⭐ (بناء على {esc(str(uc))} مستخدم)") if cr: rating_line_parts.append(f"{esc(cr)} 🌟 (بناء على {esc(str(cc))} نقاد)") if rating_line_parts: basic_lines.append(f"• التقييمات: {' — '.join(rating_line_parts)}") # التصنيف العمري age_lines = details.get("age_rating_lines", []) if age_lines: basic_lines.append("• التصنيف العمري:") for al in age_lines: basic_lines.append(f" {esc(al)}") if basic_lines: parts.append(blockquote("\n".join(basic_lines))) parts.append("") # ── اللغات (expandable) ── lang_sections = details.get("language_sections", {}) if any(lang_sections.values()): lang_lines = [] if lang_sections.get("audio"): lang_lines.append(f"🔊 الصوت / الدبلجة:") lang_lines.append(esc(" • ".join(lang_sections["audio"]))) if lang_sections.get("interface"): lang_lines.append(f"🖥️ الواجهة:") lang_lines.append(esc(" • ".join(lang_sections["interface"]))) if lang_sections.get("subtitles"): lang_lines.append(f"📝 الترجمة النصية:") lang_lines.append(esc(" • ".join(lang_sections["subtitles"]))) if lang_lines: parts.append(f"اللغات المدعومة:") parts.append(blockquote("\n".join(lang_lines), expandable=True)) parts.append("") # ── التصنيفات والخصائص (expandable) ── cat_lines = [] if details.get("genres_ar"): cat_lines.append(f"• الأنواع: {esc(details['genres_ar'])}") if details.get("themes_ar"): cat_lines.append(f"• المواضيع: {esc(details['themes_ar'])}") if details.get("game_modes_ar"): cat_lines.append(f"• أسلوب اللعب: {esc(details['game_modes_ar'])}") if details.get("perspectives_ar"): cat_lines.append(f"• منظور اللاعب: {esc(details['perspectives_ar'])}") if details.get("engine"): cat_lines.append(f"• المحرك: {esc(details['engine'])}") if details.get("franchise"): cat_lines.append(f"• السلسلة: {esc(details['franchise'])}") if details.get("parent_game"): cat_lines.append(f"• هو فرعي من: {esc(details['parent_game'])}") if details.get("collection"): cat_lines.append(f"• سلسلة أعمال: {esc(details['collection'])}") if cat_lines: parts.append(blockquote("\n".join(cat_lines), expandable=True)) parts.append("") # ── القصة ── if details.get("summary"): parts.append(f"📖 نبذة عن القصة:") parts.append(esc(details["summary"])) # ── رابط التعريب ── if game_link_in_lib: parts.append("") parts.append(f"🎌 {link('شاهد التعريب في مكتبتنا', game_link_in_lib)}") return "\n".join(p for p in parts) def build_pc_specs_message(game_name: str, game_info: dict, pc_specs: dict) -> str: """ رسالة متطلبات تشغيل PC كاملة بتنسيق HTML. كل حقل في blockquote منفصل حسب التصميم المطلوب. """ parts = [] # ── رأس الرسالة ── parts.append(f"🎮 {esc(game_name)}") # هاشتاقات الأنواع genres_raw = game_info.get("genres_ar", "") if genres_raw: tags = [] for g in genres_raw.split("|"): g = g.strip() # إزالة الإيموجي للهاشتاق tag_text = ''.join(c for c in g if c.isalpha() or c in ' _').strip().replace(' ', '_') if tag_text: tags.append(f"#{tag_text}") if tags: parts.append(" ".join(tags[:3])) # التصنيف العمري وتاريخ الإصدار age_lines = game_info.get("age_rating_lines", []) if age_lines: parts.append(f"🔞 تصنيف العمر: {esc(age_lines[0].split()[1] if len(age_lines[0].split()) > 1 else age_lines[0])}") if game_info.get("release_date"): parts.append(f"🗓️ الإصدار: {esc(game_info['release_date'])}") parts.append("") # ── الحد الأدنى ── min_specs = pc_specs.get("minimum", {}) if min_specs: parts.append(f"🔹 الحد الأدنى (Minimum)") _SPEC_LABELS_AR = { "OS": "نظام التشغيل", "CPU": "المعالج (CPU)", "RAM": "الذاكرة (RAM)", "GPU": "كرت الشاشة (GPU)", "DirectX": "DirectX", "Storage": "التخزين", "Network": "الشبكة", "Sound": "كارت الصوت", } for key, val in min_specs.items(): if val: label = _SPEC_LABELS_AR.get(key, key) parts.append(blockquote(f"{esc(label)} | {esc(str(val))}")) # ── الموصى به ── rec_specs = pc_specs.get("recommended", {}) if rec_specs: parts.append("") parts.append(f"🔹 الموصى بها (Recommended)") _SPEC_LABELS_AR = { "OS": "نظام التشغيل", "CPU": "المعالج (CPU)", "RAM": "الذاكرة (RAM)", "GPU": "كرت الشاشة (GPU)", "DirectX": "DirectX", "Storage": "التخزين", "Network": "الشبكة", "Sound": "كارت الصوت", } for key, val in rec_specs.items(): if val: label = _SPEC_LABELS_AR.get(key, key) parts.append(blockquote(f"{esc(label)} | {esc(str(val))}")) if not min_specs and not rec_specs: parts.append(blockquote("لم يتم العثور على متطلبات رسمية لهذه اللعبة.")) return "\n".join(parts) def build_new_post_notification( game_title: str, platform: str, game_link: str, channel_username: str ) -> str: """رسالة إشعار تعريب جديد (HTML)""" emoji = "🖥" if platform == "pc" else "🎮" if platform == "ps" else "📁" plat_ar = "PC" if platform == "pc" else "PlayStation" if platform == "ps" else "عام" ch_part = f"\n📢 @{esc(channel_username)}" if channel_username else "" return ( f"🔔 تعريب جديد!\n\n" f"🎮 {esc(game_title)}\n" f"{emoji} المنصة: {esc(plat_ar)}\n" f"🔗 شاهد التعريب" f"{ch_part}" ) def build_update_notification( game_title: str, update_note: str, game_link: str, channel_username: str ) -> str: """رسالة إشعار تحديث تعريب (HTML)""" ch_part = f"\n📢 @{esc(channel_username)}" if channel_username else "" note_part = f"\n✏️ {esc(update_note)}" if update_note else "" return ( f"🔄 تم تحديث تعريب!\n\n" f"🎮 {esc(game_title)}" f"{note_part}\n" f"🔗 شاهد التحديث" f"{ch_part}" )