P9(5): restore upstream Telegram bot (settings, backup, notifications)

Per user decision 2026-10-21 (reverses the 2026-10-20 drop-Telegram
call): port the upstream 3x-ui v3.7.0-generation Telegram bot wholesale
instead of rewriting a minimal set, adapted to this fork.

- new internal/web/service/tgbot package (telego): bot lifecycle,
  commands, reports, callback router, send helpers; token validation via
  telego; custom API server URL passes the fork's SSRF sanitizer
  (loopback/private bases fall back to the default endpoint, logged)
- settings: full upstream key set (tgBotEnable/Token/Proxy/APIServer/
  ChatId, tgRunTime, tgBotBackup, tgCpu/tgMemory, tgLang,
  tgEnabledEvents) with getters/setters; settings page gains a Telegram
  tab + sidebar entry; token shown masked, clearTgBotToken honored
- wiring (web.go): stats-notify cron registered from tgRunTime when the
  bot is enabled; CheckHashStorage job; CPU/memory alarm jobs gated on
  tgEnabledEvents + thresholds; login notifications via event bus;
  POST /panel/api/setting/testTgBot and /panel/api/backuptotgbot
- lite adaptation: StatsNotifyJob no longer gates on a local Xray core
  (this panel never runs one) so the periodic report and the scheduled
  DB backup (sendDocument) can actually fire
- translations: upstream TG keys restored across all locales

