telegram.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  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
  40. .split(',')
  41. .map((s) => s.trim())
  42. .filter(Boolean)) {
  43. // Telegram chat ids are integers; group/channel ids are negative.
  44. if (/^-?\d+$/.test(part)) ids.push(Number(part));
  45. else invalid.push(part);
  46. }
  47. return { ids, invalid };
  48. }
  49. // robfig/cron predefined macros (note: @reboot is NOT supported).
  50. const CRON_MACROS = new Set([
  51. '@yearly',
  52. '@annually',
  53. '@monthly',
  54. '@weekly',
  55. '@daily',
  56. '@midnight',
  57. '@hourly',
  58. ]);
  59. // Go duration: one or more <number><unit> chunks (ns, us/µs, ms, s, m, h).
  60. const GO_DURATION_RE = /^(\d+(\.\d+)?(ns|us|µs|ms|s|m|h))+$/;
  61. export function validateRunTime(s: string): CronValidation {
  62. const v = s.trim();
  63. if (!v) return { valid: false, kind: 'invalid', error: 'Schedule is empty.' };
  64. if (v.startsWith('@every ')) {
  65. const dur = v.slice('@every '.length).trim();
  66. if (GO_DURATION_RE.test(dur)) return { valid: true, kind: 'every' };
  67. return { valid: false, kind: 'invalid', error: `Invalid @every duration: "${dur}".` };
  68. }
  69. if (v.startsWith('@')) {
  70. if (CRON_MACROS.has(v)) return { valid: true, kind: 'macro' };
  71. return { valid: false, kind: 'invalid', error: `Unknown macro: "${v}".` };
  72. }
  73. const fields = v.split(/\s+/);
  74. if (fields.length === 5 || fields.length === 6) return { valid: true, kind: 'cron' };
  75. return {
  76. valid: false,
  77. kind: 'invalid',
  78. error: 'Use a 5/6-field cron, an @macro (e.g. @daily), or @every <duration>.',
  79. };
  80. }
  81. export function telegramApiBase(token: string): string {
  82. return `https://api.telegram.org/bot${token.trim()}`;
  83. }
  84. export function renderMessageTemplate(tpl: string, vars: Record<string, string>): string {
  85. return tpl.replace(/\{\{\s*(\w+)\s*\}\}/g, (_match, key: string) =>
  86. key in vars ? vars[key] : `{{${key}}}`,
  87. );
  88. }
  89. export function buildBotConfigSummary(c: BotConfig): {
  90. tgBotEnable: boolean;
  91. tgBotToken: string;
  92. tgBotChatId: string;
  93. tgRunTime: string;
  94. } {
  95. const { ids } = parseAdminIds(c.adminIds);
  96. return {
  97. tgBotEnable: true,
  98. tgBotToken: c.token.trim(),
  99. tgBotChatId: ids.join(','),
  100. tgRunTime: c.runTime.trim(),
  101. };
  102. }