telegram.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. // Pure validation + templating helpers for 3x-ui's Telegram bot settings.
  2. // Grounded in internal/web/service/tgbot/tgbot.go (admin ids parsed with
  3. // strconv.ParseInt(_,10,64); token handed to telego.NewBot → api.telegram.org)
  4. // and the panel's tg* settings (tgRunTime uses robfig/cron). No React/DOM.
  5. export interface BotConfig {
  6. token: string;
  7. adminIds: string; // raw comma-separated input
  8. runTime: string; // @daily | @every 8h | 5/6-field cron
  9. }
  10. export interface TokenValidation {
  11. valid: boolean;
  12. botId?: string;
  13. error?: string;
  14. }
  15. export interface AdminIdsResult {
  16. ids: number[];
  17. invalid: string[];
  18. }
  19. export type CronKind = 'macro' | 'every' | 'cron' | 'invalid';
  20. export interface CronValidation {
  21. valid: boolean;
  22. kind: CronKind;
  23. error?: string;
  24. }
  25. // BotFather tokens are <bot-id>:<35+ char secret of [A-Za-z0-9_-]>.
  26. const TOKEN_RE = /^(\d+):[A-Za-z0-9_-]{35,}$/;
  27. export function validateBotToken(token: string): TokenValidation {
  28. const t = token.trim();
  29. if (!t) return { valid: false, error: 'Token is empty.' };
  30. const m = TOKEN_RE.exec(t);
  31. if (!m) {
  32. return { valid: false, error: 'Expected the BotFather format <bot-id>:<35+ char secret>.' };
  33. }
  34. return { valid: true, botId: m[1] };
  35. }
  36. export function parseAdminIds(raw: string): AdminIdsResult {
  37. const ids: number[] = [];
  38. const invalid: string[] = [];
  39. for (const part of raw.split(',').map((s) => s.trim()).filter(Boolean)) {
  40. // Telegram chat ids are integers; group/channel ids are negative.
  41. if (/^-?\d+$/.test(part)) ids.push(Number(part));
  42. else invalid.push(part);
  43. }
  44. return { ids, invalid };
  45. }
  46. // robfig/cron predefined macros (note: @reboot is NOT supported).
  47. const CRON_MACROS = new Set([
  48. '@yearly',
  49. '@annually',
  50. '@monthly',
  51. '@weekly',
  52. '@daily',
  53. '@midnight',
  54. '@hourly',
  55. ]);
  56. // Go duration: one or more <number><unit> chunks (ns, us/µs, ms, s, m, h).
  57. const GO_DURATION_RE = /^(\d+(\.\d+)?(ns|us|µs|ms|s|m|h))+$/;
  58. export function validateRunTime(s: string): CronValidation {
  59. const v = s.trim();
  60. if (!v) return { valid: false, kind: 'invalid', error: 'Schedule is empty.' };
  61. if (v.startsWith('@every ')) {
  62. const dur = v.slice('@every '.length).trim();
  63. if (GO_DURATION_RE.test(dur)) return { valid: true, kind: 'every' };
  64. return { valid: false, kind: 'invalid', error: `Invalid @every duration: "${dur}".` };
  65. }
  66. if (v.startsWith('@')) {
  67. if (CRON_MACROS.has(v)) return { valid: true, kind: 'macro' };
  68. return { valid: false, kind: 'invalid', error: `Unknown macro: "${v}".` };
  69. }
  70. const fields = v.split(/\s+/);
  71. if (fields.length === 5 || fields.length === 6) return { valid: true, kind: 'cron' };
  72. return {
  73. valid: false,
  74. kind: 'invalid',
  75. error: 'Use a 5/6-field cron, an @macro (e.g. @daily), or @every <duration>.',
  76. };
  77. }
  78. export function telegramApiBase(token: string): string {
  79. return `https://api.telegram.org/bot${token.trim()}`;
  80. }
  81. export function renderMessageTemplate(tpl: string, vars: Record<string, string>): string {
  82. return tpl.replace(/\{\{\s*(\w+)\s*\}\}/g, (_match, key: string) =>
  83. key in vars ? vars[key] : `{{${key}}}`,
  84. );
  85. }
  86. export function buildBotConfigSummary(c: BotConfig): {
  87. tgBotEnable: boolean;
  88. tgBotToken: string;
  89. tgBotChatId: string;
  90. tgRunTime: string;
  91. } {
  92. const { ids } = parseAdminIds(c.adminIds);
  93. return {
  94. tgBotEnable: true,
  95. tgBotToken: c.token.trim(),
  96. tgBotChatId: ids.join(','),
  97. tgRunTime: c.runTime.trim(),
  98. };
  99. }