End-to-end smoke (real binary + fake Telegram API over HTTPS at a
public-looking address, browser form wire format): bot starts on
settings change (getMe), testTgBot delivers, panel restart re-arms the
bot, tgRunTime='@every 1m' cron really fires sendDocument carrying the
panel SQLite DB (magic verified) plus the report sendMessage, login
notification delivered, dead-endpoint failure is logged not silent.
13/13 PASS — see P9-REPORT.md.
This commit is contained in:
Muse
2026-10-21 17:16:32 +00:00
parent 5031a70ccb
commit ceca96571d
49 changed files with 8351 additions and 50 deletions
+216 -3
View File
@@ -88,7 +88,8 @@
"noExpiry": "بدون انتهاء",
"copyAllConfigs": "نسخ جميع الإعدادات",
"copyAllConfigsCopied": "تم نسخ جميع الإعدادات",
"email": "البريد"
"email": "البريد",
"individualLinks": "روابط فردية"
},
"menu": {
"theme": "الثيم",
@@ -1168,7 +1169,44 @@
"httpMethodDesc": "الأسلوب المستخدم في طلبات probe.",
"note": "تحمل موزّعات leastPing/leastLoad دائمًا burstObservatory. يخصّص هذا المفتاح معاملات probe — أوقفه لاستخدام الإعدادات الافتراضية المدمجة. تُطبَّق التغييرات بعد إعادة تشغيل اللوحة."
}
}
},
"TGBotSettings": "بوت تيليجرام",
"notifications": "الإشعارات",
"notifyTime": {
"custom": "مخصص (crontab)",
"daily": "@daily — كل يوم الساعة 00:00",
"every": "@every — التكرار ضمن فترة",
"hourly": "@hourly — كل ساعة",
"hours": "ساعات",
"interval": "الفاصل الزمني",
"minutes": "دقائق",
"monthly": "@monthly — كل شهر",
"seconds": "ثوانٍ",
"unit": "الوحدة",
"weekly": "@weekly — كل أسبوع"
},
"telegramAPIServer": "خادم API لتيليجرام",
"telegramAPIServerDesc": "سيرفر Telegram API المستخدم. سيبه فاضي لاستخدام الافتراضي.",
"telegramBotEnable": "تفعيل بوت Telegram",
"telegramBotEnableDesc": "يفعل بوت Telegram.",
"telegramBotLanguage": "لغة بوت Telegram",
"telegramChatId": "ID شات الأدمن",
"telegramChatIdDesc": "ID شات الأدمن في Telegram. (مفصول بفواصل)(تقدر تجيبه من {'@'}userinfobot) أو (استخدم '/id' في البوت)",
"telegramNotifyTime": "وقت الإشعار",
"telegramNotifyTimeDesc": "عدد مرات إرسال البوت للتقارير الدورية. اختر فترة جاهزة، أو اختر «مخصص» لإدخال تعبير crontab.",
"telegramToken": "رمز تيليجرام",
"telegramTokenConfigured": "مهيأ؛ اتركه فارغاً للاحتفاظ بالتوكن الحالي.",
"telegramTokenDesc": "توكن البوت اللي جبت من '{'@'}BotFather'.",
"telegramTokenPlaceholder": "مهيأ — أدخل توكن جديد لاستبداله",
"testTgBot": "إرسال رسالة تجريبية",
"tgBotNotEnabled": "بوت Telegram غير مفعّل",
"tgBotNotRunning": "بوت Telegram لا يعمل",
"tgEventBusNotify": "إشعارات الأحداث عبر Telegram",
"tgEventBusNotifyDesc": "اختر الأحداث التي تُطلق إشعارات Telegram",
"tgNotifyBackup": "نسخة احتياطية لقاعدة البيانات",
"tgNotifyBackupDesc": "ابعت ملف النسخة الاحتياطية لقاعدة البيانات مع التقرير.",
"tgTestFailed": "فشل اختبار Telegram",
"tgTestSuccess": "تم إرسال رسالة تجريبية إلى Telegram"
},
"xray": {
"save": "احفظ",
@@ -1657,7 +1695,182 @@
}
},
"tgbot": {
"noIpRecord": "❗ لا يوجد سجل IP!"
"noIpRecord": "❗ لا يوجد سجل IP!",
"keyboardClosed": "❌ لوحة المفاتيح مغلقة!",
"noResult": "❗ لا يوجد نتائج!",
"noQuery": "❌ لم يتم العثور على الاستعلام! يرجى استخدام الأمر مرة أخرى!",
"wentWrong": "❌ حدث خطأ ما!",
"noInbounds": "❗ لم يتم العثور على أي وارد!",
"unlimited": "♾ غير محدود (إعادة تعيين)",
"add": "إضافة",
"month": "شهر",
"months": "أشهر",
"days": "أيام",
"hours": "ساعات",
"minutes": "دقائق",
"unknown": "غير معروف",
"inbounds": "الواردات",
"clients": "العملاء",
"offline": "🔴 غير متصل",
"online": "🟢 متصل",
"commands": {
"unknown": "❗ أمر مش معروف.",
"pleaseChoose": "👇 من فضلك اختار:\r\n",
"help": "🤖 أهلا بيك في البوت! البوت ده معمول عشان يديك بيانات معينة من البانل ويسمحلك بالتعديلات.",
"start": "👋 أهلا <i>{{ .Firstname }}</i>.\r\n",
"welcome": "🤖 أهلا بيك في بوت إدارة <b>{{ .Hostname }}</b>.\r\n",
"status": "✅ البوت شغال!",
"usage": "❗ من فضلك ادخل نص للتبحث عنه!",
"getID": "🆔 الـ ID بتاعك: <code>{{ .ID }}</code>",
"helpAdminCommands": "عشان تعيد تشغيل Xray Core:\r\n<code>/restart</code>\r\n\r\nعشان تدور على إيميل عميل:\r\n<code>/usage [Email]</code>\r\n\r\nعشان تدور على إدخالات (مع إحصائيات العملاء):\r\n<code>/inbound [Remark]</code>\r\n\r\nID شات Telegram:\r\n<code>/id</code>",
"helpClientCommands": "عشان تدور على الإحصائيات، استخدم الأمر ده:\r\n\r\n<code>/usage [Email]</code>\r\n\r\nID شات Telegram:\r\n<code>/id</code>",
"restartUsage": "\r\n\r\n<code>/restart</code>",
"restartSuccess": "✅ العملية نجحت!",
"restartFailed": "❗ حصل خطأ في العملية.\r\n\r\n<code>Error: {{ .Error }}</code>.",
"xrayNotRunning": "❗ Xray Core مش شغال.",
"startDesc": "عرض القائمة الرئيسية",
"helpDesc": "مساعدة البوت",
"statusDesc": "التحقق من حالة البوت",
"idDesc": "عرض معرف Telegram الخاص بك",
"usageDesc": "عرض استهلاك العميل: /usage البريد",
"inboundDesc": "البحث في الواردات: /inbound الاسم (مشرف)",
"restartDesc": "إعادة تشغيل نواة Xray (مشرف)",
"clearallDesc": "تصفير استهلاك جميع العملاء (مشرف)"
},
"messages": {
"cpuThreshold": "حمل المعالج {{ .Percent }}% عدى الحد المسموح ({{ .Threshold }}%)",
"selectUserFailed": "❌ حصل خطأ في اختيار المستخدم!",
"userSaved": "✅ حفظت بيانات مستخدم Telegram.",
"loginSuccess": "✅ تسجيل الدخول للبانل تم بنجاح.\r\n",
"loginFailed": "❗️فشل محاولة تسجيل الدخول للبانل.\r\n",
"report": "🕰 التقارير المجدولة: {{ .RunTime }}\r\n",
"datetime": "⏰ التاريخ والوقت: {{ .DateTime }}\r\n",
"hostname": "💻 المضيف: {{ .Hostname }}\r\n",
"version": "🚀 نسخة 3X-UI: {{ .Version }}\r\n",
"xrayVersion": "📡 نسخة Xray: {{ .XrayVersion }}\r\n",
"ipv6": "🌐 IPv6: {{ .IPv6 }}\r\n",
"ipv4": "🌐 IPv4: {{ .IPv4 }}\r\n",
"ip": "🌐 IP: {{ .IP }}\r\n",
"ips": "🔢 IPs:\r\n{{ .IPs }}\r\n",
"serverUpTime": "⏳ وقت التشغيل: {{ .UpTime }} {{ .Unit }}\r\n",
"serverLoad": "📈 تحميل النظام: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n",
"serverMemory": "📋 RAM: {{ .Current }}/{{ .Total }}\r\n",
"tcpCount": "🔹 TCP: {{ .Count }}\r\n",
"udpCount": "🔸 UDP: {{ .Count }}\r\n",
"traffic": "🚦 الترافيك: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n",
"xrayStatus": "ℹ️ الحالة: {{ .State }}\r\n",
"username": "👤 اسم المستخدم: {{ .Username }}\r\n",
"reason": "❗️ السبب: {{ .Reason }}\r\n",
"time": "⏰ الوقت: {{ .Time }}\r\n",
"inbound": "📍 الوارد: {{ .Remark }}\r\n",
"port": "🔌 المنفذ: {{ .Port }}\r\n",
"expire": "📅 تاريخ الانتهاء: {{ .Time }}\r\n",
"expireIn": "📅 هيخلص بعد: {{ .Time }}\r\n",
"active": "💡 مفعل: {{ .Enable }}\r\n",
"enabled": "🚨 مفعل: {{ .Enable }}\r\n",
"online": "🌐 حالة الاتصال: {{ .Status }}\r\n",
"lastOnline": "🔙 آخر متصل: {{ .Time }}\r\n",
"email": "📧 البريد: {{ .Email }}\r\n",
"upload": "🔼 الرفع: ↑{{ .Upload }}\r\n",
"download": "🔽 التنزيل: ↓{{ .Download }}\r\n",
"total": "📊 الإجمالي: ↑↓{{ .UpDown }} / {{ .Total }}\r\n",
"TGUser": "👤 مستخدم Telegram: {{ .TelegramID }}\r\n",
"exhaustedCount": "🚨 عدد النفاذ لـ {{ .Type }}:\r\n",
"onlinesCount": "🌐 العملاء الأونلاين: {{ .Count }}\r\n",
"disabled": "🛑 معطل: {{ .Disabled }}\r\n",
"depleteSoon": "🔜 هينتهي قريب: {{ .Deplete }}\r\n\r\n",
"backupTime": "🗄 وقت النسخة الاحتياطية: {{ .Time }}\r\n",
"refreshedOn": "\r\n📋🔄 اتحدّث في: {{ .Time }}\r\n\r\n",
"yes": "✅ أيوه",
"no": "❌ لا",
"received_email": "📧📥 الإيميل اتحدث.",
"received_comment": "💬📥 التعليق اتحدث.",
"email_prompt": "📧 الإيميل الافتراضي: {{ .ClientEmail }}\n\nادخل الإيميل بتاعك.",
"comment_prompt": "💬 التعليق الافتراضي: {{ .ClientComment }}\n\nادخل تعليقك.",
"cancel": "❌ العملية اتلغت! \n\nممكن تبدأ من /start في أي وقت. 🔄",
"error_add_client": "⚠️ خطأ:\n\n {{ .error }}",
"using_default_value": "تمام، هشيل على القيمة الافتراضية. 😊",
"incorrect_input": "المدخلات مش صحيحة.\nالكلمات لازم تكون متصلة من غير فراغات.\nمثال صحيح: aaaaaa\nمثال غلط: aaa aaa 🚫",
"AreYouSure": "إنت متأكد؟ 🤔",
"SuccessResetTraffic": "📧 البريد الإلكتروني: {{ .ClientEmail }}\n🏁 النتيجة: ✅ تم بنجاح",
"FailedResetTraffic": "📧 البريد الإلكتروني: {{ .ClientEmail }}\n🏁 النتيجة: ❌ فشل \n\n🛠️ الخطأ: [ {{ .ErrorMessage }} ]",
"FinishProcess": "🔚 عملية إعادة ضبط الترافيك خلصت لكل العملاء.",
"eventOutboundDown": "الصادر {{ .Tag }} غير متصل",
"eventOutboundUp": "الصادر {{ .Tag }} متصل",
"eventErrorDetail": "الخطأ: {{ .Error }}",
"eventDelayDetail": "التأخير: {{ .Delay }} مللي ثانية",
"eventXrayCrash": "تعطّل Xray",
"eventXrayCrashError": "الخطأ: {{ .Error }}",
"eventNodeDown": "العقدة {{ .Name }} غير متصلة",
"eventNodeUp": "العقدة {{ .Name }} متصلة",
"eventLoginFallback": "فشل تسجيل الدخول من {{ .Source }}",
"memoryThreshold": "استخدام الذاكرة {{ .Percent }}% يتجاوز الحد {{ .Threshold }}%"
},
"buttons": {
"closeKeyboard": "❌ اقفل الكيبورد",
"cancel": "❌ إلغاء",
"cancelReset": "❌ إلغاء إعادة الضبط",
"cancelIpLimit": "❌ إلغاء حد الـ IP",
"confirmResetTraffic": "✅ تأكيد إعادة ضبط الترافيك؟",
"confirmClearIps": "✅ تأكيد مسح الـ IPs؟",
"confirmRemoveTGUser": "✅ تأكيد حذف مستخدم Telegram؟",
"confirmToggle": "✅ تأكيد تفعيل/تعطيل المستخدم؟",
"dbBackup": "احصل على نسخة DB",
"serverUsage": "استخدام السيرفر",
"getInbounds": "احصل على الإدخالات",
"depleteSoon": "هينتهي قريب",
"clientUsage": "استخدام العميل",
"onlines": "العملاء الأونلاين",
"commands": "الأوامر",
"refresh": "🔄 تجديد",
"clearIPs": "❌ مسح الـ IPs",
"removeTGUser": "❌ حذف مستخدم Telegram",
"selectTGUser": "👤 اختار مستخدم Telegram",
"selectOneTGUser": "👤 اختار مستخدم Telegram:",
"resetTraffic": "📈 إعادة ضبط الترافيك",
"resetExpire": "📅 تغيير تاريخ الانتهاء",
"ipLog": "🔢 سجل الـ IP",
"ipLimit": "🔢 حد الـ IP",
"setTGUser": "👤 ضبط مستخدم Telegram",
"toggle": "🔘 تفعيل / تعطيل",
"custom": "🔢 مخصص",
"confirmNumber": "✅ تأكيد: {{ .Num }}",
"confirmNumberAdd": "✅ تأكيد إضافة: {{ .Num }}",
"limitTraffic": "🚧 حد الترافيك",
"getBanLogs": "احصل على سجلات الحظر",
"allClients": "كل العملاء",
"addClient": "إضافة عميل",
"submitDisable": "إرسال كمعطّل ☑️",
"submitEnable": "إرسال كمفعّل ✅",
"use_default": "🏷️ استخدام الإعدادات الافتراضية",
"change_email": "⚙️📧 البريد",
"change_comment": "⚙️💬 تعليق",
"ResetAllTraffics": "إعادة ضبط جميع الترافيك",
"SortedTrafficUsageReport": "تقرير استخدام الترافيك المرتب"
},
"answers": {
"successfulOperation": "✅ العملية نجحت!",
"errorOperation": "❗ حصل خطأ في العملية.",
"getInboundsFailed": "❌ فشل الحصول على الإدخالات.",
"getClientsFailed": "❌ فشل الحصول على العملاء.",
"canceled": "❌ {{ .Email }}: العملية اتلغت.",
"clientRefreshSuccess": "✅ {{ .Email }}: العميل اتحدث بنجاح.",
"IpRefreshSuccess": "✅ {{ .Email }}: الـ IPs اتحدثت بنجاح.",
"TGIdRefreshSuccess": "✅ {{ .Email }}: مستخدم Telegram اتحدث بنجاح.",
"resetTrafficSuccess": "✅ {{ .Email }}: الترافيك اتظبط بنجاح.",
"setTrafficLimitSuccess": "✅ {{ .Email }}: حد الترافيك اتسجل بنجاح.",
"expireResetSuccess": "✅ {{ .Email }}: أيام الانتهاء اتظبطت بنجاح.",
"resetIpSuccess": "✅ {{ .Email }}: حد الـ IP ({{ .Count }}) اتسجل بنجاح.",
"clearIpSuccess": "✅ {{ .Email }}: الـ IPs اتمسحت بنجاح.",
"getIpLog": "✅ {{ .Email }}: سجل الـ IP اتجاب.",
"getUserInfo": "✅ {{ .Email }}: بيانات مستخدم Telegram اتجاب.",
"removedTGUserSuccess": "✅ {{ .Email }}: مستخدم Telegram اتحذف بنجاح.",
"enableSuccess": "✅ {{ .Email }}: اتفعل بنجاح.",
"disableSuccess": "✅ {{ .Email }}: اتعطل بنجاح.",
"askToAddUserId": "مافيش إعدادات ليك!\r\nاطلب من الأدمن يضيف الـ Telegram ChatID الخاص بيك في إعداداتك.\r\n\r\nالـ ChatID بتاعك: <code>{{ .TgUserID }}</code>",
"chooseClient": "اختار عميل للإدخال {{ .Inbound }}",
"chooseInbound": "اختار الإدخال"
}
},
"email": {}
}