telegram/client: Bot API на fetch с таймаутом 5 с; ошибки делятся на повторяемые (сеть, таймаут, 429, 5xx) и конфигурационные (400/403…), токен не попадает ни в тексты ошибок, ни в tg_last_error. format: заявка и уточнение по шаблону плана, HTML с экранированием, источник из utm / referrer, первый визит. outbox: Notifier с отправкой после ответа и тиком раз в минуту, пауза 1, 2, 4… мин, уточнение только после своей заявки, после 10 попыток — в «Систему». system: тема «Система» с троттлингом ×N за период. bot: long polling, только TELEGRAM_ADMIN_ID, /setup создаёт темы и присылает id в личку. config: всё окружение в одном месте.
60 lines
2.1 KiB
TypeScript
60 lines
2.1 KiB
TypeScript
// Всё окружение api читается здесь. Telegram-переменные необязательны: без них api
|
|
// принимает заявки, а отправки копятся в pending до настройки.
|
|
import { DEFAULT_DB_PATH } from './db/index.js';
|
|
|
|
export type TelegramConfig = {
|
|
token?: string;
|
|
/** id группы, у супергрупп отрицательный: -100… */
|
|
chatId?: number;
|
|
leadsThreadId?: number;
|
|
systemThreadId?: number;
|
|
adminId?: number;
|
|
};
|
|
|
|
export type Config = {
|
|
port: number;
|
|
dbPath: string;
|
|
version: string;
|
|
trustedProxies: string[];
|
|
telegram: TelegramConfig;
|
|
};
|
|
|
|
type Env = Record<string, string | undefined>;
|
|
|
|
function optional(env: Env, name: string): string | undefined {
|
|
const value = env[name]?.trim();
|
|
return value ? value : undefined;
|
|
}
|
|
|
|
function optionalInt(env: Env, name: string): number | undefined {
|
|
const raw = optional(env, name);
|
|
if (raw === undefined) return undefined;
|
|
if (!/^-?\d+$/.test(raw) || !Number.isSafeInteger(Number(raw))) {
|
|
throw new Error(`${name}: ожидается целое число`);
|
|
}
|
|
return Number(raw);
|
|
}
|
|
|
|
export function loadConfig(env: Env = process.env): Config {
|
|
const port = optionalInt(env, 'PORT') ?? 3001;
|
|
const token = optional(env, 'TELEGRAM_BOT_TOKEN');
|
|
// Значение токена в текст ошибки не попадает — только факт, что формат не тот.
|
|
if (token !== undefined && !/^\d+:[\w-]+$/.test(token)) throw new Error('TELEGRAM_BOT_TOKEN: неверный формат');
|
|
return {
|
|
port,
|
|
dbPath: optional(env, 'DB_PATH') ?? DEFAULT_DB_PATH,
|
|
version: optional(env, 'APP_VERSION') ?? 'dev',
|
|
trustedProxies: (optional(env, 'TRUSTED_PROXIES') ?? '')
|
|
.split(',')
|
|
.map((s) => s.trim())
|
|
.filter(Boolean),
|
|
telegram: {
|
|
token,
|
|
chatId: optionalInt(env, 'TELEGRAM_CHAT_ID'),
|
|
leadsThreadId: optionalInt(env, 'TELEGRAM_LEADS_THREAD_ID'),
|
|
systemThreadId: optionalInt(env, 'TELEGRAM_SYSTEM_THREAD_ID'),
|
|
adminId: optionalInt(env, 'TELEGRAM_ADMIN_ID'),
|
|
},
|
|
};
|
|
}
|