Files
arestools/src/config.js
T
ros 0e19c84a6d Make bot multi-tenant with colorful welcome and per-owner isolation
The bot is now public: anyone connects it to their own Telegram Business
account and gets antidelete for their own private chats. On connection
(business_connection enabled) and on /start it sends a colorful welcome
describing features, how to connect, and limitations.

- connections.js: resolve a connection's owner from business_connection_id
  (memo -> DB -> getBusinessConnection). Business messages have no outgoing
  flag, so the owner's own messages are filtered by comparing from.id; if
  the owner can't be resolved the message is not cached.
- Strict per-owner isolation: captures scoped by owner_id; the panel shows
  each user only their own feed; notifications go to the owner's chat.
- db.js: multi-tenant schema (connections table; messages keyed by
  (conn_id, chat_id, msg_id); captures/counts scoped by owner_id).
- media.js: key cached-media filenames by (connId, chatId, msgId) to
  prevent one tenant overwriting another's encrypted media.
- panel.js: drop the owner-only barrier; /start sends welcome + own feed.
- config.js: OWNER_ID is now optional (service logs only, grants no access).
- Docs: README/.env.example rewritten for the multi-tenant model and the
  shared-key privacy caveat.
- Stop tracking .claude/settings.local.json; restore the Hcrgram/ ignore.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 22:16:44 +03:00

58 lines
3.1 KiB
JavaScript

import { fileURLToPath } from 'node:url';
const req = (name) => {
const value = process.env[name];
if (!value) throw new Error(`${name} не задан в .env (см. .env.example).`);
return value;
};
const num = (name) => {
const value = Number(process.env[name]);
if (!Number.isInteger(value)) throw new Error(`${name} должен быть целым числом.`);
return value;
};
/**
* Конфиг бота. Работает через официальное бизнес-подключение Telegram —
* никакой строки сессии, только токен бота от @BotFather. Всё чувствительное
* (токен, ключ шифрования) живёт только в .env и никуда не уходит.
*
* Мультитенант: любой может подключить бота к своему бизнес-аккаунту. Доступ
* к перехватам изолирован по владельцу (см. db.js), поэтому OWNER_ID больше
* ничего не открывает и не обязателен.
*/
export const config = {
// Бот (@BotFather, Business Mode). К нему пользователи подключают свои
// бизнес-аккаунты (Настройки → Telegram для бизнеса → Чат-боты).
botToken: () => req('BOT_TOKEN'),
// Необязательный id «оператора» бота — только для служебных логов, доступ
// к чужим данным он НЕ даёт (строгая изоляция). null, если не задан.
ownerId: () => (process.env.OWNER_ID ? num('OWNER_ID') : null),
// Ключ шифрования локального хранилища (32+ байта, hex или ascii).
encryptionKey: () => req('ENCRYPTION_KEY'),
// Сколько дней держать перехваченное, прежде чем чистить (0 — бессрочно).
retentionDays: Number(process.env.RETENTION_DAYS ?? 30),
// Качать ли медиа входящих сразу (нужно, чтобы восстанавливать удалённые
// картинки/видео — при удалении file_id уже не приходит). Ест диск.
cacheMedia: process.env.CACHE_MEDIA !== '0',
dataDir: new URL('../data/', import.meta.url),
mediaDir: new URL('../data/media/', import.meta.url),
logLevel: process.env.LOG_LEVEL ?? 'info',
};
export const dataPath = () => fileURLToPath(config.dataDir);
export const mediaPath = () => fileURLToPath(config.mediaDir);
/** Проверяем всё, что нужно для старта, одним махом — понятная ошибка вместо падения в рантайме. */
export function assertConfig() {
config.botToken();
const key = config.encryptionKey();
if (Buffer.from(key, key.match(/^[0-9a-f]+$/i) ? 'hex' : 'utf8').length < 32) {
throw new Error('ENCRYPTION_KEY должен быть не короче 32 байт (64 hex-символа).');
}
}