mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 09:41:11 +00:00
refactor: dedupe messaging lowercase helpers
This commit is contained in:
@@ -589,7 +589,7 @@ export function parseTapbackText(params: {
|
||||
quotedText: string;
|
||||
} | null {
|
||||
const trimmed = params.text.trim();
|
||||
const lower = trimmed.toLowerCase();
|
||||
const lower = normalizeLowercaseStringOrEmpty(trimmed);
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@ import {
|
||||
resolveServicePrefixedAllowTarget,
|
||||
resolveServicePrefixedTarget,
|
||||
} from "openclaw/plugin-sdk/channel-targets";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
export type BlueBubblesService = "imessage" | "sms" | "auto";
|
||||
|
||||
@@ -28,18 +32,6 @@ const SERVICE_PREFIXES: Array<{ prefix: string; service: BlueBubblesService }> =
|
||||
const CHAT_IDENTIFIER_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
const CHAT_IDENTIFIER_HEX_RE = /^[0-9a-f]{24,64}$/i;
|
||||
|
||||
function normalizeOptionalString(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed ? trimmed : undefined;
|
||||
}
|
||||
|
||||
function normalizeLowercaseStringOrEmpty(value: unknown): string {
|
||||
return normalizeOptionalString(value)?.toLowerCase() ?? "";
|
||||
}
|
||||
|
||||
function parseRawChatGuid(value: string): string | null {
|
||||
const trimmed = normalizeOptionalString(value);
|
||||
if (!trimmed) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
stripChannelTargetPrefix,
|
||||
type ChannelOutboundSessionRouteParams,
|
||||
} from "openclaw/plugin-sdk/channel-core";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
export function resolveFeishuOutboundSessionRoute(params: ChannelOutboundSessionRouteParams) {
|
||||
let trimmed = stripChannelTargetPrefix(params.target, "feishu", "lark");
|
||||
@@ -10,7 +11,7 @@ export function resolveFeishuOutboundSessionRoute(params: ChannelOutboundSession
|
||||
return null;
|
||||
}
|
||||
|
||||
const lower = trimmed.toLowerCase();
|
||||
const lower = normalizeLowercaseStringOrEmpty(trimmed);
|
||||
let isGroup = false;
|
||||
let typeExplicit = false;
|
||||
|
||||
@@ -25,7 +26,7 @@ export function resolveFeishuOutboundSessionRoute(params: ChannelOutboundSession
|
||||
}
|
||||
|
||||
if (!typeExplicit) {
|
||||
const idLower = trimmed.toLowerCase();
|
||||
const idLower = normalizeLowercaseStringOrEmpty(trimmed);
|
||||
if (idLower.startsWith("ou_") || idLower.startsWith("on_")) {
|
||||
isGroup = false;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import type { FeishuIdType } from "./types.js";
|
||||
|
||||
const CHAT_ID_PREFIX = "oc_";
|
||||
@@ -29,7 +30,7 @@ export function normalizeFeishuTarget(raw: string): string | null {
|
||||
}
|
||||
|
||||
const withoutProvider = stripProviderPrefix(trimmed);
|
||||
const lowered = withoutProvider.toLowerCase();
|
||||
const lowered = normalizeLowercaseStringOrEmpty(withoutProvider);
|
||||
if (lowered.startsWith("chat:")) {
|
||||
return withoutProvider.slice("chat:".length).trim() || null;
|
||||
}
|
||||
@@ -65,7 +66,7 @@ export function formatFeishuTarget(id: string, type?: FeishuIdType): string {
|
||||
|
||||
export function resolveReceiveIdType(id: string): "chat_id" | "open_id" | "user_id" {
|
||||
const trimmed = id.trim();
|
||||
const lowered = trimmed.toLowerCase();
|
||||
const lowered = normalizeLowercaseStringOrEmpty(trimmed);
|
||||
if (
|
||||
lowered.startsWith("chat:") ||
|
||||
lowered.startsWith("group:") ||
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { normalizeE164 } from "openclaw/plugin-sdk/account-resolution";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
const SERVICE_PREFIXES = ["imessage:", "sms:", "auto:"] as const;
|
||||
const CHAT_TARGET_PREFIX_RE =
|
||||
/^(chat_id:|chatid:|chat:|chat_guid:|chatguid:|guid:|chat_identifier:|chatidentifier:|chatident:)/i;
|
||||
|
||||
function trimMessagingTarget(raw: string): string | undefined {
|
||||
const trimmed = raw.trim();
|
||||
return trimmed || undefined;
|
||||
}
|
||||
|
||||
function looksLikeHandleOrPhoneTarget(params: {
|
||||
raw: string;
|
||||
prefixPattern: RegExp;
|
||||
@@ -32,7 +31,7 @@ export function normalizeIMessageHandle(raw: string): string {
|
||||
if (!trimmed) {
|
||||
return "";
|
||||
}
|
||||
const lowered = trimmed.toLowerCase();
|
||||
const lowered = normalizeLowercaseStringOrEmpty(trimmed);
|
||||
if (lowered.startsWith("imessage:")) {
|
||||
return normalizeIMessageHandle(trimmed.slice("imessage:".length));
|
||||
}
|
||||
@@ -51,7 +50,7 @@ export function normalizeIMessageHandle(raw: string): string {
|
||||
return `${prefix.toLowerCase()}${value}`;
|
||||
}
|
||||
if (trimmed.includes("@")) {
|
||||
return trimmed.toLowerCase();
|
||||
return normalizeLowercaseStringOrEmpty(trimmed);
|
||||
}
|
||||
const normalized = normalizeE164(trimmed);
|
||||
if (normalized) {
|
||||
@@ -61,12 +60,12 @@ export function normalizeIMessageHandle(raw: string): string {
|
||||
}
|
||||
|
||||
export function normalizeIMessageMessagingTarget(raw: string): string | undefined {
|
||||
const trimmed = trimMessagingTarget(raw);
|
||||
const trimmed = normalizeOptionalString(raw);
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const lower = trimmed.toLowerCase();
|
||||
const lower = normalizeLowercaseStringOrEmpty(trimmed);
|
||||
for (const prefix of SERVICE_PREFIXES) {
|
||||
if (lower.startsWith(prefix)) {
|
||||
const remainder = trimmed.slice(prefix.length).trim();
|
||||
@@ -86,7 +85,7 @@ export function normalizeIMessageMessagingTarget(raw: string): string | undefine
|
||||
}
|
||||
|
||||
export function looksLikeIMessageTargetId(raw: string): boolean {
|
||||
const trimmed = trimMessagingTarget(raw);
|
||||
const trimmed = normalizeOptionalString(raw);
|
||||
if (!trimmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
type WizardPrompter,
|
||||
} from "openclaw/plugin-sdk/setup-runtime";
|
||||
import { formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { resolveDefaultIMessageAccountId, resolveIMessageAccount } from "./accounts.js";
|
||||
import { normalizeIMessageHandle } from "./targets.js";
|
||||
|
||||
@@ -25,7 +26,7 @@ const channel = "imessage" as const;
|
||||
|
||||
export function parseIMessageAllowFromEntries(raw: string): { entries: string[]; error?: string } {
|
||||
return parseSetupEntriesAllowingWildcard(raw, (entry) => {
|
||||
const lower = entry.toLowerCase();
|
||||
const lower = normalizeLowercaseStringOrEmpty(entry);
|
||||
if (lower.startsWith("chat_id:")) {
|
||||
const id = entry.slice("chat_id:".length).trim();
|
||||
if (!/^\d+$/.test(id)) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { normalizeE164 } from "openclaw/plugin-sdk/account-resolution";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
createAllowedChatSenderMatcher,
|
||||
type ChatSenderAllowParams,
|
||||
@@ -32,7 +33,7 @@ export function normalizeIMessageHandle(raw: string): string {
|
||||
if (!trimmed) {
|
||||
return "";
|
||||
}
|
||||
const lowered = trimmed.toLowerCase();
|
||||
const lowered = normalizeLowercaseStringOrEmpty(trimmed);
|
||||
if (lowered.startsWith("imessage:")) {
|
||||
return normalizeIMessageHandle(trimmed.slice(9));
|
||||
}
|
||||
@@ -64,7 +65,7 @@ export function normalizeIMessageHandle(raw: string): string {
|
||||
}
|
||||
|
||||
if (trimmed.includes("@")) {
|
||||
return trimmed.toLowerCase();
|
||||
return normalizeLowercaseStringOrEmpty(trimmed);
|
||||
}
|
||||
const normalized = normalizeE164(trimmed);
|
||||
if (normalized) {
|
||||
@@ -78,7 +79,7 @@ export function parseIMessageTarget(raw: string): IMessageTarget {
|
||||
if (!trimmed) {
|
||||
throw new Error("iMessage target is required");
|
||||
}
|
||||
const lower = trimmed.toLowerCase();
|
||||
const lower = normalizeLowercaseStringOrEmpty(trimmed);
|
||||
|
||||
const servicePrefixed = resolveServicePrefixedChatTarget({
|
||||
trimmed,
|
||||
@@ -112,7 +113,7 @@ export function looksLikeIMessageExplicitTargetId(raw: string): boolean {
|
||||
if (!trimmed) {
|
||||
return false;
|
||||
}
|
||||
const lower = trimmed.toLowerCase();
|
||||
const lower = normalizeLowercaseStringOrEmpty(trimmed);
|
||||
if (/^(imessage:|sms:|auto:)/.test(lower)) {
|
||||
return true;
|
||||
}
|
||||
@@ -140,7 +141,7 @@ export function parseIMessageAllowTarget(raw: string): IMessageAllowTarget {
|
||||
if (!trimmed) {
|
||||
return { kind: "handle", handle: "" };
|
||||
}
|
||||
const lower = trimmed.toLowerCase();
|
||||
const lower = normalizeLowercaseStringOrEmpty(trimmed);
|
||||
|
||||
const servicePrefixed = resolveServicePrefixedOrChatAllowTarget({
|
||||
trimmed,
|
||||
|
||||
@@ -2,13 +2,14 @@ import type { ChannelOutboundAdapter } from "openclaw/plugin-sdk/channel-contrac
|
||||
import type { ChannelPlugin } from "openclaw/plugin-sdk/core";
|
||||
import { resolveOutboundSendDep } from "openclaw/plugin-sdk/outbound-runtime";
|
||||
import { collectStatusIssuesFromLastError } from "openclaw/plugin-sdk/status-helpers";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
function normalizeIMessageTestHandle(raw: string): string {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) {
|
||||
return "";
|
||||
}
|
||||
const lowered = trimmed.toLowerCase();
|
||||
const lowered = normalizeLowercaseStringOrEmpty(trimmed);
|
||||
if (lowered.startsWith("imessage:")) {
|
||||
return normalizeIMessageTestHandle(trimmed.slice("imessage:".length));
|
||||
}
|
||||
@@ -24,7 +25,7 @@ function normalizeIMessageTestHandle(raw: string): string {
|
||||
);
|
||||
}
|
||||
if (trimmed.includes("@")) {
|
||||
return trimmed.toLowerCase();
|
||||
return normalizeLowercaseStringOrEmpty(trimmed);
|
||||
}
|
||||
const digits = trimmed.replace(/[^\d+]/g, "");
|
||||
if (digits) {
|
||||
|
||||
@@ -45,6 +45,12 @@ vi.mock("openclaw/plugin-sdk/config-runtime", () => ({
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/text-runtime", () => ({
|
||||
convertMarkdownTables: vi.fn((text: string) => text),
|
||||
normalizeLowercaseStringOrEmpty: vi.fn((value: string | null | undefined) => {
|
||||
if (typeof value !== "string") {
|
||||
return "";
|
||||
}
|
||||
return value.trim().toLowerCase();
|
||||
}),
|
||||
normalizeOptionalString: vi.fn((value: string | null | undefined) => {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { isPrivateNetworkOptInEnabled } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { convertMarkdownTables } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
convertMarkdownTables,
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import { getMattermostRuntime } from "../runtime.js";
|
||||
import { resolveMattermostAccount } from "./accounts.js";
|
||||
import {
|
||||
@@ -94,7 +97,7 @@ export function parseMattermostTarget(raw: string): MattermostTarget {
|
||||
if (!trimmed) {
|
||||
throw new Error("Recipient is required for Mattermost sends");
|
||||
}
|
||||
const lower = trimmed.toLowerCase();
|
||||
const lower = normalizeLowercaseStringOrEmpty(trimmed);
|
||||
if (lower.startsWith("channel:")) {
|
||||
const id = trimmed.slice("channel:".length).trim();
|
||||
if (!id) {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
export function normalizeMattermostMessagingTarget(raw: string): string | undefined {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
const lower = trimmed.toLowerCase();
|
||||
const lower = normalizeLowercaseStringOrEmpty(trimmed);
|
||||
if (lower.startsWith("channel:")) {
|
||||
const id = trimmed.slice("channel:".length).trim();
|
||||
return id ? `channel:${id}` : undefined;
|
||||
|
||||
@@ -6,13 +6,14 @@ import {
|
||||
type ChannelOutboundSessionRouteParams,
|
||||
} from "openclaw/plugin-sdk/core";
|
||||
import { normalizeOutboundThreadId } from "openclaw/plugin-sdk/routing";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
export function resolveMattermostOutboundSessionRoute(params: ChannelOutboundSessionRouteParams) {
|
||||
let trimmed = stripChannelTargetPrefix(params.target, "mattermost");
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
const lower = trimmed.toLowerCase();
|
||||
const lower = normalizeLowercaseStringOrEmpty(trimmed);
|
||||
const resolvedKind = params.resolvedTarget?.kind;
|
||||
const isUser =
|
||||
resolvedKind === "user" ||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { resolveMergedAccountConfig } from "openclaw/plugin-sdk/account-resolution";
|
||||
import { tryReadSecretFileSync } from "openclaw/plugin-sdk/channel-core";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
createAccountListHelpers,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
@@ -11,7 +14,7 @@ import { normalizeResolvedSecretInputString } from "./secret-input.js";
|
||||
import type { CoreConfig, NextcloudTalkAccountConfig } from "./types.js";
|
||||
|
||||
function isTruthyEnvValue(value?: string): boolean {
|
||||
const normalized = normalizeOptionalString(value)?.toLowerCase() ?? "";
|
||||
const normalized = normalizeLowercaseStringOrEmpty(value);
|
||||
return normalized === "true" || normalized === "1" || normalized === "yes" || normalized === "on";
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
createComputedAccountStatusAdapter,
|
||||
createDefaultChannelRuntimeState,
|
||||
} from "openclaw/plugin-sdk/status-helpers";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
listNextcloudTalkAccountIds,
|
||||
resolveDefaultNextcloudTalkAccountId,
|
||||
@@ -197,7 +198,7 @@ export const nextcloudTalkPlugin: ChannelPlugin<ResolvedNextcloudTalkAccount> =
|
||||
message: "OpenClaw: your access has been approved.",
|
||||
normalizeAllowEntry: createPairingPrefixStripper(
|
||||
/^(nextcloud-talk|nc-talk|nc):/i,
|
||||
(entry) => entry.toLowerCase(),
|
||||
(entry) => normalizeLowercaseStringOrEmpty(entry),
|
||||
),
|
||||
notify: createLoggedPairingApprovalNotifier(
|
||||
({ id }) => `[nextcloud-talk] User ${id} approved for pairing`,
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import * as http from "node:http";
|
||||
import * as https from "node:https";
|
||||
import { safeParseJsonWithSchema, safeParseWithSchema } from "openclaw/plugin-sdk/extension-shared";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { z } from "zod";
|
||||
|
||||
const MIN_SEND_INTERVAL_MS = 500;
|
||||
@@ -221,16 +222,16 @@ export async function resolveLegacyWebhookNameToChatUserId(params: {
|
||||
log?: { warn: (...args: unknown[]) => void };
|
||||
}): Promise<number | undefined> {
|
||||
const users = await fetchChatUsers(params.incomingUrl, params.allowInsecureSsl, params.log);
|
||||
const lower = params.mutableWebhookUsername.toLowerCase();
|
||||
const lower = normalizeLowercaseStringOrEmpty(params.mutableWebhookUsername);
|
||||
|
||||
// Match by nickname first (webhook "username" field = Chat "nickname")
|
||||
const byNickname = users.find((u) => u.nickname.toLowerCase() === lower);
|
||||
const byNickname = users.find((u) => normalizeLowercaseStringOrEmpty(u.nickname) === lower);
|
||||
if (byNickname) {
|
||||
return byNickname.user_id;
|
||||
}
|
||||
|
||||
// Then by username
|
||||
const byUsername = users.find((u) => u.username.toLowerCase() === lower);
|
||||
const byUsername = users.find((u) => normalizeLowercaseStringOrEmpty(u.username) === lower);
|
||||
if (byUsername) {
|
||||
return byUsername.user_id;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user