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>
35 lines
1.5 KiB
JavaScript
35 lines
1.5 KiB
JavaScript
import { getCachedMessage } from '../core/db.js';
|
|
import { logger } from '../core/logger.js';
|
|
|
|
/**
|
|
* Ловит удаления. Telegram в событии deleted_business_messages присылает ТОЛЬКО
|
|
* id сообщений и чат — без содержимого. Поэтому восстанавливаем из кэша, который
|
|
* наполняет модуль [cache], по тройке (connId, chatId, msgId). Нашли — значит это
|
|
* было входящее в личке: сохраняем как перехват владельца и уведомляем его.
|
|
*/
|
|
export default {
|
|
name: 'antidelete',
|
|
description: 'Сохраняет удалённые собеседником сообщения и медиа',
|
|
|
|
async onDeleted({ connId, ownerId, ownerChatId, chatId, msgIds }, ctx) {
|
|
if (!msgIds?.length) return;
|
|
|
|
for (const id of msgIds) {
|
|
const cached = getCachedMessage(connId, chatId, id);
|
|
if (!cached) continue; // не наше/не кэшировали — пропускаем
|
|
|
|
await ctx.capture('deleted', {
|
|
ownerId,
|
|
ownerChatId,
|
|
chatId: cached.chat_id,
|
|
senderId: cached.sender_id,
|
|
sender: cached.sender,
|
|
text: cached.text,
|
|
mediaKind: cached.media_kind,
|
|
mediaPath: cached.media_path,
|
|
});
|
|
logger.info(`antidelete: восстановлено удалённое сообщение ${id} от ${cached.sender || '?'}`);
|
|
}
|
|
},
|
|
};
|