mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-09 06:06:08 +00:00
9b91f0f42e
Fold the standalone 3x-ui-docs project (Next.js 16 + Fumadocs, deployed to docs.sanaei.dev) into docs/ so the panel and its documentation share a single source of truth, the way sing-box keeps its docs in-tree. The old repo becomes redundant and can be retired. - Import the full site under docs/ (app, components, content, lib, public, scripts, config). The self-contained pnpm project sits alongside the existing engineering notes with no filename collisions. - Re-point "Edit on GitHub" links from MHSanaei/3x-ui-docs to this repo's docs/content/docs path (docs/lib/shared.ts, docs/app/.../page.tsx). - Add docs-ci.yml and docs-deploy.yml under .github/workflows/, scoped to docs/** and run with working-directory: docs, since GitHub only runs workflows from the repo-root .github/. deploy-static.yml's GitHub Pages publish (CNAME docs.sanaei.dev) carries over unchanged. Follow-up (outside this commit): attach the docs.sanaei.dev custom domain to this repository's Pages (or set the Vercel project's root directory to docs), confirm the site is live from the monorepo, then delete MHSanaei/3x-ui-docs.
116 lines
3.4 KiB
TypeScript
116 lines
3.4 KiB
TypeScript
// Pure validation + templating helpers for 3x-ui's Telegram bot settings.
|
|
// Grounded in internal/web/service/tgbot/tgbot.go (admin ids parsed with
|
|
// strconv.ParseInt(_,10,64); token handed to telego.NewBot → api.telegram.org)
|
|
// and the panel's tg* settings (tgRunTime uses robfig/cron). No React/DOM.
|
|
|
|
export interface BotConfig {
|
|
token: string;
|
|
adminIds: string; // raw comma-separated input
|
|
runTime: string; // @daily | @every 8h | 5/6-field cron
|
|
}
|
|
|
|
export interface TokenValidation {
|
|
valid: boolean;
|
|
botId?: string;
|
|
error?: string;
|
|
}
|
|
|
|
export interface AdminIdsResult {
|
|
ids: number[];
|
|
invalid: string[];
|
|
}
|
|
|
|
export type CronKind = 'macro' | 'every' | 'cron' | 'invalid';
|
|
export interface CronValidation {
|
|
valid: boolean;
|
|
kind: CronKind;
|
|
error?: string;
|
|
}
|
|
|
|
// BotFather tokens are <bot-id>:<35+ char secret of [A-Za-z0-9_-]>.
|
|
const TOKEN_RE = /^(\d+):[A-Za-z0-9_-]{35,}$/;
|
|
|
|
export function validateBotToken(token: string): TokenValidation {
|
|
const t = token.trim();
|
|
if (!t) return { valid: false, error: 'Token is empty.' };
|
|
const m = TOKEN_RE.exec(t);
|
|
if (!m) {
|
|
return { valid: false, error: 'Expected the BotFather format <bot-id>:<35+ char secret>.' };
|
|
}
|
|
return { valid: true, botId: m[1] };
|
|
}
|
|
|
|
export function parseAdminIds(raw: string): AdminIdsResult {
|
|
const ids: number[] = [];
|
|
const invalid: string[] = [];
|
|
for (const part of raw.split(',').map((s) => s.trim()).filter(Boolean)) {
|
|
// Telegram chat ids are integers; group/channel ids are negative.
|
|
if (/^-?\d+$/.test(part)) ids.push(Number(part));
|
|
else invalid.push(part);
|
|
}
|
|
return { ids, invalid };
|
|
}
|
|
|
|
// robfig/cron predefined macros (note: @reboot is NOT supported).
|
|
const CRON_MACROS = new Set([
|
|
'@yearly',
|
|
'@annually',
|
|
'@monthly',
|
|
'@weekly',
|
|
'@daily',
|
|
'@midnight',
|
|
'@hourly',
|
|
]);
|
|
|
|
// Go duration: one or more <number><unit> chunks (ns, us/µs, ms, s, m, h).
|
|
const GO_DURATION_RE = /^(\d+(\.\d+)?(ns|us|µs|ms|s|m|h))+$/;
|
|
|
|
export function validateRunTime(s: string): CronValidation {
|
|
const v = s.trim();
|
|
if (!v) return { valid: false, kind: 'invalid', error: 'Schedule is empty.' };
|
|
|
|
if (v.startsWith('@every ')) {
|
|
const dur = v.slice('@every '.length).trim();
|
|
if (GO_DURATION_RE.test(dur)) return { valid: true, kind: 'every' };
|
|
return { valid: false, kind: 'invalid', error: `Invalid @every duration: "${dur}".` };
|
|
}
|
|
|
|
if (v.startsWith('@')) {
|
|
if (CRON_MACROS.has(v)) return { valid: true, kind: 'macro' };
|
|
return { valid: false, kind: 'invalid', error: `Unknown macro: "${v}".` };
|
|
}
|
|
|
|
const fields = v.split(/\s+/);
|
|
if (fields.length === 5 || fields.length === 6) return { valid: true, kind: 'cron' };
|
|
return {
|
|
valid: false,
|
|
kind: 'invalid',
|
|
error: 'Use a 5/6-field cron, an @macro (e.g. @daily), or @every <duration>.',
|
|
};
|
|
}
|
|
|
|
export function telegramApiBase(token: string): string {
|
|
return `https://api.telegram.org/bot${token.trim()}`;
|
|
}
|
|
|
|
export function renderMessageTemplate(tpl: string, vars: Record<string, string>): string {
|
|
return tpl.replace(/\{\{\s*(\w+)\s*\}\}/g, (_match, key: string) =>
|
|
key in vars ? vars[key] : `{{${key}}}`,
|
|
);
|
|
}
|
|
|
|
export function buildBotConfigSummary(c: BotConfig): {
|
|
tgBotEnable: boolean;
|
|
tgBotToken: string;
|
|
tgBotChatId: string;
|
|
tgRunTime: string;
|
|
} {
|
|
const { ids } = parseAdminIds(c.adminIds);
|
|
return {
|
|
tgBotEnable: true,
|
|
tgBotToken: c.token.trim(),
|
|
tgBotChatId: ids.join(','),
|
|
tgRunTime: c.runTime.trim(),
|
|
};
|
|
}
|