206 lines
5.7 KiB
TypeScript
206 lines
5.7 KiB
TypeScript
/**
|
|
* Message handlers for converting GramJS events to openclaw format.
|
|
*/
|
|
|
|
import type { GramJSMessageContext, ResolvedGramJSAccount } from "./types.js";
|
|
import type { MsgContext } from "../auto-reply/templating.js";
|
|
import { createSubsystemLogger } from "../logging/subsystem.js";
|
|
|
|
const log = createSubsystemLogger("telegram-gramjs:handlers");
|
|
|
|
/**
|
|
* Convert GramJS message context to openclaw MsgContext.
|
|
*/
|
|
export async function convertToMsgContext(
|
|
gramjsContext: GramJSMessageContext,
|
|
account: ResolvedGramJSAccount,
|
|
accountId: string,
|
|
): Promise<MsgContext | null> {
|
|
try {
|
|
const {
|
|
messageId,
|
|
chatId,
|
|
senderId,
|
|
text,
|
|
date,
|
|
replyToId,
|
|
isGroup,
|
|
isChannel,
|
|
chatTitle,
|
|
senderUsername,
|
|
senderFirstName,
|
|
} = gramjsContext;
|
|
|
|
// Skip messages without text for now (Phase 2 will handle media)
|
|
if (!text || text.trim() === "") {
|
|
log.verbose(`Skipping message ${messageId} (no text content)`);
|
|
return null;
|
|
}
|
|
|
|
// Determine chat type
|
|
const chatType = isGroup ? "group" : isChannel ? "channel" : "direct";
|
|
|
|
// Skip channel messages unless explicitly configured
|
|
// (most users want DMs and groups only)
|
|
if (isChannel) {
|
|
log.verbose(`Skipping channel message ${messageId} (channel messages not supported yet)`);
|
|
return null;
|
|
}
|
|
|
|
// Build session key
|
|
// - DMs: Use senderId for main session
|
|
// - Groups: Use groupId for isolated session (per openclaw convention)
|
|
const sessionKey = isGroup
|
|
? `telegram-gramjs:${accountId}:group:${chatId}`
|
|
: `telegram-gramjs:${accountId}:${senderId}`;
|
|
|
|
// Build From field (sender identifier)
|
|
// Use username if available, otherwise user ID
|
|
const from = senderUsername ? `@${senderUsername}` : String(senderId);
|
|
|
|
// Build sender name for display
|
|
const senderName = senderFirstName || senderUsername || String(senderId);
|
|
|
|
// Create openclaw MsgContext
|
|
const msgContext: MsgContext = {
|
|
// Core message data
|
|
Body: text,
|
|
RawBody: text,
|
|
CommandBody: text,
|
|
BodyForAgent: text,
|
|
BodyForCommands: text,
|
|
|
|
// Identifiers
|
|
From: from,
|
|
To: String(chatId),
|
|
SessionKey: sessionKey,
|
|
AccountId: accountId,
|
|
MessageSid: String(messageId),
|
|
MessageSidFull: `${chatId}:${messageId}`,
|
|
|
|
// Reply context
|
|
ReplyToId: replyToId ? String(replyToId) : undefined,
|
|
ReplyToIdFull: replyToId ? `${chatId}:${replyToId}` : undefined,
|
|
|
|
// Timestamps
|
|
Timestamp: date ? date * 1000 : Date.now(),
|
|
|
|
// Chat metadata
|
|
ChatType: chatType,
|
|
ChatId: String(chatId),
|
|
|
|
// Sender metadata (for groups)
|
|
SenderId: senderId ? String(senderId) : undefined,
|
|
SenderUsername: senderUsername,
|
|
SenderName: senderName,
|
|
|
|
// Group metadata
|
|
GroupId: isGroup ? String(chatId) : undefined,
|
|
GroupSubject: isGroup ? chatTitle : undefined,
|
|
|
|
// Provider metadata
|
|
Provider: "telegram-gramjs",
|
|
Surface: "telegram-gramjs",
|
|
};
|
|
|
|
// For groups, check if bot was mentioned
|
|
if (isGroup) {
|
|
// TODO: Add mention detection logic
|
|
// This requires knowing the bot's username/ID
|
|
// For now, we'll rely on group requireMention config
|
|
const requireMention = account.config.groups?.[String(chatId)]?.requireMention;
|
|
|
|
if (requireMention) {
|
|
// For now, process all group messages
|
|
// Mention detection will be added in a follow-up
|
|
log.verbose(`Group message requires mention check (not yet implemented)`);
|
|
}
|
|
}
|
|
|
|
log.verbose(`Converted message ${messageId} from ${from} (chat: ${chatId})`);
|
|
|
|
return msgContext;
|
|
} catch (err) {
|
|
log.error("Error converting GramJS message to MsgContext:", err);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extract sender info from GramJS context.
|
|
*/
|
|
export function extractSenderInfo(gramjsContext: GramJSMessageContext): {
|
|
senderId: string;
|
|
senderUsername?: string;
|
|
senderName: string;
|
|
} {
|
|
const { senderId, senderUsername, senderFirstName } = gramjsContext;
|
|
|
|
return {
|
|
senderId: String(senderId || "unknown"),
|
|
senderUsername,
|
|
senderName: senderFirstName || senderUsername || String(senderId || "unknown"),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Build session key for routing messages to the correct agent session.
|
|
*
|
|
* Rules:
|
|
* - DMs: Use senderId (main session per user)
|
|
* - Groups: Use groupId (isolated session per group)
|
|
*/
|
|
export function buildSessionKey(gramjsContext: GramJSMessageContext, accountId: string): string {
|
|
const { chatId, senderId, isGroup } = gramjsContext;
|
|
|
|
if (isGroup) {
|
|
return `telegram-gramjs:${accountId}:group:${chatId}`;
|
|
}
|
|
|
|
return `telegram-gramjs:${accountId}:${senderId}`;
|
|
}
|
|
|
|
/**
|
|
* Check if a message mentions the bot (for group messages).
|
|
*
|
|
* NOTE: This is a placeholder. Full implementation requires:
|
|
* - Knowing the bot's username (from client.getMe())
|
|
* - Parsing @mentions in message text
|
|
* - Checking message.entities for mentions
|
|
*/
|
|
export function wasMessageMentioned(
|
|
_gramjsContext: GramJSMessageContext,
|
|
_botUsername?: string,
|
|
): boolean {
|
|
// TODO: Implement mention detection
|
|
// For now, return false (rely on requireMention config)
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Extract command from message text.
|
|
*
|
|
* Telegram commands start with / (e.g., /start, /help)
|
|
*/
|
|
export function extractCommand(text: string): {
|
|
isCommand: boolean;
|
|
command?: string;
|
|
args?: string;
|
|
} {
|
|
const trimmed = text.trim();
|
|
|
|
if (!trimmed.startsWith("/")) {
|
|
return { isCommand: false };
|
|
}
|
|
|
|
const parts = trimmed.split(/\s+/);
|
|
const command = parts[0].slice(1); // Remove leading /
|
|
const args = parts.slice(1).join(" ");
|
|
|
|
return {
|
|
isCommand: true,
|
|
command,
|
|
args: args || undefined,
|
|
};
|
|
}
|