0e19c84a6d
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>
68 lines
2.8 KiB
JavaScript
68 lines
2.8 KiB
JavaScript
import { getConnection, upsertConnection } from '../core/db.js';
|
|
import { logger } from '../core/logger.js';
|
|
|
|
/**
|
|
* Резолвер владельца по business_connection_id.
|
|
*
|
|
* Зачем: у бизнес-сообщений НЕТ флага «исходящее». Единственный надёжный способ
|
|
* отличить сообщение владельца от сообщения собеседника — сравнить from.id с id
|
|
* владельца подключения. А уведомления об удалении надо слать в личку владельца
|
|
* (owner_chat_id). И то, и другое берём из подключения.
|
|
*
|
|
* Порядок поиска: память → БД → getBusinessConnection (и результат кэшируем).
|
|
* getBusinessConnection нужен после рестарта, если событие connection пришло до
|
|
* запуска и в памяти пусто, но в БД запись есть — тогда чаще хватит БД.
|
|
*/
|
|
const memo = new Map(); // connId -> { ownerId, ownerChatId, ownerName, ownerUsername, isEnabled }
|
|
|
|
/** Кладёт/обновляет подключение и в память, и в БД. Возвращает нормализованную запись. */
|
|
export function rememberConnection(conn) {
|
|
const record = {
|
|
connId: String(conn.id),
|
|
ownerId: conn.user?.id != null ? String(conn.user.id) : '',
|
|
ownerChatId: conn.user_chat_id != null ? String(conn.user_chat_id) : '',
|
|
ownerName: [conn.user?.first_name, conn.user?.last_name].filter(Boolean).join(' '),
|
|
ownerUsername: conn.user?.username ?? '',
|
|
isEnabled: Boolean(conn.is_enabled),
|
|
};
|
|
memo.set(record.connId, record);
|
|
upsertConnection(record);
|
|
return record;
|
|
}
|
|
|
|
/**
|
|
* Возвращает { ownerId, ownerChatId, ... } по connId или null.
|
|
* api — bot.api (для getBusinessConnection); можно не передавать, тогда только кэш/БД.
|
|
*/
|
|
export async function resolveOwner(connId, api) {
|
|
if (connId == null) return null;
|
|
const key = String(connId);
|
|
|
|
const cached = memo.get(key);
|
|
if (cached) return cached;
|
|
|
|
const row = getConnection(key);
|
|
if (row) {
|
|
const record = {
|
|
connId: key,
|
|
ownerId: row.owner_id ?? '',
|
|
ownerChatId: row.owner_chat_id ?? '',
|
|
ownerName: row.owner_name ?? '',
|
|
ownerUsername: row.owner_username ?? '',
|
|
isEnabled: Boolean(row.is_enabled),
|
|
};
|
|
memo.set(key, record);
|
|
return record;
|
|
}
|
|
|
|
if (api?.getBusinessConnection) {
|
|
try {
|
|
const conn = await api.getBusinessConnection(key);
|
|
if (conn?.id) return rememberConnection(conn);
|
|
} catch (err) {
|
|
logger.warn(`connections: getBusinessConnection(${key}) не удался:`, err.message);
|
|
}
|
|
}
|
|
return null;
|
|
}
|