refactor: dedupe telegram matrix lowercase helpers

This commit is contained in:
Peter Steinberger
2026-04-07 20:01:52 +01:00
parent 182d41d678
commit 179ccb952c
34 changed files with 159 additions and 110 deletions

View File

@@ -1,3 +1,4 @@
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { parseTelegramTarget } from "./targets.js";
export function resolveTelegramAutoThreadId(params: {
@@ -13,7 +14,10 @@ export function resolveTelegramAutoThreadId(params: {
return undefined;
}
const parsedChannel = parseTelegramTarget(context.currentChannelId);
if (parsedTo.chatId.toLowerCase() !== parsedChannel.chatId.toLowerCase()) {
if (
normalizeLowercaseStringOrEmpty(parsedTo.chatId) !==
normalizeLowercaseStringOrEmpty(parsedChannel.chatId)
) {
return undefined;
}
return context.currentThreadTs;

View File

@@ -28,6 +28,7 @@ import {
} from "openclaw/plugin-sdk/reply-history";
import type { MsgContext } from "openclaw/plugin-sdk/reply-runtime";
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
import type { NormalizedAllowFrom } from "./bot-access.js";
import { isSenderAllowed } from "./bot-access.js";
import type {
@@ -118,7 +119,7 @@ export async function resolveTelegramInboundBody(params: {
historyLimit,
logger,
} = params;
const botUsername = primaryCtx.me?.username?.toLowerCase();
const botUsername = normalizeOptionalLowercaseString(primaryCtx.me?.username);
const mentionRegexes = buildMentionRegexes(cfg, routeAgentId);
const messageTextParts = getTelegramTextParts(msg);
const allowForCommands = isGroup ? effectiveGroupAllow : effectiveDmAllow;

View File

@@ -19,6 +19,7 @@ import {
import type { ResolvedAgentRoute } from "openclaw/plugin-sdk/routing";
import { logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
import { evaluateSupplementalContextVisibility } from "openclaw/plugin-sdk/security-runtime";
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
import type { NormalizedAllowFrom } from "./bot-access.js";
import { isSenderAllowed, normalizeAllowFrom } from "./bot-access.js";
import type {
@@ -241,7 +242,7 @@ export async function buildTelegramInboundContextPayload(params: {
topicConfig,
});
const commandBody = normalizeCommandBody(rawBody, {
botUsername: primaryCtx.me?.username?.toLowerCase(),
botUsername: normalizeOptionalLowercaseString(primaryCtx.me?.username),
});
const inboundHistory =
isGroup && historyKey && historyLimit > 0

View File

@@ -29,6 +29,7 @@ import { getRuntimeConfigSnapshot } from "openclaw/plugin-sdk/runtime-config-sna
import { danger, logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { getChildLogger } from "openclaw/plugin-sdk/runtime-env";
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { resolveTelegramAccount } from "./accounts.js";
import { withTelegramApiErrorLogging } from "./api-logging.js";
import { isSenderAllowed, normalizeDmAllowFromWithStore } from "./bot-access.js";
@@ -506,7 +507,7 @@ export const registerTelegramNativeCommands = ({
listNativeCommandSpecs().map((command) => normalizeTelegramCommandName(command.name)),
);
for (const command of skillCommands) {
reservedCommands.add(command.name.toLowerCase());
reservedCommands.add(normalizeLowercaseStringOrEmpty(command.name));
}
const customResolution = resolveTelegramCustomCommands({
commands: telegramCfg.customCommands,
@@ -524,7 +525,7 @@ export const registerTelegramNativeCommands = ({
[
...nativeCommands.map((command) => normalizeTelegramCommandName(command.name)),
...customCommands.map((command) => command.command),
].map((command) => command.toLowerCase()),
].map((command) => normalizeLowercaseStringOrEmpty(command)),
);
const pluginCatalog = buildPluginTelegramMenuCommands({
specs: pluginCommandSpecs,

View File

@@ -20,7 +20,10 @@ import { danger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtim
import { getChildLogger } from "openclaw/plugin-sdk/runtime-env";
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
import { createNonExitingRuntime, type RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import {
normalizeOptionalLowercaseString,
normalizeOptionalString,
} from "openclaw/plugin-sdk/text-runtime";
import { resolveTelegramAccount } from "./accounts.js";
import { defaultTelegramBotDeps, type TelegramBotDeps } from "./bot-deps.js";
import { registerTelegramHandlers } from "./bot-handlers.js";
@@ -129,7 +132,7 @@ function extractTelegramApiMethod(input: TelegramFetchInput): string | null {
const pathname = new URL(url).pathname;
const segments = pathname.split("/").filter(Boolean);
const method = segments.length > 0 ? (segments.at(-1) ?? null) : null;
return method?.toLowerCase() ?? null;
return normalizeOptionalLowercaseString(method) ?? null;
} catch {
return null;
}

View File

@@ -1,6 +1,9 @@
import type { Chat, Message, MessageOrigin, User } from "@grammyjs/types";
import type { NormalizedLocation } from "openclaw/plugin-sdk/channel-inbound";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "openclaw/plugin-sdk/text-runtime";
type TelegramMediaMessage = Pick<
Message,
@@ -123,8 +126,8 @@ function hasStandaloneTelegramMention(text: string, mention: string): boolean {
export function hasBotMention(msg: Message, botUsername: string) {
const { text, entities } = getTelegramTextParts(msg);
const mention = `@${botUsername}`.toLowerCase();
if (hasStandaloneTelegramMention(text.toLowerCase(), mention)) {
const mention = normalizeLowercaseStringOrEmpty(`@${botUsername}`);
if (hasStandaloneTelegramMention(normalizeLowercaseStringOrEmpty(text), mention)) {
return true;
}
for (const ent of entities) {
@@ -132,7 +135,7 @@ export function hasBotMention(msg: Message, botUsername: string) {
continue;
}
const slice = text.slice(ent.offset, ent.offset + ent.length);
if (slice.toLowerCase() === mention) {
if (normalizeLowercaseStringOrEmpty(slice) === mention) {
return true;
}
}

View File

@@ -17,6 +17,7 @@ import {
sanitizeAgentId,
} from "openclaw/plugin-sdk/routing";
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import {
buildTelegramGroupPeerId,
buildTelegramParentPeer,
@@ -64,32 +65,29 @@ export function resolveTelegramConversationRoute(params: {
// Preserve the configured topic agent ID so topic-bound sessions stay stable
// even when that agent is not present in the current config snapshot.
const topicAgentId = sanitizeAgentId(rawTopicAgentId);
route = {
...route,
agentId: topicAgentId,
sessionKey: buildAgentSessionKey({
const sessionKey = normalizeLowercaseStringOrEmpty(
buildAgentSessionKey({
agentId: topicAgentId,
channel: "telegram",
accountId: params.accountId,
peer: { kind: params.isGroup ? "group" : "direct", id: peerId },
dmScope: params.cfg.session?.dmScope,
identityLinks: params.cfg.session?.identityLinks,
}).toLowerCase(),
mainSessionKey: buildAgentMainSessionKey({
}),
);
const mainSessionKey = normalizeLowercaseStringOrEmpty(
buildAgentMainSessionKey({
agentId: topicAgentId,
}).toLowerCase(),
}),
);
route = {
...route,
agentId: topicAgentId,
sessionKey,
mainSessionKey,
lastRoutePolicy: deriveLastRoutePolicy({
sessionKey: buildAgentSessionKey({
agentId: topicAgentId,
channel: "telegram",
accountId: params.accountId,
peer: { kind: params.isGroup ? "group" : "direct", id: peerId },
dmScope: params.cfg.session?.dmScope,
identityLinks: params.cfg.session?.identityLinks,
}).toLowerCase(),
mainSessionKey: buildAgentMainSessionKey({
agentId: topicAgentId,
}).toLowerCase(),
sessionKey,
mainSessionKey,
}),
};
logVerbose(
@@ -170,18 +168,20 @@ export function resolveTelegramConversationBaseSessionKey(params: {
if (!isNamedAccountFallback || params.isGroup) {
return params.route.sessionKey;
}
return buildAgentSessionKey({
agentId: params.route.agentId,
channel: "telegram",
accountId: params.route.accountId,
peer: {
kind: "direct",
id: resolveTelegramDirectPeerId({
chatId: params.chatId,
senderId: params.senderId,
}),
},
dmScope: "per-account-channel-peer",
identityLinks: params.cfg.session?.identityLinks,
}).toLowerCase();
return normalizeLowercaseStringOrEmpty(
buildAgentSessionKey({
agentId: params.route.agentId,
channel: "telegram",
accountId: params.route.accountId,
peer: {
kind: "direct",
id: resolveTelegramDirectPeerId({
chatId: params.chatId,
senderId: params.senderId,
}),
},
dmScope: "per-account-channel-peer",
identityLinks: params.cfg.session?.identityLinks,
}),
);
}

View File

@@ -402,7 +402,9 @@ function formatErrorCodes(err: unknown): string {
function shouldUseTelegramTransportFallback(err: unknown): boolean {
const ctx: TelegramTransportFallbackContext = {
message:
err && typeof err === "object" && "message" in err ? String(err.message).toLowerCase() : "",
err && typeof err === "object" && "message" in err
? normalizeLowercaseStringOrEmpty(String(err.message))
: "",
codes: collectErrorCodes(err),
};
for (const rule of TELEGRAM_TRANSPORT_FALLBACK_RULES) {

View File

@@ -3,6 +3,7 @@ import {
FILE_REF_EXTENSIONS_WITH_TLD,
isAutoLinkedFileRef,
markdownToIR,
normalizeLowercaseStringOrEmpty,
type MarkdownLinkSpan,
type MarkdownIR,
renderMarkdownIRChunksWithinLimit,
@@ -182,7 +183,7 @@ export function wrapFileReferencesInHtml(html: string): string {
const tagStart = match.index;
const tagEnd = HTML_TAG_PATTERN.lastIndex;
const isClosing = match[1] === "</";
const tagName = match[2].toLowerCase();
const tagName = normalizeLowercaseStringOrEmpty(match[2]);
// Process text before this tag
const textBefore = deLinkified.slice(lastIndex, tagStart);
@@ -393,7 +394,7 @@ export function splitTelegramHtmlChunks(html: string, limit: number): string[] {
const rawTag = match[0];
const isClosing = match[1] === "</";
const tagName = match[2].toLowerCase();
const tagName = normalizeLowercaseStringOrEmpty(match[2]);
const isSelfClosing =
!isClosing &&
(TELEGRAM_SELF_CLOSING_HTML_TAGS.has(tagName) || rawTag.trimEnd().endsWith("/>"));

View File

@@ -1,6 +1,7 @@
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import type { TelegramGroupConfig } from "openclaw/plugin-sdk/config-runtime";
import { normalizeAccountId } from "openclaw/plugin-sdk/routing";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
type TelegramGroups = Record<string, TelegramGroupConfig>;
@@ -29,7 +30,7 @@ function resolveAccountGroups(
return { groups: exact.groups };
}
const matchKey = Object.keys(accounts).find(
(key) => key.toLowerCase() === normalized.toLowerCase(),
(key) => normalizeLowercaseStringOrEmpty(key) === normalizeLowercaseStringOrEmpty(normalized),
);
return { groups: matchKey ? accounts[matchKey]?.groups : undefined };
}

View File

@@ -114,7 +114,7 @@ function normalizeTelegramNetworkMethod(method?: string | null): string | null {
if (!trimmed) {
return null;
}
return trimmed.toLowerCase();
return normalizeLowercaseStringOrEmpty(trimmed);
}
export function tagTelegramNetworkError(err: unknown, origin: TelegramNetworkErrorOrigin): void {

View File

@@ -1,3 +1,4 @@
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { normalizeTelegramLookupTarget, parseTelegramTarget } from "./targets.js";
const TELEGRAM_PREFIX_RE = /^(telegram|tg):/i;
@@ -36,7 +37,7 @@ export function normalizeTelegramMessagingTarget(raw: string): string | undefine
if (!normalizedBody) {
return undefined;
}
return `telegram:${normalizedBody}`.toLowerCase();
return normalizeLowercaseStringOrEmpty(`telegram:${normalizedBody}`);
}
export function looksLikeTelegramTargetId(raw: string): boolean {

View File

@@ -5,6 +5,7 @@ import {
sleepWithAbort,
} from "openclaw/plugin-sdk/runtime-env";
import { formatErrorMessage } from "openclaw/plugin-sdk/ssrf-runtime";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { withTelegramApiErrorLogging } from "./api-logging.js";
import { createTelegramBot } from "./bot.js";
import { type TelegramTransport } from "./fetch.js";
@@ -450,7 +451,7 @@ const isGetUpdatesConflict = (err: unknown) => {
}
const haystack = [typed.method, typed.description, typed.message]
.filter((value): value is string => typeof value === "string")
.join(" ")
.toLowerCase();
return haystack.includes("getupdates");
.join(" ");
const normalizedHaystack = normalizeLowercaseStringOrEmpty(haystack);
return normalizedHaystack.includes("getupdates");
};

View File

@@ -1,7 +1,10 @@
import { formatReasoningMessage } from "openclaw/plugin-sdk/agent-runtime";
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
import { findCodeRegions, isInsideCode } from "openclaw/plugin-sdk/text-runtime";
import { stripReasoningTagsFromText } from "openclaw/plugin-sdk/text-runtime";
import {
normalizeLowercaseStringOrEmpty,
stripReasoningTagsFromText,
} from "openclaw/plugin-sdk/text-runtime";
const REASONING_MESSAGE_PREFIX = "Reasoning:\n";
const REASONING_TAG_PREFIXES = [
@@ -44,7 +47,7 @@ function extractThinkingFromTaggedStreamOutsideCode(text: string): string {
}
function isPartialReasoningTagPrefix(text: string): boolean {
const trimmed = text.trimStart().toLowerCase();
const trimmed = normalizeLowercaseStringOrEmpty(text.trimStart());
if (!trimmed.startsWith("<")) {
return false;
}

View File

@@ -4,6 +4,7 @@ import {
sleepWithAbort,
type BackoffPolicy,
} from "openclaw/plugin-sdk/runtime-env";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
export type TelegramSendChatActionLogger = (message: string) => void;
@@ -60,7 +61,9 @@ function is401Error(error: unknown): boolean {
return false;
}
const message = error instanceof Error ? error.message : JSON.stringify(error);
return message.includes("401") || message.toLowerCase().includes("unauthorized");
return (
message.includes("401") || normalizeLowercaseStringOrEmpty(message).includes("unauthorized")
);
}
/**

View File

@@ -16,6 +16,7 @@ import {
} from "openclaw/plugin-sdk/media-runtime";
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { STATE_DIR } from "openclaw/plugin-sdk/state-paths";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { getTelegramRuntime } from "./runtime.js";
const CACHE_FILE = path.join(STATE_DIR, "telegram", "sticker-cache.json");
@@ -75,12 +76,12 @@ export function cacheSticker(sticker: CachedSticker): void {
*/
export function searchStickers(query: string, limit = 10): CachedSticker[] {
const cache = loadCache();
const queryLower = query.toLowerCase();
const queryLower = normalizeLowercaseStringOrEmpty(query);
const results: Array<{ sticker: CachedSticker; score: number }> = [];
for (const sticker of Object.values(cache.stickers)) {
let score = 0;
const descLower = sticker.description.toLowerCase();
const descLower = normalizeLowercaseStringOrEmpty(sticker.description);
// Exact substring match in description
if (descLower.includes(queryLower)) {
@@ -102,7 +103,7 @@ export function searchStickers(query: string, limit = 10): CachedSticker[] {
}
// Set name match
if (sticker.setName?.toLowerCase().includes(queryLower)) {
if (normalizeLowercaseStringOrEmpty(sticker.setName).includes(queryLower)) {
score += 3;
}
@@ -193,7 +194,8 @@ export async function describeStickerImage(params: DescribeStickerParams): Promi
const selectCatalogModel = (provider: string) => {
const entries = catalog.filter(
(entry) =>
entry.provider.toLowerCase() === provider.toLowerCase() && modelSupportsVision(entry),
normalizeLowercaseStringOrEmpty(entry.provider) ===
normalizeLowercaseStringOrEmpty(provider) && modelSupportsVision(entry),
);
if (entries.length === 0) {
return undefined;

View File

@@ -9,6 +9,7 @@ import {
saveCronStore,
} from "openclaw/plugin-sdk/config-runtime";
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import {
normalizeTelegramChatId,
normalizeTelegramLookupTarget,
@@ -30,7 +31,7 @@ function normalizeTelegramLookupTargetForMatch(raw: string): string | undefined
if (!normalized) {
return undefined;
}
return normalized.startsWith("@") ? normalized.toLowerCase() : normalized;
return normalized.startsWith("@") ? normalizeLowercaseStringOrEmpty(normalized) : normalized;
}
function normalizeTelegramTargetForMatch(raw: string): string | undefined {