refactor: dedupe telegram trimmed readers

This commit is contained in:
Peter Steinberger
2026-04-07 23:17:32 +01:00
parent aaa88398bf
commit 96fe85fb77
18 changed files with 88 additions and 53 deletions

View File

@@ -34,7 +34,7 @@ function inspectTokenFile(pathValue: unknown): {
tokenSource: "tokenFile" | "none";
tokenStatus: TelegramCredentialStatus;
} | null {
const tokenFile = typeof pathValue === "string" ? pathValue.trim() : "";
const tokenFile = normalizeOptionalString(pathValue) ?? "";
if (!tokenFile) {
return null;
}
@@ -85,10 +85,10 @@ function inspectTokenValue(params: { cfg: OpenClawConfig; value: unknown }): {
tokenStatus: "configured_unavailable",
};
}
const envValue = process.env[ref.id];
if (envValue && envValue.trim()) {
const envValue = normalizeOptionalString(process.env[ref.id]);
if (envValue) {
return {
token: envValue.trim(),
token: envValue,
tokenSource: "env",
tokenStatus: "available",
};
@@ -187,7 +187,11 @@ function inspectTelegramAccountPrimary(params: {
}
const allowEnv = accountId === DEFAULT_ACCOUNT_ID;
const envToken = allowEnv ? (params.envToken ?? process.env.TELEGRAM_BOT_TOKEN)?.trim() : "";
const envToken = allowEnv
? (normalizeOptionalString(params.envToken) ??
normalizeOptionalString(process.env.TELEGRAM_BOT_TOKEN) ??
"")
: "";
if (envToken) {
return {
accountId,

View File

@@ -11,7 +11,10 @@ import {
resolveReactionMessageId,
} from "openclaw/plugin-sdk/channel-actions";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
import {
normalizeOptionalLowercaseString,
normalizeOptionalString,
} from "openclaw/plugin-sdk/text-runtime";
import { createTelegramActionGate, resolveTelegramPollActionGateState } from "./accounts.js";
import {
fitsTelegramCallbackData,
@@ -114,9 +117,8 @@ export function readTelegramButtons(
throw new Error(`buttons[${rowIndex}][${buttonIndex}] must be an object`);
}
const rawButton = button as RawTelegramButton;
const text = typeof rawButton.text === "string" ? rawButton.text.trim() : "";
const callbackData =
typeof rawButton.callback_data === "string" ? rawButton.callback_data.trim() : "";
const text = normalizeOptionalString(rawButton.text) ?? "";
const callbackData = normalizeOptionalString(rawButton.callback_data) ?? "";
if (!text || !callbackData) {
throw new Error(`buttons[${rowIndex}][${buttonIndex}] requires text and callback_data`);
}

View File

@@ -13,6 +13,7 @@ import {
type PluginApprovalRequest,
} from "openclaw/plugin-sdk/infra-runtime";
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { resolveTelegramInlineButtons } from "./button-types.js";
import {
isTelegramExecApprovalHandlerConfigured,
@@ -49,7 +50,7 @@ function resolveHandlerContext(params: ChannelApprovalCapabilityHandlerContext):
context: TelegramApprovalHandlerContext;
} | null {
const context = params.context as TelegramApprovalHandlerContext | undefined;
const accountId = params.accountId?.trim() || "";
const accountId = normalizeOptionalString(params.accountId) ?? "";
if (!context?.token || !accountId) {
return null;
}

View File

@@ -34,7 +34,7 @@ function resolveTurnSourceTelegramOriginTarget(
request: ApprovalRequest,
): TelegramOriginTarget | null {
const turnSourceChannel = normalizeLowercaseStringOrEmpty(request.request.turnSourceChannel);
const rawTurnSourceTo = request.request.turnSourceTo?.trim() || "";
const rawTurnSourceTo = normalizeOptionalString(request.request.turnSourceTo) ?? "";
const parsedTurnSourceTarget = rawTurnSourceTo ? parseTelegramTarget(rawTurnSourceTo) : null;
const turnSourceTo = normalizeTelegramChatId(parsedTurnSourceTarget?.chatId ?? rawTurnSourceTo);
if (turnSourceChannel !== "telegram" || !turnSourceTo) {

View File

@@ -10,6 +10,13 @@ vi.mock("openclaw/plugin-sdk/text-runtime", () => ({
fetchWithTimeout: fetchWithTimeoutMock,
isRecord: (value: unknown): value is Record<string, unknown> =>
typeof value === "object" && value !== null,
normalizeOptionalString: (value: unknown) => {
if (typeof value !== "string") {
return undefined;
}
const trimmed = value.trim();
return trimmed ? trimmed : undefined;
},
}));
function mockGetChatMemberStatus(status: string) {

View File

@@ -1,5 +1,6 @@
import type { TelegramGroupConfig } from "openclaw/plugin-sdk/config-runtime";
import type { TelegramNetworkConfig } from "openclaw/plugin-sdk/config-runtime";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
export type TelegramGroupMembershipAuditEntry = {
chatId: string;
@@ -46,7 +47,7 @@ export function collectTelegramUnmentionedGroupIds(
if (value.requireMention !== false) {
continue;
}
const id = String(key).trim();
const id = normalizeOptionalString(String(key)) ?? "";
if (!id) {
continue;
}
@@ -82,7 +83,7 @@ export async function auditTelegramGroupMembership(
params: AuditTelegramGroupMembershipParams,
): Promise<TelegramGroupMembershipAudit> {
const started = Date.now();
const token = params.token?.trim() ?? "";
const token = normalizeOptionalString(params.token) ?? "";
if (!token || params.groupIds.length === 0) {
return {
ok: true,

View File

@@ -5,6 +5,7 @@ import {
type AllowlistMatch,
} from "openclaw/plugin-sdk/allow-from";
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
export type NormalizedAllowFrom = {
entries: string[];
@@ -40,7 +41,9 @@ function warnInvalidAllowFromEntries(entries: string[]) {
}
export const normalizeAllowFrom = (list?: Array<string | number>): NormalizedAllowFrom => {
const entries = (list ?? []).map((value) => String(value).trim()).filter(Boolean);
const entries = (list ?? [])
.map((value) => normalizeOptionalString(String(value)) ?? "")
.filter(Boolean);
const hasWildcard = entries.includes("*");
const normalized = entries
.filter((value) => value !== "*")

View File

@@ -6,7 +6,7 @@ import type { Bot } from "grammy";
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
import { readStringValue } from "openclaw/plugin-sdk/text-runtime";
import { normalizeOptionalString, readStringValue } from "openclaw/plugin-sdk/text-runtime";
import { withTelegramApiErrorLogging } from "./api-logging.js";
import { normalizeTelegramCommandName, TELEGRAM_COMMAND_NAME_PATTERN } from "./command-config.js";
@@ -154,7 +154,7 @@ export function buildPluginTelegramMenuCommands(params: {
);
continue;
}
const description = typeof spec.description === "string" ? spec.description.trim() : "";
const description = normalizeOptionalString(spec.description) ?? "";
if (!description) {
issues.push(`Plugin command "/${normalized}" is missing a description.`);
continue;

View File

@@ -7,6 +7,7 @@ import type {
} from "openclaw/plugin-sdk/config-runtime";
import { readChannelAllowFromStore } from "openclaw/plugin-sdk/conversation-runtime";
import { normalizeAccountId } from "openclaw/plugin-sdk/routing";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { firstDefined, normalizeAllowFrom, type NormalizedAllowFrom } from "../bot-access.js";
import { normalizeTelegramReplyToMessageId } from "../outbound-params.js";
import { resolveTelegramPreviewStreamMode } from "../preview-streaming.js";
@@ -279,7 +280,8 @@ export function resolveTelegramDirectPeerId(params: {
chatId: number | string;
senderId?: number | string | null;
}) {
const senderId = params.senderId != null ? String(params.senderId).trim() : "";
const senderId =
params.senderId != null ? (normalizeOptionalString(String(params.senderId)) ?? "") : "";
if (senderId) {
return senderId;
}

View File

@@ -276,13 +276,13 @@ function targetsMatchTelegramReplySuppression(params: {
const origin = parseTelegramTarget(params.originTarget);
const target = parseTelegramTarget(params.targetKey);
const originThreadId =
origin.messageThreadId != null && String(origin.messageThreadId).trim()
? String(origin.messageThreadId).trim()
origin.messageThreadId != null && normalizeOptionalString(String(origin.messageThreadId))
? normalizeOptionalString(String(origin.messageThreadId))
: undefined;
const targetThreadId =
params.targetThreadId?.trim() ||
(target.messageThreadId != null && String(target.messageThreadId).trim()
? String(target.messageThreadId).trim()
normalizeOptionalString(params.targetThreadId) ||
(target.messageThreadId != null && normalizeOptionalString(String(target.messageThreadId))
? normalizeOptionalString(String(target.messageThreadId))
: undefined);
if (
normalizeOptionalLowercaseString(origin.chatId) !==
@@ -304,8 +304,8 @@ function resolveTelegramCommandConversation(params: {
}) {
const chatId = [params.originatingTo, params.commandTo, params.fallbackTo]
.map((candidate) => {
const trimmed = candidate?.trim();
return trimmed ? parseTelegramTarget(trimmed).chatId.trim() : "";
const trimmed = normalizeOptionalString(candidate) ?? "";
return trimmed ? (normalizeOptionalString(parseTelegramTarget(trimmed).chatId) ?? "") : "";
})
.find((candidate) => candidate.length > 0);
if (!chatId) {
@@ -331,12 +331,13 @@ function resolveTelegramInboundConversation(params: {
conversationId?: string;
threadId?: string | number;
}) {
const rawTarget = params.to?.trim() || params.conversationId?.trim() || "";
const rawTarget =
normalizeOptionalString(params.to) ?? normalizeOptionalString(params.conversationId) ?? "";
if (!rawTarget) {
return null;
}
const parsedTarget = parseTelegramTarget(rawTarget);
const chatId = parsedTarget.chatId.trim();
const chatId = normalizeOptionalString(parsedTarget.chatId) ?? "";
if (!chatId) {
return null;
}

View File

@@ -4,6 +4,7 @@ import {
} from "openclaw/plugin-sdk/channel-contract";
import { type OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { inspectTelegramAccount } from "./account-inspect.js";
import { listTelegramAccountIds, resolveTelegramAccount } from "./accounts.js";
import { isNumericTelegramUserId, normalizeTelegramAllowFromEntry } from "./allow-from.js";
@@ -34,7 +35,7 @@ function sanitizeForLog(value: string): string {
}
function hasAllowFromEntries(values?: DoctorAllowFromList): boolean {
return Array.isArray(values) && values.some((entry) => String(entry).trim());
return Array.isArray(values) && values.some((entry) => normalizeOptionalString(String(entry)));
}
function collectTelegramAccountScopes(
@@ -113,7 +114,7 @@ export function scanTelegramAllowFromUsernameEntries(
if (!normalized || normalized === "*" || isNumericTelegramUserId(normalized)) {
continue;
}
hits.push({ path: pathLabel, entry: String(entry).trim() });
hits.push({ path: pathLabel, entry: normalizeOptionalString(String(entry)) ?? "" });
}
};
@@ -177,7 +178,8 @@ export async function maybeRepairTelegramAllowFromUsernames(cfg: OpenClawConfig)
`- Telegram account ${accountId}: failed to inspect bot token (configured but unavailable in this command path).`,
);
}
const token = inspected.tokenSource === "none" ? "" : inspected.token.trim();
const token =
inspected.tokenSource === "none" ? "" : (normalizeOptionalString(inspected.token) ?? "");
if (token) {
resolverAccountIds.push(accountId);
}
@@ -195,7 +197,7 @@ export async function maybeRepairTelegramAllowFromUsernames(cfg: OpenClawConfig)
};
}
const resolveUserId = async (raw: string): Promise<string | null> => {
const trimmed = raw.trim();
const trimmed = normalizeOptionalString(raw) ?? "";
if (!trimmed) {
return null;
}
@@ -252,15 +254,15 @@ export async function maybeRepairTelegramAllowFromUsernames(cfg: OpenClawConfig)
const resolved = await resolveUserId(String(entry));
if (resolved) {
out.push(resolved);
replaced.push({ from: String(entry).trim(), to: resolved });
replaced.push({ from: normalizeOptionalString(String(entry)) ?? "", to: resolved });
} else {
out.push(String(entry).trim());
out.push(normalizeOptionalString(String(entry)) ?? "");
}
}
const deduped: DoctorAllowFromList = [];
const seen = new Set<string>();
for (const entry of out) {
const keyValue = String(entry).trim();
const keyValue = normalizeOptionalString(String(entry)) ?? "";
if (!keyValue || seen.has(keyValue)) {
continue;
}

View File

@@ -11,13 +11,16 @@ import type { TelegramExecApprovalConfig } from "openclaw/plugin-sdk/config-runt
import type { ExecApprovalRequest, PluginApprovalRequest } from "openclaw/plugin-sdk/infra-runtime";
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
import { normalizeAccountId } from "openclaw/plugin-sdk/routing";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "openclaw/plugin-sdk/text-runtime";
import { listTelegramAccountIds, resolveTelegramAccount } from "./accounts.js";
import { resolveTelegramInlineButtonsConfigScope } from "./inline-buttons.js";
import { normalizeTelegramChatId, resolveTelegramTargetChatType } from "./targets.js";
function normalizeApproverId(value: string | number): string {
return String(value).trim();
return normalizeOptionalString(String(value)) ?? "";
}
function normalizeTelegramDirectApproverId(value: string | number): string | undefined {

View File

@@ -7,6 +7,7 @@ import {
type SecretDefaults,
type SecretTargetRegistryEntry,
} from "openclaw/plugin-sdk/channel-secret-basic-runtime";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
export const secretTargetRegistryEntries = [
{
@@ -65,9 +66,9 @@ export function collectRuntimeConfigAssignments(params: {
return;
}
const { channel: telegram, surface } = resolved;
const baseTokenFile = typeof telegram.tokenFile === "string" ? telegram.tokenFile.trim() : "";
const baseTokenFile = normalizeOptionalString(telegram.tokenFile) ?? "";
const accountTokenFile = (account: Record<string, unknown>) =>
typeof account.tokenFile === "string" ? account.tokenFile.trim() : "";
normalizeOptionalString(account.tokenFile) ?? "";
collectConditionalChannelFieldAssignments({
channelKey: "telegram",
field: "botToken",
@@ -91,12 +92,10 @@ export function collectRuntimeConfigAssignments(params: {
"no enabled Telegram surface inherits this top-level botToken (tokenFile is configured).",
accountInactiveReason: "Telegram account is disabled or tokenFile is configured.",
});
const baseWebhookUrl = typeof telegram.webhookUrl === "string" ? telegram.webhookUrl.trim() : "";
const baseWebhookUrl = normalizeOptionalString(telegram.webhookUrl) ?? "";
const accountWebhookUrl = (account: Record<string, unknown>) =>
hasOwnProperty(account, "webhookUrl")
? typeof account.webhookUrl === "string"
? account.webhookUrl.trim()
: ""
? (normalizeOptionalString(account.webhookUrl) ?? "")
: baseWebhookUrl;
collectConditionalChannelFieldAssignments({
channelKey: "telegram",

View File

@@ -1,6 +1,7 @@
import { resolveNativeSkillsEnabled } from "openclaw/plugin-sdk/config-runtime";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { readChannelAllowFromStore } from "openclaw/plugin-sdk/conversation-runtime";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import type { ResolvedTelegramAccount } from "./accounts.js";
import { isNumericTelegramUserId, normalizeTelegramAllowFromEntry } from "./allow-from.js";
@@ -36,7 +37,8 @@ export async function collectTelegramSecurityAuditFindings(params: {
}
const telegramCfg = params.account.config ?? {};
const accountId = params.accountId?.trim() || params.account.accountId || "default";
const accountId =
normalizeOptionalString(params.accountId) ?? params.account.accountId ?? "default";
const defaultGroupPolicy = params.cfg.channels?.defaults?.groupPolicy;
const groupPolicy =
(telegramCfg.groupPolicy as string | undefined) ?? defaultGroupPolicy ?? "allowlist";
@@ -51,7 +53,9 @@ export async function collectTelegramSecurityAuditFindings(params: {
const storeAllowFrom = await readChannelAllowFromStore("telegram", process.env, accountId).catch(
() => [],
);
const storeHasWildcard = storeAllowFrom.some((value) => String(value).trim() === "*");
const storeHasWildcard = storeAllowFrom.some(
(value) => (normalizeOptionalString(String(value)) ?? "") === "*",
);
const invalidTelegramAllowFromEntries = new Set<string>();
collectInvalidTelegramAllowFromEntries({
entries: storeAllowFrom,
@@ -60,7 +64,9 @@ export async function collectTelegramSecurityAuditFindings(params: {
const groupAllowFrom = Array.isArray(telegramCfg.groupAllowFrom)
? telegramCfg.groupAllowFrom
: [];
const groupAllowFromHasWildcard = groupAllowFrom.some((value) => String(value).trim() === "*");
const groupAllowFromHasWildcard = groupAllowFrom.some(
(value) => (normalizeOptionalString(String(value)) ?? "") === "*",
);
collectInvalidTelegramAllowFromEntries({
entries: groupAllowFrom,
target: invalidTelegramAllowFromEntries,

View File

@@ -7,6 +7,7 @@ import {
patchChannelConfigForAccount,
} from "openclaw/plugin-sdk/setup";
import { formatCliCommand, formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import {
mergeTelegramAccountConfig,
resolveDefaultTelegramAccountId,
@@ -45,7 +46,8 @@ export function shouldShowTelegramDmAccessWarning(cfg: OpenClawConfig, accountId
const merged = mergeTelegramAccountConfig(cfg, accountId);
const policy = merged.dmPolicy ?? "pairing";
const hasAllowFrom =
Array.isArray(merged.allowFrom) && merged.allowFrom.some((entry) => String(entry).trim());
Array.isArray(merged.allowFrom) &&
merged.allowFrom.some((entry) => normalizeOptionalString(String(entry)));
return policy === "pairing" && !hasAllowFrom;
}

View File

@@ -9,7 +9,10 @@ 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 {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "openclaw/plugin-sdk/text-runtime";
import {
normalizeTelegramChatId,
normalizeTelegramLookupTarget,
@@ -89,7 +92,7 @@ function rewriteTargetIfMatch(params: {
if (typeof params.rawValue !== "string" && typeof params.rawValue !== "number") {
return null;
}
const value = String(params.rawValue).trim();
const value = normalizeOptionalString(String(params.rawValue)) ?? "";
if (!value) {
return null;
}

View File

@@ -253,8 +253,7 @@ function loadBindingsFromDisk(accountId: string): TelegramThreadBindingRecord[]
const bindings: TelegramThreadBindingRecord[] = [];
for (const entry of parsed.bindings) {
const conversationId = normalizeOptionalString(entry?.conversationId);
const targetSessionKey =
typeof entry?.targetSessionKey === "string" ? entry.targetSessionKey.trim() : "";
const targetSessionKey = normalizeOptionalString(entry?.targetSessionKey) ?? "";
const targetKind = entry?.targetKind === "subagent" ? "subagent" : "acp";
if (!conversationId || !targetSessionKey) {
continue;
@@ -591,8 +590,8 @@ export function createTelegramThreadBindingManager(
return null;
}
const threadName =
(typeof metadata.threadName === "string" ? metadata.threadName.trim() : "") ||
(typeof metadata.label === "string" ? metadata.label.trim() : "") ||
(normalizeOptionalString(metadata.threadName) ?? "") ||
(normalizeOptionalString(metadata.label) ?? "") ||
`Agent: ${targetSessionKey.split(":").pop()}`;
try {
const tokenResolution = resolveTelegramToken(cfg, { accountId });

View File

@@ -139,7 +139,7 @@ function isTrustedProxyAddress(
}
const blockList = new net.BlockList();
for (const proxy of trustedProxies) {
const trimmed = proxy.trim();
const trimmed = normalizeOptionalString(proxy) ?? "";
if (!trimmed) {
continue;
}
@@ -251,7 +251,7 @@ export async function startTelegramWebhook(opts: {
const healthPath = opts.healthPath ?? "/healthz";
const port = opts.port ?? 8787;
const host = opts.host ?? "127.0.0.1";
const secret = typeof opts.secret === "string" ? opts.secret.trim() : "";
const secret = normalizeOptionalString(opts.secret) ?? "";
if (!secret) {
throw new Error(
"Telegram webhook mode requires a non-empty secret token. " +