Перевести проект на Telegram Business (без хранения сессии)
Смена архитектуры с MTProto-юзербота (GramJS + строка сессии) на официальное бизнес-подключение Telegram (Bot API). Проект больше НЕ хранит сессию — работает только через бота с включённым Business Mode, подключённого в Настройках → Telegram для бизнеса → Чат-боты. Это соответствует ToS. Изменения: - удалено: scripts/login.js, src/userbot/client.js, модуль onetime - src/userbot/ → src/business/: events.js подписывает business_message / edited / deleted / connection и нормализует сообщение; context.js даёт ctx.api = bot.api - core/media.js: скачивание через getFile + fetch вместо GramJS - core/db.js: составной ключ PRIMARY KEY (chat_id, msg_id) — в Bot API message_id уникален только внутри чата - bot/panel.js: барьер владельца пропускает бизнес-апдейты (у них ctx.from это собеседник); убран UI одноразовых - index.js: сборка только на grammY, явный allowed_updates с бизнес-типами - config.js: убраны API_ID/API_HASH/SESSION, проверки на старте почищены - package.json: убраны зависимости telegram и input, скрипт login - README и .env.example переписаны под бизнес-подключение Границы: onetime невозможен через Business API (сервер отбрасывает self-destruct до доставки боту), медиа >~20 МБ не выгружаются (лимит getFile), перехват — только личные 1-на-1 чаты. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import { isOwner } from '../config.js';
|
||||
import { detectMediaFromBusiness } from '../core/media.js';
|
||||
import { logger } from '../core/logger.js';
|
||||
import { runHook } from '../core/registry.js';
|
||||
|
||||
/**
|
||||
* Подписывает бота на бизнес-апдейты Telegram и раздаёт их модулям через хуки.
|
||||
* Работает через официальное бизнес-подключение (Настройки → Telegram для
|
||||
* бизнеса → Чат-боты) — никакой сессии, только апдейты от подключённого аккаунта.
|
||||
*
|
||||
* Событие удаления (deleted_business_messages) несёт только id сообщений и чат —
|
||||
* без содержимого. Поэтому модуль cache складывает каждое входящее заранее,
|
||||
* а antidelete достаёт его из кэша по (chatId, msgId).
|
||||
*/
|
||||
export function wireBusiness(bot, registry, ctx) {
|
||||
bot.on('business_message', (gctx) =>
|
||||
runHook(registry, 'onMessage', normalizeBusinessMessage(gctx), ctx),
|
||||
);
|
||||
|
||||
bot.on('edited_business_message', (gctx) =>
|
||||
runHook(registry, 'onEdited', normalizeBusinessMessage(gctx), ctx),
|
||||
);
|
||||
|
||||
bot.on('deleted_business_messages', (gctx) => {
|
||||
const d = gctx.deletedBusinessMessages;
|
||||
return runHook(
|
||||
registry,
|
||||
'onDeleted',
|
||||
{ chatId: d.chat?.id, msgIds: d.message_ids ?? [] },
|
||||
ctx,
|
||||
);
|
||||
});
|
||||
|
||||
bot.on('business_connection', (gctx) => {
|
||||
const c = gctx.businessConnection;
|
||||
if (c?.is_enabled) {
|
||||
logger.info(`Бизнес-подключение активно (id ${c.id})`);
|
||||
} else {
|
||||
logger.warn(`Бизнес-подключение отключено владельцем (id ${c?.id}). Перехват приостановлен.`);
|
||||
}
|
||||
});
|
||||
|
||||
logger.debug('бизнес-апдейты подписаны: message / edited / deleted / connection');
|
||||
}
|
||||
|
||||
/**
|
||||
* Приводит grammY-контекст бизнес-сообщения к плоскому виду, понятному модулям.
|
||||
* isPrivateIncoming — входящее в личном 1-на-1 чате, не от самого владельца.
|
||||
*/
|
||||
export function normalizeBusinessMessage(gctx) {
|
||||
const msg = gctx.businessMessage ?? gctx.editedBusinessMessage ?? gctx.msg;
|
||||
const from = msg?.from;
|
||||
const chat = msg?.chat;
|
||||
return {
|
||||
chatId: chat?.id,
|
||||
msgId: msg?.message_id,
|
||||
senderId: from?.id,
|
||||
sender: senderLabel(from),
|
||||
text: msg?.text ?? msg?.caption ?? '',
|
||||
media: detectMediaFromBusiness(msg),
|
||||
// Свои исходящие отсекаем через isOwner (как раньше через message.out).
|
||||
isPrivateIncoming: chat?.type === 'private' && !isOwner(from?.id),
|
||||
};
|
||||
}
|
||||
|
||||
function senderLabel(from) {
|
||||
if (!from) return '';
|
||||
const name = [from.first_name, from.last_name].filter(Boolean).join(' ');
|
||||
return from.username ? `${name} (@${from.username})`.trim() : name;
|
||||
}
|
||||
Reference in New Issue
Block a user