refactor: dedupe bluebubbles and zalouser readers

This commit is contained in:
Peter Steinberger
2026-04-07 08:02:44 +01:00
parent 90a45a4907
commit 424b65b697
13 changed files with 45 additions and 27 deletions

View File

@@ -1,6 +1,7 @@
import crypto from "node:crypto";
import path from "node:path";
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { resolveBlueBubblesServerAccount } from "./account-resolve.js";
import { assertMultipartActionOk, postMultipartFormData } from "./multipart.js";
import {
@@ -161,7 +162,7 @@ export async function sendBlueBubblesAttachment(params: {
const wantsVoice = asVoice === true;
const fallbackName = wantsVoice ? "Audio Message" : "attachment";
filename = sanitizeFilename(filename, fallbackName);
contentType = contentType?.trim() || undefined;
contentType = normalizeOptionalString(contentType);
const { baseUrl, password, accountId, allowPrivateNetwork } = resolveAccount(opts);
const privateApiStatus = getCachedBlueBubblesPrivateApiStatus(accountId);
const privateApiEnabled = isBlueBubblesPrivateApiStatusEnabled(privateApiStatus);

View File

@@ -1,5 +1,9 @@
import { parseFiniteNumber } from "openclaw/plugin-sdk/infra-runtime";
import { asNullableRecord, readStringField } from "openclaw/plugin-sdk/text-runtime";
import {
asNullableRecord,
normalizeOptionalString,
readStringField,
} from "openclaw/plugin-sdk/text-runtime";
import { extractHandleFromChatGuid, normalizeBlueBubblesHandle } from "./targets.js";
import type { BlueBubblesAttachment } from "./types.js";
@@ -164,8 +168,8 @@ function extractReplyMetadata(message: Record<string, unknown>): {
: undefined;
return {
replyToId: (replyToId ?? fallbackReplyId)?.trim() || undefined,
replyToBody: replyToBody?.trim() || undefined,
replyToId: normalizeOptionalString(replyToId ?? fallbackReplyId),
replyToBody: normalizeOptionalString(replyToBody),
replyToSender: normalizedSender || undefined,
};
}
@@ -336,7 +340,7 @@ function normalizeParticipantEntry(entry: unknown): BlueBubblesParticipant | nul
if (!normalizedId) {
return null;
}
const name = nameRaw?.trim() || undefined;
const name = normalizeOptionalString(nameRaw);
return { id: normalizedId, name };
}
@@ -563,7 +567,9 @@ export function resolveTapbackContext(message: NormalizedWebhookMessage): {
if (!hasTapbackType && !hasTapbackMarker) {
return null;
}
const replyToId = message.associatedMessageGuid?.trim() || message.replyToId?.trim() || undefined;
const replyToId =
normalizeOptionalString(message.associatedMessageGuid) ??
normalizeOptionalString(message.replyToId);
const actionHint = resolveTapbackActionHint(associatedType);
const emojiHint =
message.associatedMessageEmoji?.trim() || REACTION_TYPE_MAP.get(associatedType ?? -1)?.emoji;

View File

@@ -4,6 +4,7 @@ import {
sendMediaWithLeadingCaption,
} from "openclaw/plugin-sdk/reply-payload";
import { isPrivateNetworkOptInEnabled } from "openclaw/plugin-sdk/ssrf-runtime";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { downloadBlueBubblesAttachment } from "./attachments.js";
import { markBlueBubblesChatRead, sendBlueBubblesTyping } from "./chat.js";
import { resolveBlueBubblesConversationRoute } from "./conversation-route.js";
@@ -92,8 +93,7 @@ const pendingOutboundMessageIds: PendingOutboundMessageId[] = [];
let pendingOutboundMessageIdCounter = 0;
function trimOrUndefined(value?: string | null): string | undefined {
const trimmed = value?.trim();
return trimmed ? trimmed : undefined;
return normalizeOptionalString(value);
}
function normalizeSnippet(value: string): string {
@@ -732,7 +732,7 @@ export async function processMessage(
chatId: message.chatId ?? undefined,
chatIdentifier: message.chatIdentifier ?? undefined,
});
const groupName = message.chatName?.trim() || undefined;
const groupName = normalizeOptionalString(message.chatName);
if (accessDecision.decision !== "allow") {
if (isGroup) {
@@ -1105,11 +1105,11 @@ export async function processMessage(
// The sender identity is included in the envelope body via formatInboundEnvelope.
const senderLabel = message.senderName || `user:${message.senderId}`;
const fromLabel = isGroup
? `${message.chatName?.trim() || "Group"} id:${peerId}`
? `${normalizeOptionalString(message.chatName) || "Group"} id:${peerId}`
: senderLabel !== message.senderId
? `${senderLabel} id:${message.senderId}`
: senderLabel;
const groupSubject = isGroup ? message.chatName?.trim() || undefined : undefined;
const groupSubject = isGroup ? normalizeOptionalString(message.chatName) : undefined;
const groupMembers = isGroup
? formatGroupMembers({
participants: message.participants,

View File

@@ -2,6 +2,7 @@ import { execFile, type ExecFileOptionsWithStringEncoding } from "node:child_pro
import { access, readdir } from "node:fs/promises";
import { join } from "node:path";
import { promisify } from "node:util";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import type { BlueBubblesParticipant } from "./monitor-normalize.js";
const execFileAsync = promisify(execFile) as ExecFileRunner;
@@ -313,7 +314,7 @@ export async function enrichBlueBubblesParticipantsWithContactNames(
try {
const resolved = await lookup([...pendingPhoneKeys]);
for (const phoneKey of pendingPhoneKeys) {
const name = resolved.get(phoneKey)?.trim() || undefined;
const name = normalizeOptionalString(resolved.get(phoneKey));
writeCacheEntry(phoneKey, name, nowMs);
if (name) {
cachedNames.set(phoneKey, name);

View File

@@ -146,7 +146,7 @@ export function registerBrowserCookiesAndStorageCommands(
method: "GET",
path: `/storage/${kind}`,
query: {
key: key?.trim() || undefined,
key: normalizeOptionalString(key),
targetId,
profile,
},

View File

@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
import path from "node:path";
import { Type } from "@sinclair/typebox";
import Ajv from "ajv";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import {
formatXHighModelHint,
normalizeThinkLevel,
@@ -96,8 +97,8 @@ export function createLlmTaskTool(api: OpenClawPluginApi) {
const defaultsModel = api.config?.agents?.defaults?.model;
const primary =
typeof defaultsModel === "string"
? defaultsModel.trim()
: (defaultsModel?.primary?.trim() ?? undefined);
? normalizeOptionalString(defaultsModel)
: normalizeOptionalString(defaultsModel?.primary);
const primaryProvider = typeof primary === "string" ? primary.split("/")[0] : undefined;
const primaryModel =
typeof primary === "string" ? primary.split("/").slice(1).join("/") : undefined;

View File

@@ -81,7 +81,7 @@ export function renderWikiMarkdown(params: {
export function extractTitleFromMarkdown(body: string): string | undefined {
const match = body.match(/^#\s+(.+?)\s*$/m);
return match?.[1]?.trim() || undefined;
return normalizeOptionalString(match?.[1]);
}
export function normalizeSourceIds(value: unknown): string[] {

View File

@@ -5,6 +5,7 @@ import os from "node:os";
import path from "node:path";
import { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";
import { resolveStateDir as resolvePluginStateDir } from "openclaw/plugin-sdk/state-paths";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { normalizeZaloReactionIcon } from "./reaction.js";
import type {
ZaloAuthStatus,
@@ -500,7 +501,7 @@ function resolveUploadedVoiceAsset(
continue;
}
if (fileType === "others" || fileType === "video") {
return { fileUrl, fileName: item.fileName?.trim() || undefined };
return { fileUrl, fileName: normalizeOptionalString(item.fileName) };
}
}
return undefined;
@@ -963,7 +964,8 @@ export async function listZaloGroupMembers(
continue;
}
currentById.set(id, {
displayName: member.dName?.trim() || member.zaloName?.trim() || undefined,
displayName:
normalizeOptionalString(member.dName) ?? normalizeOptionalString(member.zaloName),
avatar: member.avatar || undefined,
});
}
@@ -990,7 +992,9 @@ export async function listZaloGroupMembers(
continue;
}
profileMap.set(id, {
displayName: profileValue.displayName?.trim() || profileValue.zaloName?.trim() || undefined,
displayName:
normalizeOptionalString(profileValue.displayName) ??
normalizeOptionalString(profileValue.zaloName),
avatar: profileValue.avatar || undefined,
});
}
@@ -1024,7 +1028,7 @@ export async function resolveZaloGroupContext(
| undefined;
const context: ZaloGroupContext = {
groupId: normalizedGroupId,
name: groupInfo?.name?.trim() || undefined,
name: normalizeOptionalString(groupInfo?.name),
members: extractGroupMembersFromInfo(groupInfo),
};
writeCachedGroupContext(profile, context);

View File

@@ -3,6 +3,7 @@ import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { normalizeOptionalString } from "../src/shared/string-coerce.ts";
type Args = {
agentId: string;
@@ -36,7 +37,7 @@ const parseArgs = (): Args => {
continue;
}
if (arg === "--session-key" && args[i + 1]) {
sessionKey = String(args[++i]).trim() || undefined;
sessionKey = normalizeOptionalString(String(args[++i]));
continue;
}
}

View File

@@ -2,6 +2,7 @@ import { execFileSync } from "node:child_process";
import { mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { normalizeOptionalString } from "../../src/shared/string-coerce.ts";
import { parseReleaseVersion } from "../openclaw-npm-release-check.ts";
import { resolveNpmPublishPlan } from "./npm-publish-plan.mjs";
@@ -270,7 +271,7 @@ export function collectPublishablePluginPackages(
version,
channel: parsedVersion.channel,
publishTag: resolveNpmPublishPlan(version).publishTag,
installNpmSpec: packageJson.openclaw?.install?.npmSpec?.trim() || undefined,
installNpmSpec: normalizeOptionalString(packageJson.openclaw?.install?.npmSpec),
});
}

View File

@@ -1,5 +1,6 @@
import { createHash, randomBytes } from "node:crypto";
import type { OAuthCredentials } from "@mariozechner/pi-ai";
import { normalizeOptionalString } from "../shared/string-coerce.js";
export const CHUTES_OAUTH_ISSUER = "https://api.chutes.ai";
export const CHUTES_AUTHORIZE_ENDPOINT = `${CHUTES_OAUTH_ISSUER}/idp/authorize`;
@@ -66,8 +67,8 @@ export function parseOAuthCallbackInput(
}
}
const code = url.searchParams.get("code")?.trim();
const state = url.searchParams.get("state")?.trim();
const code = normalizeOptionalString(url.searchParams.get("code"));
const state = normalizeOptionalString(url.searchParams.get("state"));
if (!code) {
return { error: "Missing 'code' parameter in URL" };
}
@@ -181,7 +182,7 @@ export async function refreshChutesTokens(params: {
if (!clientId) {
throw new Error("Missing CHUTES_CLIENT_ID for Chutes OAuth refresh (set env var or re-auth).");
}
const clientSecret = process.env.CHUTES_CLIENT_SECRET?.trim() || undefined;
const clientSecret = normalizeOptionalString(process.env.CHUTES_CLIENT_SECRET);
const body = new URLSearchParams({
grant_type: "refresh_token",

View File

@@ -156,7 +156,7 @@ export function buildEmbeddedRunPayloads(params: {
})
: undefined;
const rawErrorMessage = lastAssistantErrored
? params.lastAssistant?.errorMessage?.trim() || undefined
? normalizeOptionalString(params.lastAssistant?.errorMessage)
: undefined;
const rawErrorFingerprint = rawErrorMessage
? getApiErrorPayloadFingerprint(rawErrorMessage)

View File

@@ -1,3 +1,5 @@
import { normalizeOptionalString } from "../shared/string-coerce.js";
export const testServiceAuditCodes = {
gatewayEntrypointMismatch: "gateway-entrypoint-mismatch",
gatewayTokenMismatch: "gateway-token-mismatch",
@@ -11,5 +13,5 @@ export function readEmbeddedGatewayTokenForTest(
) {
return command?.environmentValueSources?.OPENCLAW_GATEWAY_TOKEN === "file"
? undefined
: command?.environment?.OPENCLAW_GATEWAY_TOKEN?.trim() || undefined;
: normalizeOptionalString(command?.environment?.OPENCLAW_GATEWAY_TOKEN);
}