mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-20 06:20:55 +00:00
fix: honor Telegram available_reactions for status reactions
This commit is contained in:
@@ -64,6 +64,7 @@ import type { StickerMetadata, TelegramContext } from "./bot/types.js";
|
||||
import { evaluateTelegramGroupBaseAccess } from "./group-access.js";
|
||||
import {
|
||||
buildTelegramStatusReactionVariants,
|
||||
resolveTelegramAllowedEmojiReactions,
|
||||
resolveTelegramReactionVariant,
|
||||
resolveTelegramStatusReactionEmojis,
|
||||
} from "./status-reaction-variants.js";
|
||||
@@ -527,9 +528,11 @@ export const buildTelegramMessageContext = async ({
|
||||
messageId: number,
|
||||
reactions: Array<{ type: "emoji"; emoji: string }>,
|
||||
) => Promise<void>;
|
||||
getChat?: (chatId: number | string) => Promise<unknown>;
|
||||
};
|
||||
const reactionApi =
|
||||
typeof api.setMessageReaction === "function" ? api.setMessageReaction.bind(api) : null;
|
||||
const getChatApi = typeof api.getChat === "function" ? api.getChat.bind(api) : null;
|
||||
|
||||
// Status Reactions controller (lifecycle reactions)
|
||||
const statusReactionsConfig = cfg.messages?.statusReactions;
|
||||
@@ -542,6 +545,7 @@ export const buildTelegramMessageContext = async ({
|
||||
const statusReactionVariantsByEmoji = buildTelegramStatusReactionVariants(
|
||||
resolvedStatusReactionEmojis,
|
||||
);
|
||||
let allowedStatusReactionEmojisPromise: Promise<Set<string> | null> | null = null;
|
||||
const statusReactionController: StatusReactionController | null =
|
||||
statusReactionsEnabled && msg.message_id
|
||||
? createStatusReactionController({
|
||||
@@ -549,9 +553,23 @@ export const buildTelegramMessageContext = async ({
|
||||
adapter: {
|
||||
setReaction: async (emoji: string) => {
|
||||
if (reactionApi) {
|
||||
if (!allowedStatusReactionEmojisPromise) {
|
||||
allowedStatusReactionEmojisPromise = resolveTelegramAllowedEmojiReactions({
|
||||
chat: msg.chat,
|
||||
chatId,
|
||||
getChat: getChatApi ?? undefined,
|
||||
}).catch((err) => {
|
||||
logVerbose(
|
||||
`telegram status-reaction available_reactions lookup failed for chat ${chatId}: ${String(err)}`,
|
||||
);
|
||||
return new Set<string>();
|
||||
});
|
||||
}
|
||||
const allowedStatusReactionEmojis = await allowedStatusReactionEmojisPromise;
|
||||
const resolvedEmoji = resolveTelegramReactionVariant({
|
||||
requestedEmoji: emoji,
|
||||
variantsByRequestedEmoji: statusReactionVariantsByEmoji,
|
||||
allowedEmojiReactions: allowedStatusReactionEmojis,
|
||||
});
|
||||
if (!resolvedEmoji) {
|
||||
return;
|
||||
|
||||
@@ -2,7 +2,9 @@ import { describe, expect, it } from "vitest";
|
||||
import { DEFAULT_EMOJIS } from "../channels/status-reactions.js";
|
||||
import {
|
||||
buildTelegramStatusReactionVariants,
|
||||
extractTelegramAllowedEmojiReactions,
|
||||
isTelegramSupportedReactionEmoji,
|
||||
resolveTelegramAllowedEmojiReactions,
|
||||
resolveTelegramReactionVariant,
|
||||
resolveTelegramStatusReactionEmojis,
|
||||
} from "./status-reaction-variants.js";
|
||||
@@ -58,6 +60,45 @@ describe("isTelegramSupportedReactionEmoji", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("extractTelegramAllowedEmojiReactions", () => {
|
||||
it("returns undefined when chat does not include available_reactions", () => {
|
||||
const result = extractTelegramAllowedEmojiReactions({ id: 1 });
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns null when available_reactions is omitted/null", () => {
|
||||
const result = extractTelegramAllowedEmojiReactions({ available_reactions: null });
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("extracts emoji reactions only", () => {
|
||||
const result = extractTelegramAllowedEmojiReactions({
|
||||
available_reactions: [
|
||||
{ type: "emoji", emoji: "👍" },
|
||||
{ type: "custom_emoji", custom_emoji_id: "abc" },
|
||||
{ type: "emoji", emoji: "🔥" },
|
||||
],
|
||||
});
|
||||
expect(result ? Array.from(result).toSorted() : null).toEqual(["👍", "🔥"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveTelegramAllowedEmojiReactions", () => {
|
||||
it("uses getChat lookup when message chat does not include available_reactions", async () => {
|
||||
const getChat = async () => ({
|
||||
available_reactions: [{ type: "emoji", emoji: "👍" }],
|
||||
});
|
||||
|
||||
const result = await resolveTelegramAllowedEmojiReactions({
|
||||
chat: { id: 1 },
|
||||
chatId: 1,
|
||||
getChat,
|
||||
});
|
||||
|
||||
expect(result ? Array.from(result) : null).toEqual(["👍"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveTelegramReactionVariant", () => {
|
||||
it("returns requested emoji when already Telegram-supported", () => {
|
||||
const variantsByEmoji = buildTelegramStatusReactionVariants({
|
||||
@@ -96,6 +137,36 @@ describe("resolveTelegramReactionVariant", () => {
|
||||
expect(result).toBe("👍");
|
||||
});
|
||||
|
||||
it("respects chat allowed reactions", () => {
|
||||
const variantsByEmoji = buildTelegramStatusReactionVariants({
|
||||
...DEFAULT_EMOJIS,
|
||||
coding: "👨💻",
|
||||
});
|
||||
|
||||
const result = resolveTelegramReactionVariant({
|
||||
requestedEmoji: "👨💻",
|
||||
variantsByRequestedEmoji: variantsByEmoji,
|
||||
allowedEmojiReactions: new Set(["👍"]),
|
||||
});
|
||||
|
||||
expect(result).toBe("👍");
|
||||
});
|
||||
|
||||
it("returns undefined when no candidate is chat-allowed", () => {
|
||||
const variantsByEmoji = buildTelegramStatusReactionVariants({
|
||||
...DEFAULT_EMOJIS,
|
||||
coding: "👨💻",
|
||||
});
|
||||
|
||||
const result = resolveTelegramReactionVariant({
|
||||
requestedEmoji: "👨💻",
|
||||
variantsByRequestedEmoji: variantsByEmoji,
|
||||
allowedEmojiReactions: new Set(["🎉"]),
|
||||
});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined for empty requested emoji", () => {
|
||||
const result = resolveTelegramReactionVariant({
|
||||
requestedEmoji: " ",
|
||||
|
||||
@@ -152,9 +152,69 @@ export function isTelegramSupportedReactionEmoji(emoji: string): boolean {
|
||||
return TELEGRAM_SUPPORTED_REACTION_EMOJIS.has(emoji);
|
||||
}
|
||||
|
||||
export function extractTelegramAllowedEmojiReactions(
|
||||
chat: unknown,
|
||||
): Set<string> | null | undefined {
|
||||
if (!chat || typeof chat !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!Object.prototype.hasOwnProperty.call(chat, "available_reactions")) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const availableReactions = (chat as { available_reactions?: unknown }).available_reactions;
|
||||
if (availableReactions == null) {
|
||||
// Explicitly omitted/null => all emoji reactions are allowed in this chat.
|
||||
return null;
|
||||
}
|
||||
if (!Array.isArray(availableReactions)) {
|
||||
return new Set<string>();
|
||||
}
|
||||
|
||||
const allowed = new Set<string>();
|
||||
for (const reaction of availableReactions) {
|
||||
if (!reaction || typeof reaction !== "object") {
|
||||
continue;
|
||||
}
|
||||
const typedReaction = reaction as { type?: unknown; emoji?: unknown };
|
||||
if (typedReaction.type !== "emoji" || typeof typedReaction.emoji !== "string") {
|
||||
continue;
|
||||
}
|
||||
const emoji = typedReaction.emoji.trim();
|
||||
if (emoji) {
|
||||
allowed.add(emoji);
|
||||
}
|
||||
}
|
||||
return allowed;
|
||||
}
|
||||
|
||||
export async function resolveTelegramAllowedEmojiReactions(params: {
|
||||
chat: unknown;
|
||||
chatId: string | number;
|
||||
getChat?: (chatId: string | number) => Promise<unknown>;
|
||||
}): Promise<Set<string> | null> {
|
||||
const fromMessage = extractTelegramAllowedEmojiReactions(params.chat);
|
||||
if (fromMessage !== undefined) {
|
||||
return fromMessage;
|
||||
}
|
||||
|
||||
if (params.getChat) {
|
||||
const chatInfo = await params.getChat(params.chatId);
|
||||
const fromLookup = extractTelegramAllowedEmojiReactions(chatInfo);
|
||||
if (fromLookup !== undefined) {
|
||||
return fromLookup;
|
||||
}
|
||||
}
|
||||
|
||||
// If unavailable, assume no explicit restriction.
|
||||
return null;
|
||||
}
|
||||
|
||||
export function resolveTelegramReactionVariant(params: {
|
||||
requestedEmoji: string;
|
||||
variantsByRequestedEmoji: Map<string, string[]>;
|
||||
allowedEmojiReactions?: Set<string> | null;
|
||||
}): string | undefined {
|
||||
const requestedEmoji = normalizeEmoji(params.requestedEmoji);
|
||||
if (!requestedEmoji) {
|
||||
@@ -170,10 +230,12 @@ export function resolveTelegramReactionVariant(params: {
|
||||
]);
|
||||
|
||||
for (const candidate of variants) {
|
||||
if (isTelegramSupportedReactionEmoji(candidate)) {
|
||||
const isAllowedByChat =
|
||||
params.allowedEmojiReactions == null || params.allowedEmojiReactions.has(candidate);
|
||||
if (isAllowedByChat && isTelegramSupportedReactionEmoji(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return variants[0];
|
||||
return undefined;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user