Telegram-user: add group support
This commit is contained in:
parent
bd3a8c3c91
commit
d7b7242e9e
@ -40,7 +40,7 @@ const meta = {
|
|||||||
detailLabel: "Telegram User",
|
detailLabel: "Telegram User",
|
||||||
docsPath: "/channels/telegram-user",
|
docsPath: "/channels/telegram-user",
|
||||||
docsLabel: "telegram-user",
|
docsLabel: "telegram-user",
|
||||||
blurb: "login as a Telegram user via QR; DM-only for now.",
|
blurb: "login as a Telegram user via QR or phone code; supports DMs + groups.",
|
||||||
order: 12,
|
order: 12,
|
||||||
quickstartAllowFrom: true,
|
quickstartAllowFrom: true,
|
||||||
};
|
};
|
||||||
@ -68,7 +68,7 @@ export const telegramUserPlugin: ChannelPlugin<ResolvedTelegramUserAccount> = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
capabilities: {
|
capabilities: {
|
||||||
chatTypes: ["direct"],
|
chatTypes: ["direct", "group"],
|
||||||
reactions: false,
|
reactions: false,
|
||||||
threads: false,
|
threads: false,
|
||||||
media: true,
|
media: true,
|
||||||
|
|||||||
@ -2,6 +2,27 @@ import { z } from "zod";
|
|||||||
|
|
||||||
const allowFromEntry = z.union([z.string(), z.number()]);
|
const allowFromEntry = z.union([z.string(), z.number()]);
|
||||||
|
|
||||||
|
const TelegramUserTopicSchema = z
|
||||||
|
.object({
|
||||||
|
requireMention: z.boolean().optional(),
|
||||||
|
skills: z.array(z.string()).optional(),
|
||||||
|
enabled: z.boolean().optional(),
|
||||||
|
allowFrom: z.array(allowFromEntry).optional(),
|
||||||
|
systemPrompt: z.string().optional(),
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
|
const TelegramUserGroupSchema = z
|
||||||
|
.object({
|
||||||
|
requireMention: z.boolean().optional(),
|
||||||
|
skills: z.array(z.string()).optional(),
|
||||||
|
topics: z.record(z.string(), TelegramUserTopicSchema.optional()).optional(),
|
||||||
|
enabled: z.boolean().optional(),
|
||||||
|
allowFrom: z.array(allowFromEntry).optional(),
|
||||||
|
systemPrompt: z.string().optional(),
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const TelegramUserAccountSchema = z
|
const TelegramUserAccountSchema = z
|
||||||
.object({
|
.object({
|
||||||
name: z.string().optional(),
|
name: z.string().optional(),
|
||||||
@ -12,6 +33,9 @@ const TelegramUserAccountSchema = z
|
|||||||
allowFrom: z.array(allowFromEntry).optional(),
|
allowFrom: z.array(allowFromEntry).optional(),
|
||||||
textChunkLimit: z.number().int().positive().optional(),
|
textChunkLimit: z.number().int().positive().optional(),
|
||||||
mediaMaxMb: z.number().positive().optional(),
|
mediaMaxMb: z.number().positive().optional(),
|
||||||
|
groupAllowFrom: z.array(allowFromEntry).optional(),
|
||||||
|
groupPolicy: z.enum(["open", "allowlist", "disabled"]).optional(),
|
||||||
|
groups: z.record(z.string(), TelegramUserGroupSchema.optional()).optional(),
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import type { TelegramClient } from "@mtcute/node";
|
|||||||
import type { MessageContext } from "@mtcute/dispatcher";
|
import type { MessageContext } from "@mtcute/dispatcher";
|
||||||
import type { RuntimeEnv } from "clawdbot/plugin-sdk";
|
import type { RuntimeEnv } from "clawdbot/plugin-sdk";
|
||||||
|
|
||||||
import { resolveAckReaction } from "clawdbot/plugin-sdk";
|
import { resolveAckReaction, resolveMentionGatingWithBypass } from "clawdbot/plugin-sdk";
|
||||||
import { getTelegramUserRuntime } from "../runtime.js";
|
import { getTelegramUserRuntime } from "../runtime.js";
|
||||||
import type { CoreConfig, TelegramUserAccountConfig } from "../types.js";
|
import type { CoreConfig, TelegramUserAccountConfig } from "../types.js";
|
||||||
import { sendMediaTelegramUser, sendMessageTelegramUser } from "../send.js";
|
import { sendMediaTelegramUser, sendMessageTelegramUser } from "../send.js";
|
||||||
@ -16,6 +16,7 @@ type TelegramUserHandlerParams = {
|
|||||||
runtime: RuntimeEnv;
|
runtime: RuntimeEnv;
|
||||||
accountId: string;
|
accountId: string;
|
||||||
accountConfig: TelegramUserAccountConfig;
|
accountConfig: TelegramUserAccountConfig;
|
||||||
|
self?: { id: number; username?: string | null };
|
||||||
};
|
};
|
||||||
|
|
||||||
function normalizeAllowEntry(raw: string): string {
|
function normalizeAllowEntry(raw: string): string {
|
||||||
@ -42,7 +43,7 @@ function parseAllowlist(entries: Array<string | number> | undefined) {
|
|||||||
const username = entry.startsWith("@") ? entry.slice(1) : entry;
|
const username = entry.startsWith("@") ? entry.slice(1) : entry;
|
||||||
if (username) usernames.add(username);
|
if (username) usernames.add(username);
|
||||||
}
|
}
|
||||||
return { hasWildcard, usernames, ids };
|
return { hasWildcard, usernames, ids, hasEntries: normalized.length > 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSenderAllowed(params: {
|
function isSenderAllowed(params: {
|
||||||
@ -66,6 +67,46 @@ function resolveTelegramUserPeer(target: string): number | string {
|
|||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function firstDefined<T>(...values: Array<T | undefined>): T | undefined {
|
||||||
|
for (const value of values) {
|
||||||
|
if (typeof value !== "undefined") return value;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildTelegramUserGroupPeerId(chatId: number | string, threadId?: number) {
|
||||||
|
return threadId != null ? `${chatId}:topic:${threadId}` : String(chatId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildTelegramUserGroupFrom(chatId: number | string, threadId?: number) {
|
||||||
|
return `telegram-user:group:${buildTelegramUserGroupPeerId(chatId, threadId)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildTelegramUserGroupLabel(
|
||||||
|
title: string | undefined,
|
||||||
|
chatId: number | string,
|
||||||
|
threadId?: number,
|
||||||
|
) {
|
||||||
|
const topicSuffix = threadId != null ? ` topic:${threadId}` : "";
|
||||||
|
if (title) return `${title} id:${chatId}${topicSuffix}`;
|
||||||
|
return `group:${chatId}${topicSuffix}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveTelegramUserGroupConfig(
|
||||||
|
accountConfig: TelegramUserAccountConfig,
|
||||||
|
chatId: number | string,
|
||||||
|
threadId?: number,
|
||||||
|
) {
|
||||||
|
const groups = accountConfig.groups ?? {};
|
||||||
|
const chatKey = String(chatId);
|
||||||
|
const groupConfig = groups[chatKey] ?? groups["*"];
|
||||||
|
if (!threadId) return { groupConfig, topicConfig: undefined };
|
||||||
|
const topicKey = String(threadId);
|
||||||
|
const topicConfig =
|
||||||
|
groupConfig?.topics?.[topicKey] ?? groups["*"]?.topics?.[topicKey];
|
||||||
|
return { groupConfig, topicConfig };
|
||||||
|
}
|
||||||
|
|
||||||
async function resolveMediaAttachment(params: {
|
async function resolveMediaAttachment(params: {
|
||||||
client: TelegramClient;
|
client: TelegramClient;
|
||||||
mediaMaxMb: number;
|
mediaMaxMb: number;
|
||||||
@ -102,17 +143,21 @@ async function resolveMediaAttachment(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createTelegramUserMessageHandler(params: TelegramUserHandlerParams) {
|
export function createTelegramUserMessageHandler(params: TelegramUserHandlerParams) {
|
||||||
const { client, cfg, runtime, accountId, accountConfig } = params;
|
const { client, cfg, runtime, accountId, accountConfig, self } = params;
|
||||||
const core = getTelegramUserRuntime();
|
const core = getTelegramUserRuntime();
|
||||||
const textLimit = accountConfig.textChunkLimit ?? DEFAULT_TEXT_LIMIT;
|
const textLimit = accountConfig.textChunkLimit ?? DEFAULT_TEXT_LIMIT;
|
||||||
const mediaMaxMb = accountConfig.mediaMaxMb ?? DEFAULT_MEDIA_MAX_MB;
|
const mediaMaxMb = accountConfig.mediaMaxMb ?? DEFAULT_MEDIA_MAX_MB;
|
||||||
const dmPolicy = accountConfig.dmPolicy ?? "pairing";
|
const dmPolicy = accountConfig.dmPolicy ?? "pairing";
|
||||||
const allowFrom = accountConfig.allowFrom ?? [];
|
const allowFrom = accountConfig.allowFrom ?? [];
|
||||||
|
const groupAllowFrom = accountConfig.groupAllowFrom ?? allowFrom;
|
||||||
|
|
||||||
return async (msg: MessageContext) => {
|
return async (msg: MessageContext) => {
|
||||||
try {
|
try {
|
||||||
if (msg.isOutgoing || msg.isService) return;
|
if (msg.isOutgoing || msg.isService) return;
|
||||||
if (msg.chat.type !== "user") return;
|
const isDirect = msg.chat.type === "user";
|
||||||
|
const isGroup =
|
||||||
|
msg.chat.type === "chat" && msg.chat.chatType !== "channel";
|
||||||
|
if (!isDirect && !isGroup) return;
|
||||||
|
|
||||||
const sender = await msg.getCompleteSender().catch(() => msg.sender);
|
const sender = await msg.getCompleteSender().catch(() => msg.sender);
|
||||||
if (sender.type !== "user") return;
|
if (sender.type !== "user") return;
|
||||||
@ -126,11 +171,36 @@ export function createTelegramUserMessageHandler(params: TelegramUserHandlerPara
|
|||||||
.readAllowFromStore("telegram-user")
|
.readAllowFromStore("telegram-user")
|
||||||
.catch(() => []);
|
.catch(() => []);
|
||||||
const combinedAllowFrom = [...allowFrom, ...storeAllowFrom];
|
const combinedAllowFrom = [...allowFrom, ...storeAllowFrom];
|
||||||
|
const chatId = msg.chat.type === "chat" ? msg.chat.id : undefined;
|
||||||
|
const threadId =
|
||||||
|
isGroup && msg.isTopicMessage
|
||||||
|
? msg.replyToMessage?.threadId ?? undefined
|
||||||
|
: undefined;
|
||||||
|
const { groupConfig, topicConfig } =
|
||||||
|
isGroup && chatId != null
|
||||||
|
? resolveTelegramUserGroupConfig(accountConfig, chatId, threadId)
|
||||||
|
: { groupConfig: undefined, topicConfig: undefined };
|
||||||
|
|
||||||
|
const groupAllowOverride = firstDefined(
|
||||||
|
topicConfig?.allowFrom,
|
||||||
|
groupConfig?.allowFrom,
|
||||||
|
);
|
||||||
|
const groupAllowEntries = [
|
||||||
|
...((groupAllowOverride ?? groupAllowFrom) as Array<string | number>),
|
||||||
|
...storeAllowFrom,
|
||||||
|
];
|
||||||
|
const effectiveGroupAllow = parseAllowlist(groupAllowEntries);
|
||||||
|
const effectiveDmAllow = parseAllowlist(combinedAllowFrom);
|
||||||
|
|
||||||
|
if (isDirect) {
|
||||||
if (dmPolicy === "disabled") return;
|
if (dmPolicy === "disabled") return;
|
||||||
if (
|
if (
|
||||||
dmPolicy !== "open" &&
|
dmPolicy !== "open" &&
|
||||||
!isSenderAllowed({ allowFrom: combinedAllowFrom, senderId, senderUsername })
|
!isSenderAllowed({
|
||||||
|
allowFrom: combinedAllowFrom,
|
||||||
|
senderId,
|
||||||
|
senderUsername,
|
||||||
|
})
|
||||||
) {
|
) {
|
||||||
if (dmPolicy === "pairing") {
|
if (dmPolicy === "pairing") {
|
||||||
const pairing = await core.channel.pairing.upsertPairingRequest({
|
const pairing = await core.channel.pairing.upsertPairingRequest({
|
||||||
@ -153,6 +223,44 @@ export function createTelegramUserMessageHandler(params: TelegramUserHandlerPara
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else if (isGroup) {
|
||||||
|
if (groupConfig?.enabled === false) return;
|
||||||
|
if (topicConfig?.enabled === false) return;
|
||||||
|
if (typeof groupAllowOverride !== "undefined") {
|
||||||
|
const allowed = isSenderAllowed({
|
||||||
|
allowFrom: groupAllowEntries,
|
||||||
|
senderId,
|
||||||
|
senderUsername,
|
||||||
|
});
|
||||||
|
if (!allowed) return;
|
||||||
|
}
|
||||||
|
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
||||||
|
const groupPolicy =
|
||||||
|
accountConfig.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
||||||
|
if (groupPolicy === "disabled") return;
|
||||||
|
if (groupPolicy === "allowlist") {
|
||||||
|
if (!senderId) return;
|
||||||
|
if (!effectiveGroupAllow.hasEntries) return;
|
||||||
|
if (
|
||||||
|
!isSenderAllowed({
|
||||||
|
allowFrom: groupAllowEntries,
|
||||||
|
senderId,
|
||||||
|
senderUsername,
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (chatId != null) {
|
||||||
|
const groupAllowlist = core.channel.groups.resolveGroupPolicy({
|
||||||
|
cfg,
|
||||||
|
channel: "telegram-user",
|
||||||
|
groupId: String(chatId),
|
||||||
|
accountId,
|
||||||
|
});
|
||||||
|
if (groupAllowlist.allowlistEnabled && !groupAllowlist.allowed) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const text = msg.text?.trim() ?? "";
|
const text = msg.text?.trim() ?? "";
|
||||||
const media = await resolveMediaAttachment({
|
const media = await resolveMediaAttachment({
|
||||||
@ -171,24 +279,95 @@ export function createTelegramUserMessageHandler(params: TelegramUserHandlerPara
|
|||||||
direction: "inbound",
|
direction: "inbound",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const groupPeerId =
|
||||||
|
isGroup && chatId != null
|
||||||
|
? buildTelegramUserGroupPeerId(chatId, threadId)
|
||||||
|
: null;
|
||||||
const route = core.channel.routing.resolveAgentRoute({
|
const route = core.channel.routing.resolveAgentRoute({
|
||||||
cfg,
|
cfg,
|
||||||
channel: "telegram-user",
|
channel: "telegram-user",
|
||||||
accountId,
|
accountId,
|
||||||
peer: {
|
peer: {
|
||||||
kind: "dm",
|
kind: isGroup ? "group" : "dm",
|
||||||
id: senderId,
|
id: isGroup && groupPeerId ? groupPeerId : senderId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const mentionRegexes = core.channel.mentions.buildMentionRegexes(cfg, route.agentId);
|
||||||
|
const hasAnyMention = msg.entities.some(
|
||||||
|
(ent) => ent.kind === "mention" || ent.kind === "text_mention",
|
||||||
|
);
|
||||||
|
const hasControlCommandInMessage = core.channel.text.hasControlCommand(text, cfg, {
|
||||||
|
botUsername: self?.username?.trim().toLowerCase(),
|
||||||
|
});
|
||||||
|
const allowForCommands = isGroup ? effectiveGroupAllow : effectiveDmAllow;
|
||||||
|
const senderAllowedForCommands = isSenderAllowed({
|
||||||
|
allowFrom: isGroup ? groupAllowEntries : combinedAllowFrom,
|
||||||
|
senderId,
|
||||||
|
senderUsername,
|
||||||
|
});
|
||||||
|
const useAccessGroups = cfg.commands?.useAccessGroups !== false;
|
||||||
|
const commandAuthorized = core.channel.commands.resolveCommandAuthorizedFromAuthorizers({
|
||||||
|
useAccessGroups,
|
||||||
|
authorizers: [{ configured: allowForCommands.hasEntries, allowed: senderAllowedForCommands }],
|
||||||
|
});
|
||||||
|
if (isGroup && hasControlCommandInMessage && !commandAuthorized) return;
|
||||||
|
|
||||||
|
const computedWasMentioned =
|
||||||
|
msg.isMention || core.channel.mentions.matchesMentionPatterns(text, mentionRegexes);
|
||||||
|
const baseRequireMention = isGroup
|
||||||
|
? core.channel.groups.resolveRequireMention({
|
||||||
|
cfg,
|
||||||
|
channel: "telegram-user",
|
||||||
|
groupId: chatId != null ? String(chatId) : undefined,
|
||||||
|
accountId,
|
||||||
|
})
|
||||||
|
: false;
|
||||||
|
const requireMention = firstDefined(
|
||||||
|
topicConfig?.requireMention,
|
||||||
|
groupConfig?.requireMention,
|
||||||
|
baseRequireMention,
|
||||||
|
);
|
||||||
|
const replySenderId =
|
||||||
|
msg.replyToMessage?.sender?.type === "user"
|
||||||
|
? msg.replyToMessage.sender.id
|
||||||
|
: undefined;
|
||||||
|
const implicitMention =
|
||||||
|
isGroup && Boolean(requireMention) && self?.id != null && replySenderId === self.id;
|
||||||
|
const canDetectMention =
|
||||||
|
Boolean(self?.username) || mentionRegexes.length > 0 || msg.isMention;
|
||||||
|
const mentionGate = resolveMentionGatingWithBypass({
|
||||||
|
isGroup,
|
||||||
|
requireMention: Boolean(requireMention),
|
||||||
|
canDetectMention,
|
||||||
|
wasMentioned: computedWasMentioned,
|
||||||
|
implicitMention,
|
||||||
|
hasAnyMention,
|
||||||
|
allowTextCommands: true,
|
||||||
|
hasControlCommand: hasControlCommandInMessage,
|
||||||
|
commandAuthorized,
|
||||||
|
});
|
||||||
|
const effectiveWasMentioned = mentionGate.effectiveWasMentioned;
|
||||||
|
if (isGroup && requireMention && canDetectMention && mentionGate.shouldSkip) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const ackReactionScope = cfg.messages?.ackReactionScope ?? "group-mentions";
|
const ackReactionScope = cfg.messages?.ackReactionScope ?? "group-mentions";
|
||||||
const removeAckAfterReply = cfg.messages?.removeAckAfterReply ?? false;
|
const removeAckAfterReply = cfg.messages?.removeAckAfterReply ?? false;
|
||||||
const ackReaction = resolveAckReaction(cfg, route.agentId);
|
const ackReaction = resolveAckReaction(cfg, route.agentId);
|
||||||
const shouldAckReaction =
|
const shouldAckReaction = () => {
|
||||||
Boolean(ackReaction) && (ackReactionScope === "all" || ackReactionScope === "direct");
|
if (!ackReaction) return false;
|
||||||
const ackReactionPromise = shouldAckReaction
|
if (ackReactionScope === "all") return true;
|
||||||
|
if (ackReactionScope === "direct") return !isGroup;
|
||||||
|
if (ackReactionScope === "group-all") return isGroup;
|
||||||
|
if (ackReactionScope === "group-mentions") {
|
||||||
|
return isGroup && Boolean(requireMention) && canDetectMention && effectiveWasMentioned;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
const ackReactionPromise = shouldAckReaction()
|
||||||
? client
|
? client
|
||||||
.sendReaction({
|
.sendReaction({
|
||||||
chatId: senderPeer,
|
chatId: isGroup && chatId != null ? chatId : senderPeer,
|
||||||
message: msg.id,
|
message: msg.id,
|
||||||
emoji: ackReaction,
|
emoji: ackReaction,
|
||||||
})
|
})
|
||||||
@ -206,6 +385,10 @@ export function createTelegramUserMessageHandler(params: TelegramUserHandlerPara
|
|||||||
storePath,
|
storePath,
|
||||||
sessionKey: route.sessionKey,
|
sessionKey: route.sessionKey,
|
||||||
});
|
});
|
||||||
|
const groupTitle = msg.chat.type === "chat" ? msg.chat.title : undefined;
|
||||||
|
const conversationLabel = isGroup && chatId != null
|
||||||
|
? buildTelegramUserGroupLabel(groupTitle, chatId, threadId)
|
||||||
|
: senderName;
|
||||||
const body = core.channel.reply.formatAgentEnvelope({
|
const body = core.channel.reply.formatAgentEnvelope({
|
||||||
channel: "Telegram User",
|
channel: "Telegram User",
|
||||||
from: senderName,
|
from: senderName,
|
||||||
@ -219,12 +402,14 @@ export function createTelegramUserMessageHandler(params: TelegramUserHandlerPara
|
|||||||
Body: body,
|
Body: body,
|
||||||
RawBody: text,
|
RawBody: text,
|
||||||
CommandBody: text,
|
CommandBody: text,
|
||||||
From: `telegram-user:${senderId}`,
|
From: isGroup && chatId != null ? buildTelegramUserGroupFrom(chatId, threadId) : `telegram-user:${senderId}`,
|
||||||
To: `telegram-user:${senderId}`,
|
To: isGroup && chatId != null ? buildTelegramUserGroupFrom(chatId, threadId) : `telegram-user:${senderId}`,
|
||||||
SessionKey: route.sessionKey,
|
SessionKey: route.sessionKey,
|
||||||
AccountId: route.accountId,
|
AccountId: route.accountId,
|
||||||
ChatType: "direct",
|
ChatType: isGroup ? "group" : "direct",
|
||||||
ConversationLabel: senderName,
|
ConversationLabel: conversationLabel,
|
||||||
|
GroupSubject: isGroup ? groupTitle ?? undefined : undefined,
|
||||||
|
GroupSystemPrompt: isGroup ? groupConfig?.systemPrompt ?? undefined : undefined,
|
||||||
SenderName: senderName,
|
SenderName: senderName,
|
||||||
SenderId: senderId,
|
SenderId: senderId,
|
||||||
SenderUsername: senderUsername ?? undefined,
|
SenderUsername: senderUsername ?? undefined,
|
||||||
@ -236,10 +421,14 @@ export function createTelegramUserMessageHandler(params: TelegramUserHandlerPara
|
|||||||
MediaPath: media?.path,
|
MediaPath: media?.path,
|
||||||
MediaType: media?.contentType,
|
MediaType: media?.contentType,
|
||||||
MediaUrl: media?.path,
|
MediaUrl: media?.path,
|
||||||
CommandAuthorized: true,
|
CommandAuthorized: commandAuthorized,
|
||||||
CommandSource: "text" as const,
|
CommandSource: "text" as const,
|
||||||
OriginatingChannel: "telegram-user" as const,
|
OriginatingChannel: "telegram-user" as const,
|
||||||
OriginatingTo: `telegram-user:${senderId}`,
|
OriginatingTo:
|
||||||
|
isGroup && chatId != null
|
||||||
|
? buildTelegramUserGroupFrom(chatId, threadId)
|
||||||
|
: `telegram-user:${senderId}`,
|
||||||
|
WasMentioned: isGroup ? effectiveWasMentioned : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
void core.channel.session
|
void core.channel.session
|
||||||
@ -262,6 +451,10 @@ export function createTelegramUserMessageHandler(params: TelegramUserHandlerPara
|
|||||||
});
|
});
|
||||||
|
|
||||||
let hasReplied = false;
|
let hasReplied = false;
|
||||||
|
const replyTarget =
|
||||||
|
isGroup && chatId != null ? `telegram-user:${chatId}` : `telegram-user:${senderId}`;
|
||||||
|
const typingTarget = isGroup && chatId != null ? chatId : senderPeer;
|
||||||
|
const typingParams = isGroup && threadId != null ? { threadId } : undefined;
|
||||||
const { dispatcher, replyOptions, markDispatchIdle } =
|
const { dispatcher, replyOptions, markDispatchIdle } =
|
||||||
core.channel.reply.createReplyDispatcherWithTyping({
|
core.channel.reply.createReplyDispatcherWithTyping({
|
||||||
responsePrefix: core.channel.reply.resolveEffectiveMessagesConfig(cfg, route.agentId)
|
responsePrefix: core.channel.reply.resolveEffectiveMessagesConfig(cfg, route.agentId)
|
||||||
@ -272,7 +465,7 @@ export function createTelegramUserMessageHandler(params: TelegramUserHandlerPara
|
|||||||
const replyText = payload.text ?? "";
|
const replyText = payload.text ?? "";
|
||||||
const mediaUrl = payload.mediaUrl;
|
const mediaUrl = payload.mediaUrl;
|
||||||
if (mediaUrl) {
|
if (mediaUrl) {
|
||||||
await sendMediaTelegramUser(`telegram-user:${senderId}`, replyText, {
|
await sendMediaTelegramUser(replyTarget, replyText, {
|
||||||
client,
|
client,
|
||||||
accountId,
|
accountId,
|
||||||
replyToId,
|
replyToId,
|
||||||
@ -291,7 +484,7 @@ export function createTelegramUserMessageHandler(params: TelegramUserHandlerPara
|
|||||||
for (const chunk of core.channel.text.chunkMarkdownText(replyText, textLimit)) {
|
for (const chunk of core.channel.text.chunkMarkdownText(replyText, textLimit)) {
|
||||||
const trimmed = chunk.trim();
|
const trimmed = chunk.trim();
|
||||||
if (!trimmed) continue;
|
if (!trimmed) continue;
|
||||||
await sendMessageTelegramUser(`telegram-user:${senderId}`, trimmed, {
|
await sendMessageTelegramUser(replyTarget, trimmed, {
|
||||||
client,
|
client,
|
||||||
accountId,
|
accountId,
|
||||||
replyToId,
|
replyToId,
|
||||||
@ -306,7 +499,7 @@ export function createTelegramUserMessageHandler(params: TelegramUserHandlerPara
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onReplyStart: async () => {
|
onReplyStart: async () => {
|
||||||
await client.sendTyping(senderPeer).catch((err) => {
|
await client.sendTyping(typingTarget, "typing", typingParams).catch((err) => {
|
||||||
runtime.error?.(`telegram-user typing failed: ${String(err)}`);
|
runtime.error?.(`telegram-user typing failed: ${String(err)}`);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -328,7 +521,7 @@ export function createTelegramUserMessageHandler(params: TelegramUserHandlerPara
|
|||||||
if (didAck) {
|
if (didAck) {
|
||||||
await client
|
await client
|
||||||
.sendReaction({
|
.sendReaction({
|
||||||
chatId: senderPeer,
|
chatId: isGroup && chatId != null ? chatId : senderPeer,
|
||||||
message: msg.id,
|
message: msg.id,
|
||||||
emoji: null,
|
emoji: null,
|
||||||
})
|
})
|
||||||
|
|||||||
@ -67,15 +67,27 @@ export async function monitorTelegramUserProvider(opts: MonitorTelegramUserOpts
|
|||||||
await client.start();
|
await client.start();
|
||||||
|
|
||||||
const dispatcher = Dispatcher.for(client);
|
const dispatcher = Dispatcher.for(client);
|
||||||
|
const self = await client.getMe().catch(() => undefined);
|
||||||
const handleMessage = createTelegramUserMessageHandler({
|
const handleMessage = createTelegramUserMessageHandler({
|
||||||
client,
|
client,
|
||||||
cfg,
|
cfg,
|
||||||
runtime,
|
runtime,
|
||||||
accountId: account.accountId,
|
accountId: account.accountId,
|
||||||
accountConfig: account.config,
|
accountConfig: account.config,
|
||||||
|
self: self
|
||||||
|
? { id: self.id, username: "username" in self ? self.username : undefined }
|
||||||
|
: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatcher.onNewMessage(filters.chat("user"), handleMessage);
|
dispatcher.onNewMessage(
|
||||||
|
filters.or(
|
||||||
|
filters.chat("user"),
|
||||||
|
filters.chat("group"),
|
||||||
|
filters.chat("supergroup"),
|
||||||
|
filters.chat("gigagroup"),
|
||||||
|
),
|
||||||
|
handleMessage,
|
||||||
|
);
|
||||||
|
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
client.onError.add((err) => {
|
client.onError.add((err) => {
|
||||||
|
|||||||
@ -1,4 +1,21 @@
|
|||||||
export type DmPolicy = "pairing" | "allowlist" | "open" | "disabled";
|
import type { DmPolicy, GroupPolicy } from "clawdbot/plugin-sdk";
|
||||||
|
|
||||||
|
export type TelegramUserTopicConfig = {
|
||||||
|
requireMention?: boolean;
|
||||||
|
skills?: string[];
|
||||||
|
enabled?: boolean;
|
||||||
|
allowFrom?: Array<string | number>;
|
||||||
|
systemPrompt?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TelegramUserGroupConfig = {
|
||||||
|
requireMention?: boolean;
|
||||||
|
skills?: string[];
|
||||||
|
topics?: Record<string, TelegramUserTopicConfig>;
|
||||||
|
enabled?: boolean;
|
||||||
|
allowFrom?: Array<string | number>;
|
||||||
|
systemPrompt?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type TelegramUserAccountConfig = {
|
export type TelegramUserAccountConfig = {
|
||||||
/** Optional display name for this account (used in CLI/UI lists). */
|
/** Optional display name for this account (used in CLI/UI lists). */
|
||||||
@ -17,6 +34,12 @@ export type TelegramUserAccountConfig = {
|
|||||||
textChunkLimit?: number;
|
textChunkLimit?: number;
|
||||||
/** Max outbound media size in MB. */
|
/** Max outbound media size in MB. */
|
||||||
mediaMaxMb?: number;
|
mediaMaxMb?: number;
|
||||||
|
/** Optional allowlist for Telegram group senders (user ids or usernames). */
|
||||||
|
groupAllowFrom?: Array<string | number>;
|
||||||
|
/** Controls how group messages are handled (open | disabled | allowlist). */
|
||||||
|
groupPolicy?: GroupPolicy;
|
||||||
|
/** Group-specific overrides (keyed by chat id). */
|
||||||
|
groups?: Record<string, TelegramUserGroupConfig>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TelegramUserConfig = TelegramUserAccountConfig & {
|
export type TelegramUserConfig = TelegramUserAccountConfig & {
|
||||||
@ -25,7 +48,17 @@ export type TelegramUserConfig = TelegramUserAccountConfig & {
|
|||||||
|
|
||||||
export type CoreConfig = {
|
export type CoreConfig = {
|
||||||
channels?: {
|
channels?: {
|
||||||
|
defaults?: {
|
||||||
|
groupPolicy?: GroupPolicy;
|
||||||
|
};
|
||||||
"telegram-user"?: TelegramUserConfig;
|
"telegram-user"?: TelegramUserConfig;
|
||||||
};
|
};
|
||||||
|
commands?: {
|
||||||
|
useAccessGroups?: boolean;
|
||||||
|
};
|
||||||
|
messages?: {
|
||||||
|
ackReactionScope?: "off" | "group-mentions" | "group-all" | "direct" | "all";
|
||||||
|
removeAckAfterReply?: boolean;
|
||||||
|
};
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user