mirror of https://github.com/openclaw/openclaw.git
fix: preselect Telegram-supported status reaction variants
This commit is contained in:
parent
6a27787209
commit
d28b61dc2d
|
|
@ -62,6 +62,11 @@ import {
|
|||
} from "./bot/helpers.js";
|
||||
import type { StickerMetadata, TelegramContext } from "./bot/types.js";
|
||||
import { evaluateTelegramGroupBaseAccess } from "./group-access.js";
|
||||
import {
|
||||
buildTelegramStatusReactionVariants,
|
||||
resolveTelegramReactionVariant,
|
||||
resolveTelegramStatusReactionEmojis,
|
||||
} from "./status-reaction-variants.js";
|
||||
|
||||
export type TelegramMediaRef = {
|
||||
path: string;
|
||||
|
|
@ -530,6 +535,13 @@ export const buildTelegramMessageContext = async ({
|
|||
const statusReactionsConfig = cfg.messages?.statusReactions;
|
||||
const statusReactionsEnabled =
|
||||
statusReactionsConfig?.enabled === true && Boolean(reactionApi) && shouldAckReaction();
|
||||
const resolvedStatusReactionEmojis = resolveTelegramStatusReactionEmojis({
|
||||
initialEmoji: ackReaction,
|
||||
overrides: statusReactionsConfig?.emojis,
|
||||
});
|
||||
const statusReactionVariantsByEmoji = buildTelegramStatusReactionVariants(
|
||||
resolvedStatusReactionEmojis,
|
||||
);
|
||||
const statusReactionController: StatusReactionController | null =
|
||||
statusReactionsEnabled && msg.message_id
|
||||
? createStatusReactionController({
|
||||
|
|
@ -537,13 +549,22 @@ export const buildTelegramMessageContext = async ({
|
|||
adapter: {
|
||||
setReaction: async (emoji: string) => {
|
||||
if (reactionApi) {
|
||||
await reactionApi(chatId, msg.message_id, [{ type: "emoji", emoji }]);
|
||||
const resolvedEmoji = resolveTelegramReactionVariant({
|
||||
requestedEmoji: emoji,
|
||||
variantsByRequestedEmoji: statusReactionVariantsByEmoji,
|
||||
});
|
||||
if (!resolvedEmoji) {
|
||||
return;
|
||||
}
|
||||
await reactionApi(chatId, msg.message_id, [
|
||||
{ type: "emoji", emoji: resolvedEmoji },
|
||||
]);
|
||||
}
|
||||
},
|
||||
// Telegram replaces atomically — no removeReaction needed
|
||||
},
|
||||
initialEmoji: ackReaction,
|
||||
emojis: statusReactionsConfig?.emojis,
|
||||
emojis: resolvedStatusReactionEmojis,
|
||||
timing: statusReactionsConfig?.timing,
|
||||
onError: (err) => {
|
||||
logVerbose(`telegram status-reaction error for chat ${chatId}: ${String(err)}`);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,107 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { DEFAULT_EMOJIS } from "../channels/status-reactions.js";
|
||||
import {
|
||||
buildTelegramStatusReactionVariants,
|
||||
isTelegramSupportedReactionEmoji,
|
||||
resolveTelegramReactionVariant,
|
||||
resolveTelegramStatusReactionEmojis,
|
||||
} from "./status-reaction-variants.js";
|
||||
|
||||
describe("resolveTelegramStatusReactionEmojis", () => {
|
||||
it("falls back to Telegram-safe defaults for empty overrides", () => {
|
||||
const result = resolveTelegramStatusReactionEmojis({
|
||||
initialEmoji: "👀",
|
||||
overrides: {
|
||||
thinking: " ",
|
||||
done: "\n",
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.queued).toBe("👀");
|
||||
expect(result.thinking).toBe(DEFAULT_EMOJIS.thinking);
|
||||
expect(result.done).toBe(DEFAULT_EMOJIS.done);
|
||||
});
|
||||
|
||||
it("preserves explicit non-empty overrides", () => {
|
||||
const result = resolveTelegramStatusReactionEmojis({
|
||||
initialEmoji: "👀",
|
||||
overrides: {
|
||||
thinking: "🫡",
|
||||
done: "🎉",
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.thinking).toBe("🫡");
|
||||
expect(result.done).toBe("🎉");
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildTelegramStatusReactionVariants", () => {
|
||||
it("puts requested emoji first and appends Telegram fallbacks", () => {
|
||||
const variants = buildTelegramStatusReactionVariants({
|
||||
...DEFAULT_EMOJIS,
|
||||
coding: "🛠️",
|
||||
});
|
||||
|
||||
expect(variants.get("🛠️")).toEqual(["🛠️", "👨💻", "🔥", "⚡"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isTelegramSupportedReactionEmoji", () => {
|
||||
it("accepts Telegram-supported reaction emojis", () => {
|
||||
expect(isTelegramSupportedReactionEmoji("👀")).toBe(true);
|
||||
expect(isTelegramSupportedReactionEmoji("👨💻")).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects unsupported emojis", () => {
|
||||
expect(isTelegramSupportedReactionEmoji("🫠")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveTelegramReactionVariant", () => {
|
||||
it("returns requested emoji when already Telegram-supported", () => {
|
||||
const variantsByEmoji = buildTelegramStatusReactionVariants({
|
||||
...DEFAULT_EMOJIS,
|
||||
coding: "👨💻",
|
||||
});
|
||||
|
||||
const result = resolveTelegramReactionVariant({
|
||||
requestedEmoji: "👨💻",
|
||||
variantsByRequestedEmoji: variantsByEmoji,
|
||||
});
|
||||
|
||||
expect(result).toBe("👨💻");
|
||||
});
|
||||
|
||||
it("returns first Telegram-supported fallback for unsupported requested emoji", () => {
|
||||
const variantsByEmoji = buildTelegramStatusReactionVariants({
|
||||
...DEFAULT_EMOJIS,
|
||||
coding: "🛠️",
|
||||
});
|
||||
|
||||
const result = resolveTelegramReactionVariant({
|
||||
requestedEmoji: "🛠️",
|
||||
variantsByRequestedEmoji: variantsByEmoji,
|
||||
});
|
||||
|
||||
expect(result).toBe("👨💻");
|
||||
});
|
||||
|
||||
it("uses generic Telegram fallbacks for unknown emojis", () => {
|
||||
const result = resolveTelegramReactionVariant({
|
||||
requestedEmoji: "🫠",
|
||||
variantsByRequestedEmoji: new Map(),
|
||||
});
|
||||
|
||||
expect(result).toBe("👍");
|
||||
});
|
||||
|
||||
it("returns undefined for empty requested emoji", () => {
|
||||
const result = resolveTelegramReactionVariant({
|
||||
requestedEmoji: " ",
|
||||
variantsByRequestedEmoji: new Map(),
|
||||
});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
import { DEFAULT_EMOJIS, type StatusReactionEmojis } from "../channels/status-reactions.js";
|
||||
|
||||
type StatusReactionEmojiKey = keyof Required<StatusReactionEmojis>;
|
||||
|
||||
const TELEGRAM_GENERIC_REACTION_FALLBACKS = ["👍", "👀", "🔥"] as const;
|
||||
|
||||
const TELEGRAM_SUPPORTED_REACTION_EMOJIS = new Set<string>([
|
||||
"❤",
|
||||
"👍",
|
||||
"👎",
|
||||
"🔥",
|
||||
"🥰",
|
||||
"👏",
|
||||
"😁",
|
||||
"🤔",
|
||||
"🤯",
|
||||
"😱",
|
||||
"🤬",
|
||||
"😢",
|
||||
"🎉",
|
||||
"🤩",
|
||||
"🤮",
|
||||
"💩",
|
||||
"🙏",
|
||||
"👌",
|
||||
"🕊",
|
||||
"🤡",
|
||||
"🥱",
|
||||
"🥴",
|
||||
"😍",
|
||||
"🐳",
|
||||
"❤🔥",
|
||||
"🌚",
|
||||
"🌭",
|
||||
"💯",
|
||||
"🤣",
|
||||
"⚡",
|
||||
"🍌",
|
||||
"🏆",
|
||||
"💔",
|
||||
"🤨",
|
||||
"😐",
|
||||
"🍓",
|
||||
"🍾",
|
||||
"💋",
|
||||
"🖕",
|
||||
"😈",
|
||||
"😴",
|
||||
"😭",
|
||||
"🤓",
|
||||
"👻",
|
||||
"👨💻",
|
||||
"👀",
|
||||
"🎃",
|
||||
"🙈",
|
||||
"😇",
|
||||
"😨",
|
||||
"🤝",
|
||||
"✍",
|
||||
"🤗",
|
||||
"🫡",
|
||||
"🎅",
|
||||
"🎄",
|
||||
"☃",
|
||||
"💅",
|
||||
"🤪",
|
||||
"🗿",
|
||||
"🆒",
|
||||
"💘",
|
||||
"🙉",
|
||||
"🦄",
|
||||
"😘",
|
||||
"💊",
|
||||
"🙊",
|
||||
"😎",
|
||||
"👾",
|
||||
"🤷♂",
|
||||
"🤷",
|
||||
"🤷♀",
|
||||
"😡",
|
||||
]);
|
||||
|
||||
export const TELEGRAM_STATUS_REACTION_VARIANTS: Record<StatusReactionEmojiKey, string[]> = {
|
||||
queued: ["👀", "👍", "🔥"],
|
||||
thinking: ["🤔", "🤓", "👀"],
|
||||
tool: ["🔥", "⚡", "👍"],
|
||||
coding: ["👨💻", "🔥", "⚡"],
|
||||
web: ["⚡", "🔥", "👍"],
|
||||
done: ["👍", "🎉", "💯"],
|
||||
error: ["😱", "😨", "🤯"],
|
||||
stallSoft: ["🥱", "😴", "🤔"],
|
||||
stallHard: ["😨", "😱", "⚡"],
|
||||
};
|
||||
|
||||
const STATUS_REACTION_EMOJI_KEYS: StatusReactionEmojiKey[] = [
|
||||
"queued",
|
||||
"thinking",
|
||||
"tool",
|
||||
"coding",
|
||||
"web",
|
||||
"done",
|
||||
"error",
|
||||
"stallSoft",
|
||||
"stallHard",
|
||||
];
|
||||
|
||||
function normalizeEmoji(value: string | undefined): string | undefined {
|
||||
const trimmed = value?.trim();
|
||||
return trimmed ? trimmed : undefined;
|
||||
}
|
||||
|
||||
function toUniqueNonEmpty(values: string[]): string[] {
|
||||
return Array.from(new Set(values.map((value) => value.trim()).filter(Boolean)));
|
||||
}
|
||||
|
||||
export function resolveTelegramStatusReactionEmojis(params: {
|
||||
initialEmoji: string;
|
||||
overrides?: StatusReactionEmojis;
|
||||
}): Required<StatusReactionEmojis> {
|
||||
const { overrides } = params;
|
||||
const queuedFallback = normalizeEmoji(params.initialEmoji) ?? DEFAULT_EMOJIS.queued;
|
||||
return {
|
||||
queued: normalizeEmoji(overrides?.queued) ?? queuedFallback,
|
||||
thinking: normalizeEmoji(overrides?.thinking) ?? DEFAULT_EMOJIS.thinking,
|
||||
tool: normalizeEmoji(overrides?.tool) ?? DEFAULT_EMOJIS.tool,
|
||||
coding: normalizeEmoji(overrides?.coding) ?? DEFAULT_EMOJIS.coding,
|
||||
web: normalizeEmoji(overrides?.web) ?? DEFAULT_EMOJIS.web,
|
||||
done: normalizeEmoji(overrides?.done) ?? DEFAULT_EMOJIS.done,
|
||||
error: normalizeEmoji(overrides?.error) ?? DEFAULT_EMOJIS.error,
|
||||
stallSoft: normalizeEmoji(overrides?.stallSoft) ?? DEFAULT_EMOJIS.stallSoft,
|
||||
stallHard: normalizeEmoji(overrides?.stallHard) ?? DEFAULT_EMOJIS.stallHard,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildTelegramStatusReactionVariants(
|
||||
emojis: Required<StatusReactionEmojis>,
|
||||
): Map<string, string[]> {
|
||||
const variantsByRequested = new Map<string, string[]>();
|
||||
for (const key of STATUS_REACTION_EMOJI_KEYS) {
|
||||
const requested = normalizeEmoji(emojis[key]);
|
||||
if (!requested) {
|
||||
continue;
|
||||
}
|
||||
const fallbackVariants = TELEGRAM_STATUS_REACTION_VARIANTS[key] ?? [];
|
||||
const candidates = toUniqueNonEmpty([requested, ...fallbackVariants]);
|
||||
variantsByRequested.set(requested, candidates);
|
||||
}
|
||||
return variantsByRequested;
|
||||
}
|
||||
|
||||
export function isTelegramSupportedReactionEmoji(emoji: string): boolean {
|
||||
return TELEGRAM_SUPPORTED_REACTION_EMOJIS.has(emoji);
|
||||
}
|
||||
|
||||
export function resolveTelegramReactionVariant(params: {
|
||||
requestedEmoji: string;
|
||||
variantsByRequestedEmoji: Map<string, string[]>;
|
||||
}): string | undefined {
|
||||
const requestedEmoji = normalizeEmoji(params.requestedEmoji);
|
||||
if (!requestedEmoji) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const configuredVariants = params.variantsByRequestedEmoji.get(requestedEmoji) ?? [
|
||||
requestedEmoji,
|
||||
];
|
||||
const variants = toUniqueNonEmpty([
|
||||
...configuredVariants,
|
||||
...TELEGRAM_GENERIC_REACTION_FALLBACKS,
|
||||
]);
|
||||
|
||||
for (const candidate of variants) {
|
||||
if (isTelegramSupportedReactionEmoji(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return variants[0];
|
||||
}
|
||||
Loading…
Reference in New Issue