mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 09:21:23 +00:00
telegram: bridge direct delivery message hooks
This commit is contained in:
@@ -433,6 +433,9 @@ export const dispatchTelegramMessage = async ({
|
|||||||
const deliveryBaseOptions = {
|
const deliveryBaseOptions = {
|
||||||
chatId: String(chatId),
|
chatId: String(chatId),
|
||||||
accountId: route.accountId,
|
accountId: route.accountId,
|
||||||
|
sessionKeyForInternalHooks: ctxPayload.SessionKey,
|
||||||
|
mirrorIsGroup: isGroup,
|
||||||
|
mirrorGroupId: isGroup ? String(chatId) : undefined,
|
||||||
token: opts.token,
|
token: opts.token,
|
||||||
runtime,
|
runtime,
|
||||||
bot,
|
bot,
|
||||||
|
|||||||
@@ -516,6 +516,9 @@ export const registerTelegramNativeCommands = ({
|
|||||||
const buildCommandDeliveryBaseOptions = (params: {
|
const buildCommandDeliveryBaseOptions = (params: {
|
||||||
chatId: string | number;
|
chatId: string | number;
|
||||||
accountId: string;
|
accountId: string;
|
||||||
|
sessionKeyForInternalHooks?: string;
|
||||||
|
mirrorIsGroup?: boolean;
|
||||||
|
mirrorGroupId?: string;
|
||||||
mediaLocalRoots?: readonly string[];
|
mediaLocalRoots?: readonly string[];
|
||||||
threadSpec: ReturnType<typeof resolveTelegramThreadSpec>;
|
threadSpec: ReturnType<typeof resolveTelegramThreadSpec>;
|
||||||
tableMode: ReturnType<typeof resolveMarkdownTableMode>;
|
tableMode: ReturnType<typeof resolveMarkdownTableMode>;
|
||||||
@@ -523,6 +526,9 @@ export const registerTelegramNativeCommands = ({
|
|||||||
}) => ({
|
}) => ({
|
||||||
chatId: String(params.chatId),
|
chatId: String(params.chatId),
|
||||||
accountId: params.accountId,
|
accountId: params.accountId,
|
||||||
|
sessionKeyForInternalHooks: params.sessionKeyForInternalHooks,
|
||||||
|
mirrorIsGroup: params.mirrorIsGroup,
|
||||||
|
mirrorGroupId: params.mirrorGroupId,
|
||||||
token: opts.token,
|
token: opts.token,
|
||||||
runtime,
|
runtime,
|
||||||
bot,
|
bot,
|
||||||
@@ -592,6 +598,9 @@ export const registerTelegramNativeCommands = ({
|
|||||||
const deliveryBaseOptions = buildCommandDeliveryBaseOptions({
|
const deliveryBaseOptions = buildCommandDeliveryBaseOptions({
|
||||||
chatId,
|
chatId,
|
||||||
accountId: route.accountId,
|
accountId: route.accountId,
|
||||||
|
sessionKeyForInternalHooks: route.sessionKey,
|
||||||
|
mirrorIsGroup: isGroup,
|
||||||
|
mirrorGroupId: isGroup ? String(chatId) : undefined,
|
||||||
mediaLocalRoots,
|
mediaLocalRoots,
|
||||||
threadSpec,
|
threadSpec,
|
||||||
tableMode,
|
tableMode,
|
||||||
@@ -827,6 +836,9 @@ export const registerTelegramNativeCommands = ({
|
|||||||
const deliveryBaseOptions = buildCommandDeliveryBaseOptions({
|
const deliveryBaseOptions = buildCommandDeliveryBaseOptions({
|
||||||
chatId,
|
chatId,
|
||||||
accountId: route.accountId,
|
accountId: route.accountId,
|
||||||
|
sessionKeyForInternalHooks: route.sessionKey,
|
||||||
|
mirrorIsGroup: isGroup,
|
||||||
|
mirrorGroupId: isGroup ? String(chatId) : undefined,
|
||||||
mediaLocalRoots,
|
mediaLocalRoots,
|
||||||
threadSpec,
|
threadSpec,
|
||||||
tableMode,
|
tableMode,
|
||||||
|
|||||||
@@ -4,6 +4,14 @@ import type { ReplyPayload } from "../../auto-reply/types.js";
|
|||||||
import type { ReplyToMode } from "../../config/config.js";
|
import type { ReplyToMode } from "../../config/config.js";
|
||||||
import type { MarkdownTableMode } from "../../config/types.base.js";
|
import type { MarkdownTableMode } from "../../config/types.base.js";
|
||||||
import { danger, logVerbose } from "../../globals.js";
|
import { danger, logVerbose } from "../../globals.js";
|
||||||
|
import { fireAndForgetHook } from "../../hooks/fire-and-forget.js";
|
||||||
|
import { createInternalHookEvent, triggerInternalHook } from "../../hooks/internal-hooks.js";
|
||||||
|
import {
|
||||||
|
buildCanonicalSentMessageHookContext,
|
||||||
|
toInternalMessageSentContext,
|
||||||
|
toPluginMessageContext,
|
||||||
|
toPluginMessageSentEvent,
|
||||||
|
} from "../../hooks/message-hook-mappers.js";
|
||||||
import { formatErrorMessage } from "../../infra/errors.js";
|
import { formatErrorMessage } from "../../infra/errors.js";
|
||||||
import { buildOutboundMediaLoadOptions } from "../../media/load-options.js";
|
import { buildOutboundMediaLoadOptions } from "../../media/load-options.js";
|
||||||
import { isGifMedia, kindFromMime } from "../../media/mime.js";
|
import { isGifMedia, kindFromMime } from "../../media/mime.js";
|
||||||
@@ -493,10 +501,68 @@ async function maybePinFirstDeliveredMessage(params: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function emitMessageSentHooks(params: {
|
||||||
|
hookRunner: ReturnType<typeof getGlobalHookRunner>;
|
||||||
|
enabled: boolean;
|
||||||
|
sessionKeyForInternalHooks?: string;
|
||||||
|
chatId: string;
|
||||||
|
accountId?: string;
|
||||||
|
content: string;
|
||||||
|
success: boolean;
|
||||||
|
error?: string;
|
||||||
|
messageId?: number;
|
||||||
|
isGroup?: boolean;
|
||||||
|
groupId?: string;
|
||||||
|
}): void {
|
||||||
|
if (!params.enabled && !params.sessionKeyForInternalHooks) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const canonical = buildCanonicalSentMessageHookContext({
|
||||||
|
to: params.chatId,
|
||||||
|
content: params.content,
|
||||||
|
success: params.success,
|
||||||
|
error: params.error,
|
||||||
|
channelId: "telegram",
|
||||||
|
accountId: params.accountId,
|
||||||
|
conversationId: params.chatId,
|
||||||
|
messageId: typeof params.messageId === "number" ? String(params.messageId) : undefined,
|
||||||
|
isGroup: params.isGroup,
|
||||||
|
groupId: params.groupId,
|
||||||
|
});
|
||||||
|
if (params.enabled) {
|
||||||
|
fireAndForgetHook(
|
||||||
|
Promise.resolve(
|
||||||
|
params.hookRunner!.runMessageSent(
|
||||||
|
toPluginMessageSentEvent(canonical),
|
||||||
|
toPluginMessageContext(canonical),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"telegram: message_sent plugin hook failed",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!params.sessionKeyForInternalHooks) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fireAndForgetHook(
|
||||||
|
triggerInternalHook(
|
||||||
|
createInternalHookEvent(
|
||||||
|
"message",
|
||||||
|
"sent",
|
||||||
|
params.sessionKeyForInternalHooks,
|
||||||
|
toInternalMessageSentContext(canonical),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"telegram: message:sent internal hook failed",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export async function deliverReplies(params: {
|
export async function deliverReplies(params: {
|
||||||
replies: ReplyPayload[];
|
replies: ReplyPayload[];
|
||||||
chatId: string;
|
chatId: string;
|
||||||
accountId?: string;
|
accountId?: string;
|
||||||
|
sessionKeyForInternalHooks?: string;
|
||||||
|
mirrorIsGroup?: boolean;
|
||||||
|
mirrorGroupId?: string;
|
||||||
token: string;
|
token: string;
|
||||||
runtime: RuntimeEnv;
|
runtime: RuntimeEnv;
|
||||||
bot: Bot;
|
bot: Bot;
|
||||||
@@ -622,37 +688,31 @@ export async function deliverReplies(params: {
|
|||||||
firstDeliveredMessageId,
|
firstDeliveredMessageId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (hasMessageSentHooks) {
|
emitMessageSentHooks({
|
||||||
const deliveredThisReply = progress.deliveredCount > deliveredCountBeforeReply;
|
hookRunner,
|
||||||
void hookRunner?.runMessageSent(
|
enabled: hasMessageSentHooks,
|
||||||
{
|
sessionKeyForInternalHooks: params.sessionKeyForInternalHooks,
|
||||||
to: params.chatId,
|
chatId: params.chatId,
|
||||||
content: contentForSentHook,
|
accountId: params.accountId,
|
||||||
success: deliveredThisReply,
|
content: contentForSentHook,
|
||||||
},
|
success: progress.deliveredCount > deliveredCountBeforeReply,
|
||||||
{
|
messageId: firstDeliveredMessageId,
|
||||||
channelId: "telegram",
|
isGroup: params.mirrorIsGroup,
|
||||||
accountId: params.accountId,
|
groupId: params.mirrorGroupId,
|
||||||
conversationId: params.chatId,
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (hasMessageSentHooks) {
|
emitMessageSentHooks({
|
||||||
void hookRunner?.runMessageSent(
|
hookRunner,
|
||||||
{
|
enabled: hasMessageSentHooks,
|
||||||
to: params.chatId,
|
sessionKeyForInternalHooks: params.sessionKeyForInternalHooks,
|
||||||
content: contentForSentHook,
|
chatId: params.chatId,
|
||||||
success: false,
|
accountId: params.accountId,
|
||||||
error: error instanceof Error ? error.message : String(error),
|
content: contentForSentHook,
|
||||||
},
|
success: false,
|
||||||
{
|
error: error instanceof Error ? error.message : String(error),
|
||||||
channelId: "telegram",
|
isGroup: params.mirrorIsGroup,
|
||||||
accountId: params.accountId,
|
groupId: params.mirrorGroupId,
|
||||||
conversationId: params.chatId,
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { RuntimeEnv } from "../../runtime.js";
|
|||||||
import { deliverReplies } from "./delivery.js";
|
import { deliverReplies } from "./delivery.js";
|
||||||
|
|
||||||
const loadWebMedia = vi.fn();
|
const loadWebMedia = vi.fn();
|
||||||
|
const triggerInternalHook = vi.hoisted(() => vi.fn(async () => {}));
|
||||||
const messageHookRunner = vi.hoisted(() => ({
|
const messageHookRunner = vi.hoisted(() => ({
|
||||||
hasHooks: vi.fn<(name: string) => boolean>(() => false),
|
hasHooks: vi.fn<(name: string) => boolean>(() => false),
|
||||||
runMessageSending: vi.fn(),
|
runMessageSending: vi.fn(),
|
||||||
@@ -31,6 +32,16 @@ vi.mock("../../plugins/hook-runner-global.js", () => ({
|
|||||||
getGlobalHookRunner: () => messageHookRunner,
|
getGlobalHookRunner: () => messageHookRunner,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock("../../hooks/internal-hooks.js", async () => {
|
||||||
|
const actual = await vi.importActual<typeof import("../../hooks/internal-hooks.js")>(
|
||||||
|
"../../hooks/internal-hooks.js",
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
triggerInternalHook,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
vi.mock("grammy", () => ({
|
vi.mock("grammy", () => ({
|
||||||
InputFile: class {
|
InputFile: class {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -108,6 +119,7 @@ function createVoiceFailureHarness(params: {
|
|||||||
describe("deliverReplies", () => {
|
describe("deliverReplies", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
loadWebMedia.mockClear();
|
loadWebMedia.mockClear();
|
||||||
|
triggerInternalHook.mockReset();
|
||||||
messageHookRunner.hasHooks.mockReset();
|
messageHookRunner.hasHooks.mockReset();
|
||||||
messageHookRunner.hasHooks.mockReturnValue(false);
|
messageHookRunner.hasHooks.mockReturnValue(false);
|
||||||
messageHookRunner.runMessageSending.mockReset();
|
messageHookRunner.runMessageSending.mockReset();
|
||||||
@@ -199,6 +211,53 @@ describe("deliverReplies", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("emits internal message:sent when session hook context is available", async () => {
|
||||||
|
const runtime = createRuntime(false);
|
||||||
|
const sendMessage = vi.fn().mockResolvedValue({ message_id: 9, chat: { id: "123" } });
|
||||||
|
const bot = createBot({ sendMessage });
|
||||||
|
|
||||||
|
await deliverWith({
|
||||||
|
sessionKeyForInternalHooks: "agent:test:telegram:123",
|
||||||
|
mirrorIsGroup: true,
|
||||||
|
mirrorGroupId: "123",
|
||||||
|
replies: [{ text: "hello" }],
|
||||||
|
runtime,
|
||||||
|
bot,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(triggerInternalHook).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
type: "message",
|
||||||
|
action: "sent",
|
||||||
|
sessionKey: "agent:test:telegram:123",
|
||||||
|
context: expect.objectContaining({
|
||||||
|
to: "123",
|
||||||
|
content: "hello",
|
||||||
|
success: true,
|
||||||
|
channelId: "telegram",
|
||||||
|
conversationId: "123",
|
||||||
|
messageId: "9",
|
||||||
|
isGroup: true,
|
||||||
|
groupId: "123",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not emit internal message:sent without a session key", async () => {
|
||||||
|
const runtime = createRuntime(false);
|
||||||
|
const sendMessage = vi.fn().mockResolvedValue({ message_id: 11, chat: { id: "123" } });
|
||||||
|
const bot = createBot({ sendMessage });
|
||||||
|
|
||||||
|
await deliverWith({
|
||||||
|
replies: [{ text: "hello" }],
|
||||||
|
runtime,
|
||||||
|
bot,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(triggerInternalHook).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it("passes media metadata to message_sending hooks", async () => {
|
it("passes media metadata to message_sending hooks", async () => {
|
||||||
messageHookRunner.hasHooks.mockImplementation((name: string) => name === "message_sending");
|
messageHookRunner.hasHooks.mockImplementation((name: string) => name === "message_sending");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user