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,4 +1,5 @@
import { Type } from "@sinclair/typebox";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { extractToolSend } from "openclaw/plugin-sdk/tool-send";
import { requiresExplicitMatrixDefaultAccount } from "./account-selection.js";
import { resolveDefaultMatrixAccountId, resolveMatrixAccount } from "./matrix/accounts.js";
@@ -287,13 +288,11 @@ export const matrixMessageActions: ChannelMessageActionAdapter = {
}
if (action === "permissions") {
const operation = (
const operation = normalizeLowercaseStringOrEmpty(
readStringParam(params, "operation") ??
readStringParam(params, "mode") ??
"verification-list"
)
.trim()
.toLowerCase();
readStringParam(params, "mode") ??
"verification-list",
);
const operationToAction: Record<string, string> = {
"encryption-status": "encryptionStatus",
"verification-status": "verificationStatus",

View File

@@ -42,7 +42,7 @@ function normalizeComparableTarget(value: string): string {
if (target.kind === "user") {
return `user:${normalizeMatrixUserId(target.id)}`;
}
return `${target.kind.toLowerCase()}:${target.id}`;
return `${normalizeLowercaseStringOrEmpty(target.kind)}:${target.id}`;
}
function resolveMatrixNativeTarget(raw: string): string | null {

View File

@@ -25,7 +25,10 @@ import {
createDefaultChannelRuntimeState,
} from "openclaw/plugin-sdk/status-helpers";
import { chunkTextForOutbound } from "openclaw/plugin-sdk/text-chunking";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "openclaw/plugin-sdk/text-runtime";
import { matrixMessageActions } from "./actions.js";
import { matrixApprovalCapability } from "./approval-native.js";
import { createMatrixPairingText, createMatrixProbeAccount } from "./channel-account-paths.js";
@@ -100,7 +103,7 @@ const listMatrixDirectoryPeersFromConfig =
if (!raw || raw === "*") {
return null;
}
const lowered = raw.toLowerCase();
const lowered = normalizeLowercaseStringOrEmpty(raw);
const cleaned = lowered.startsWith("user:") ? raw.slice("user:".length).trim() : raw;
return cleaned.startsWith("@") ? `user:${cleaned}` : cleaned;
},
@@ -116,7 +119,7 @@ const listMatrixDirectoryGroupsFromConfig =
if (!raw || raw === "*") {
return null;
}
const lowered = raw.toLowerCase();
const lowered = normalizeLowercaseStringOrEmpty(raw);
if (lowered.startsWith("room:") || lowered.startsWith("channel:")) {
return raw;
}

View File

@@ -1,4 +1,7 @@
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "openclaw/plugin-sdk/text-runtime";
import { resolveMatrixAuth } from "./matrix/client.js";
import { MatrixAuthedHttpClient } from "./matrix/sdk/http-client.js";
import { isMatrixQualifiedUserId, normalizeMatrixMessagingTarget } from "./matrix/target-ids.js";
@@ -66,7 +69,7 @@ async function resolveMatrixDirectoryContext(params: MatrixDirectoryLiveParams):
auth,
client: createMatrixDirectoryClient(auth),
query,
queryLower: query.toLowerCase(),
queryLower: normalizeLowercaseStringOrEmpty(query),
};
}
@@ -217,7 +220,7 @@ export async function listMatrixDirectoryGroupsLive(
for (const roomId of rooms) {
const name = await fetchMatrixRoomName(client, roomId);
if (!name || !name.toLowerCase().includes(queryLower)) {
if (!name || !normalizeLowercaseStringOrEmpty(name).includes(queryLower)) {
continue;
}
results.push({

View File

@@ -1,4 +1,5 @@
import { normalizeAccountId, normalizeOptionalAccountId } from "openclaw/plugin-sdk/account-id";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
const MATRIX_SCOPED_ENV_SUFFIXES = [
"HOMESERVER",
@@ -60,7 +61,7 @@ function decodeMatrixEnvAccountToken(token: string): string | undefined {
if (!char || !/[A-Z0-9]/.test(char)) {
return undefined;
}
decoded += char.toLowerCase();
decoded += normalizeLowercaseStringOrEmpty(char);
index += 1;
}
const normalized = normalizeOptionalAccountId(decoded);

View File

@@ -1,11 +1,12 @@
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
export function formatMatrixErrorMessage(err: unknown): string {
return formatErrorMessage(err);
}
export function formatMatrixErrorReason(err: unknown): string {
return formatMatrixErrorMessage(err).toLowerCase();
return normalizeLowercaseStringOrEmpty(formatMatrixErrorMessage(err));
}
export function isMatrixNotFoundError(err: unknown): boolean {

View File

@@ -1,5 +1,8 @@
import MarkdownIt from "markdown-it";
import { isAutoLinkedFileRef } from "openclaw/plugin-sdk/text-runtime";
import {
isAutoLinkedFileRef,
normalizeLowercaseStringOrEmpty,
} from "openclaw/plugin-sdk/text-runtime";
import type { MatrixClient } from "./sdk.js";
import { isMatrixQualifiedUserId } from "./target-ids.js";
@@ -147,7 +150,7 @@ function buildMentionCandidate(raw: string, start: number): MatrixMentionCandida
if (!normalized) {
return null;
}
const kind = normalized.raw.toLowerCase() === "@room" ? "room" : "user";
const kind = normalizeLowercaseStringOrEmpty(normalized.raw) === "@room" ? "room" : "user";
const base: MatrixMentionCandidate = {
raw: normalized.raw,
start,

View File

@@ -3,6 +3,7 @@ import {
type AllowlistMatch,
} from "openclaw/plugin-sdk/allow-from";
import { normalizeStringEntries } from "openclaw/plugin-sdk/string-normalization-runtime";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
function normalizeAllowList(list?: Array<string | number>) {
return normalizeStringEntries(list);
@@ -14,19 +15,19 @@ function normalizeMatrixUser(raw?: string | null): string {
return "";
}
if (!value.startsWith("@") || !value.includes(":")) {
return value.toLowerCase();
return normalizeLowercaseStringOrEmpty(value);
}
const withoutAt = value.slice(1);
const splitIndex = withoutAt.indexOf(":");
if (splitIndex === -1) {
return value.toLowerCase();
return normalizeLowercaseStringOrEmpty(value);
}
const localpart = withoutAt.slice(0, splitIndex).toLowerCase();
const server = withoutAt.slice(splitIndex + 1).toLowerCase();
const localpart = normalizeLowercaseStringOrEmpty(withoutAt.slice(0, splitIndex));
const server = normalizeLowercaseStringOrEmpty(withoutAt.slice(splitIndex + 1));
if (!server) {
return value.toLowerCase();
return normalizeLowercaseStringOrEmpty(value);
}
return `@${localpart}:${server.toLowerCase()}`;
return `@${localpart}:${server}`;
}
export function normalizeMatrixUserId(raw?: string | null): string {
@@ -34,7 +35,7 @@ export function normalizeMatrixUserId(raw?: string | null): string {
if (!trimmed) {
return "";
}
const lowered = trimmed.toLowerCase();
const lowered = normalizeLowercaseStringOrEmpty(trimmed);
if (lowered.startsWith("matrix:")) {
return normalizeMatrixUser(trimmed.slice("matrix:".length));
}
@@ -52,7 +53,7 @@ function normalizeMatrixAllowListEntry(raw: string): string {
if (trimmed === "*") {
return trimmed;
}
const lowered = trimmed.toLowerCase();
const lowered = normalizeLowercaseStringOrEmpty(trimmed);
if (lowered.startsWith("matrix:")) {
return `matrix:${normalizeMatrixUser(trimmed.slice("matrix:".length))}`;
}

View File

@@ -1,3 +1,4 @@
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { getMatrixRuntime } from "../../runtime.js";
import type { RoomMessageEventContent } from "./types.js";
@@ -26,7 +27,7 @@ function decodeNumericHtmlEntity(match: string, rawValue: string, radix: 10 | 16
function decodeHtmlEntities(value: string): string {
return value.replace(/&(#x?[0-9a-f]+|\w+);/gi, (match, entity: string) => {
const normalized = entity.toLowerCase();
const normalized = normalizeLowercaseStringOrEmpty(entity);
if (normalized.startsWith("#x")) {
return decodeNumericHtmlEntity(match, normalized.slice(2), 16);
}
@@ -38,12 +39,11 @@ function decodeHtmlEntities(value: string): string {
}
function normalizeVisibleMentionText(value: string): string {
return decodeHtmlEntities(
value.replace(/<[^>]+>/g, " ").replace(/[\u200b-\u200f\u202a-\u202e\u2060-\u206f]/g, ""),
)
.replace(/\s+/g, " ")
.trim()
.toLowerCase();
return normalizeLowercaseStringOrEmpty(
decodeHtmlEntities(
value.replace(/<[^>]+>/g, " ").replace(/[\u200b-\u200f\u202a-\u202e\u2060-\u206f]/g, ""),
).replace(/\s+/g, " "),
);
}
function extractVisibleMentionText(value?: string): string {

View File

@@ -1,3 +1,4 @@
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { getMatrixRuntime } from "../../runtime.js";
import type { MatrixClient } from "../sdk.js";
import { chunkMatrixText, sendMessageMatrix } from "../send.js";
@@ -15,7 +16,7 @@ function shouldSuppressReasoningReplyText(text?: string): boolean {
if (!trimmedStart) {
return false;
}
if (trimmedStart.toLowerCase().startsWith("reasoning:")) {
if (normalizeLowercaseStringOrEmpty(trimmedStart).startsWith("reasoning:")) {
return true;
}
THINKING_TAG_RE.lastIndex = 0;

View File

@@ -28,7 +28,7 @@ function resolveMatrixDmSessionKey(params: {
kind: "channel",
id: params.roomId,
},
}).toLowerCase();
});
}
function shouldApplyMatrixPerRoomDmSessionScope(params: {

View File

@@ -1,4 +1,7 @@
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "openclaw/plugin-sdk/text-runtime";
import type { MatrixClient } from "./sdk.js";
export const MATRIX_PROFILE_AVATAR_MAX_BYTES = 10 * 1024 * 1024;
@@ -24,11 +27,11 @@ export type MatrixProfileSyncResult = {
};
export function isMatrixMxcUri(value: string): boolean {
return normalizeOptionalString(value)?.toLowerCase().startsWith("mxc://") ?? false;
return normalizeLowercaseStringOrEmpty(normalizeOptionalString(value)).startsWith("mxc://");
}
export function isMatrixHttpAvatarUri(value: string): boolean {
const normalized = normalizeOptionalString(value)?.toLowerCase() ?? "";
const normalized = normalizeLowercaseStringOrEmpty(normalizeOptionalString(value));
return normalized.startsWith("https://") || normalized.startsWith("http://");
}

View File

@@ -1,4 +1,7 @@
import { normalizeOptionalStringifiedId } from "openclaw/plugin-sdk/text-runtime";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalStringifiedId,
} from "openclaw/plugin-sdk/text-runtime";
import { inspectMatrixDirectRooms, persistMatrixDirectRoomMapping } from "../direct-management.js";
import { isStrictDirectRoom } from "../direct-room.js";
import type { MatrixClient } from "../sdk.js";
@@ -83,7 +86,7 @@ async function resolveDirectRoomId(client: MatrixClient, userId: string): Promis
export async function resolveMatrixRoomId(client: MatrixClient, raw: string): Promise<string> {
const target = normalizeMatrixResolvableTarget(normalizeTarget(raw));
const lowered = target.toLowerCase();
const lowered = normalizeLowercaseStringOrEmpty(target);
if (lowered.startsWith("user:")) {
return await resolveDirectRoomId(client, target.slice("user:".length));
}

View File

@@ -1,3 +1,5 @@
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
type MatrixTarget = { kind: "room"; id: string } | { kind: "user"; id: string };
const MATRIX_PREFIX = "matrix:";
const ROOM_PREFIX = "room:";
@@ -7,7 +9,7 @@ const USER_PREFIX = "user:";
function stripKnownPrefixes(raw: string, prefixes: readonly string[]): string {
let normalized = raw.trim();
while (normalized) {
const lowered = normalized.toLowerCase();
const lowered = normalizeLowercaseStringOrEmpty(normalized);
const matched = prefixes.find((prefix) => lowered.startsWith(prefix));
if (!matched) {
return normalized;
@@ -22,7 +24,7 @@ export function resolveMatrixTargetIdentity(raw: string): MatrixTarget | null {
if (!normalized) {
return null;
}
const lowered = normalized.toLowerCase();
const lowered = normalizeLowercaseStringOrEmpty(normalized);
if (lowered.startsWith(USER_PREFIX)) {
const id = normalized.slice(USER_PREFIX.length).trim();
return id ? { kind: "user", id } : null;
@@ -73,7 +75,7 @@ export function normalizeMatrixDirectoryGroupId(raw: string): string | undefined
if (!normalized || normalized === "*") {
return undefined;
}
const lowered = normalized.toLowerCase();
const lowered = normalizeLowercaseStringOrEmpty(normalized);
if (lowered.startsWith(ROOM_PREFIX) || lowered.startsWith(CHANNEL_PREFIX)) {
return normalized;
}

View File

@@ -4,6 +4,7 @@ import {
type ChannelSetupWizardAdapter,
} from "openclaw/plugin-sdk/setup";
import { isPrivateNetworkOptInEnabled } from "openclaw/plugin-sdk/ssrf-runtime";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { requiresExplicitMatrixDefaultAccount } from "./account-selection.js";
import { listMatrixDirectoryGroupsLive } from "./directory-live.js";
import {
@@ -360,7 +361,9 @@ async function configureMatrixAccessPrompts(params: {
limit: 10,
});
const exact = matches.find(
(match) => (match.name ?? "").toLowerCase() === trimmed.toLowerCase(),
(match) =>
normalizeLowercaseStringOrEmpty(match.name) ===
normalizeLowercaseStringOrEmpty(trimmed),
);
const best = exact ?? matches[0];
if (best?.id) {

View File

@@ -1,11 +1,10 @@
import crypto from "node:crypto";
import path from "node:path";
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
export function sanitizeMatrixPathSegment(value: string): string {
const cleaned = value
.trim()
.toLowerCase()
const cleaned = normalizeLowercaseStringOrEmpty(value)
.replace(/[^a-z0-9._-]+/g, "_")
.replace(/^_+|_+$/g, "");
return cleaned || "unknown";

View File

@@ -81,10 +81,9 @@ function readRoomId(params: Record<string, unknown>, required = true): string {
}
function toSnakeCaseKey(key: string): string {
return key
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2")
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
.toLowerCase();
return normalizeOptionalLowercaseString(
key.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z0-9])([A-Z])/g, "$1_$2"),
)!;
}
function readRawParam(params: Record<string, unknown>, key: string): unknown {

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 {