Skip to content

Commit 88b394b

Browse files
committed
refactor: dedupe feishu and bluebubbles lowercase helpers
1 parent ae4f8da commit 88b394b

12 files changed

Lines changed: 54 additions & 39 deletions

File tree

extensions/bluebubbles/src/attachments.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import crypto from "node:crypto";
22
import path from "node:path";
33
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
44
import {
5+
normalizeLowercaseStringOrEmpty,
56
normalizeOptionalLowercaseString,
67
normalizeOptionalString,
78
} from "openclaw/plugin-sdk/text-runtime";
@@ -49,7 +50,7 @@ function sanitizeFilename(input: string | undefined, fallback: string): string {
4950

5051
function ensureExtension(filename: string, extension: string, fallbackBase: string): string {
5152
const currentExt = path.extname(filename);
52-
if (currentExt.toLowerCase() === extension) {
53+
if (normalizeLowercaseStringOrEmpty(currentExt) === extension) {
5354
return filename;
5455
}
5556
const base = currentExt ? filename.slice(0, -currentExt.length) : filename;
@@ -58,7 +59,7 @@ function ensureExtension(filename: string, extension: string, fallbackBase: stri
5859

5960
function resolveVoiceInfo(filename: string, contentType?: string) {
6061
const normalizedType = normalizeOptionalLowercaseString(contentType);
61-
const extension = path.extname(filename).toLowerCase();
62+
const extension = normalizeLowercaseStringOrEmpty(path.extname(filename));
6263
const isMp3 =
6364
extension === ".mp3" || (normalizedType ? AUDIO_MIME_MP3.has(normalizedType) : false);
6465
const isCaf =

extensions/bluebubbles/src/monitor-normalize.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { parseFiniteNumber } from "openclaw/plugin-sdk/infra-runtime";
22
import {
33
asNullableRecord,
4+
normalizeLowercaseStringOrEmpty,
45
normalizeOptionalString,
56
readStringField,
67
} from "openclaw/plugin-sdk/text-runtime";
@@ -356,7 +357,7 @@ export function normalizeParticipantList(raw: unknown): BlueBubblesParticipant[]
356357
if (!normalized?.id) {
357358
continue;
358359
}
359-
const key = normalized.id.toLowerCase();
360+
const key = normalizeLowercaseStringOrEmpty(normalized.id);
360361
if (seen.has(key)) {
361362
continue;
362363
}
@@ -376,7 +377,7 @@ export function formatGroupMembers(params: {
376377
if (!entry?.id) {
377378
continue;
378379
}
379-
const key = entry.id.toLowerCase();
380+
const key = normalizeLowercaseStringOrEmpty(entry.id);
380381
if (seen.has(key)) {
381382
continue;
382383
}

extensions/bluebubbles/src/reactions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
12
import { resolveBlueBubblesServerAccount } from "./account-resolve.js";
23
import { getCachedBlueBubblesPrivateApiStatus } from "./probe.js";
34
import type { OpenClawConfig } from "./runtime-api.js";
@@ -120,7 +121,7 @@ export function normalizeBlueBubblesReactionInput(emoji: string, remove?: boolea
120121
if (!trimmed) {
121122
throw new Error("BlueBubbles reaction requires an emoji or name.");
122123
}
123-
let raw = trimmed.toLowerCase();
124+
let raw = normalizeLowercaseStringOrEmpty(trimmed);
124125
if (raw.startsWith("-")) {
125126
raw = raw.slice(1);
126127
}

extensions/bluebubbles/src/send.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import crypto from "node:crypto";
22
import {
3+
normalizeLowercaseStringOrEmpty,
34
normalizeOptionalLowercaseString,
45
normalizeOptionalString,
56
stripMarkdown,
@@ -364,7 +365,7 @@ export async function createChatForHandle(params: {
364365
if (
365366
res.status === 400 ||
366367
res.status === 403 ||
367-
errorText.toLowerCase().includes("private api")
368+
normalizeLowercaseStringOrEmpty(errorText).includes("private api")
368369
) {
369370
throw new Error(
370371
`BlueBubbles send failed: Cannot create new chat - Private API must be enabled. Original error: ${errorText || res.status}`,

extensions/bluebubbles/src/targets.ts

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,10 @@ import {
66
resolveServicePrefixedAllowTarget,
77
resolveServicePrefixedTarget,
88
} from "openclaw/plugin-sdk/channel-targets";
9-
10-
function normalizeOptionalString(value: unknown): string | undefined {
11-
if (typeof value !== "string") {
12-
return undefined;
13-
}
14-
const trimmed = value.trim();
15-
return trimmed || undefined;
16-
}
9+
import {
10+
normalizeLowercaseStringOrEmpty,
11+
normalizeOptionalString,
12+
} from "openclaw/plugin-sdk/text-runtime";
1713

1814
export type BlueBubblesService = "imessage" | "sms" | "auto";
1915

@@ -66,7 +62,7 @@ function stripBlueBubblesPrefix(value: string): string {
6662
if (!trimmed) {
6763
return "";
6864
}
69-
if (!trimmed.toLowerCase().startsWith("bluebubbles:")) {
65+
if (!normalizeLowercaseStringOrEmpty(trimmed).startsWith("bluebubbles:")) {
7066
return trimmed;
7167
}
7268
return trimmed.slice("bluebubbles:".length).trim();
@@ -122,7 +118,7 @@ export function normalizeBlueBubblesHandle(raw: string): string {
122118
if (!trimmed) {
123119
return "";
124120
}
125-
const lowered = trimmed.toLowerCase();
121+
const lowered = normalizeLowercaseStringOrEmpty(trimmed);
126122
if (lowered.startsWith("imessage:")) {
127123
return normalizeBlueBubblesHandle(trimmed.slice(9));
128124
}
@@ -133,7 +129,7 @@ export function normalizeBlueBubblesHandle(raw: string): string {
133129
return normalizeBlueBubblesHandle(trimmed.slice(5));
134130
}
135131
if (trimmed.includes("@")) {
136-
return trimmed.toLowerCase();
132+
return normalizeLowercaseStringOrEmpty(trimmed);
137133
}
138134
return trimmed.replace(/\s+/g, "");
139135
}
@@ -204,7 +200,7 @@ export function looksLikeBlueBubblesTargetId(raw: string, normalized?: string):
204200
if (parseRawChatGuid(candidate)) {
205201
return true;
206202
}
207-
const lowered = candidate.toLowerCase();
203+
const lowered = normalizeLowercaseStringOrEmpty(candidate);
208204
if (/^(imessage|sms|auto):/.test(lowered)) {
209205
return true;
210206
}
@@ -234,7 +230,7 @@ export function looksLikeBlueBubblesTargetId(raw: string, normalized?: string):
234230
if (!normalizedTrimmed) {
235231
return false;
236232
}
237-
const normalizedLower = normalizedTrimmed.toLowerCase();
233+
const normalizedLower = normalizeLowercaseStringOrEmpty(normalizedTrimmed);
238234
if (
239235
/^(imessage|sms|auto):/.test(normalizedLower) ||
240236
/^(chat_id|chat_guid|chat_identifier):/.test(normalizedLower)
@@ -254,7 +250,7 @@ export function looksLikeBlueBubblesExplicitTargetId(raw: string, normalized?: s
254250
if (!candidate) {
255251
return false;
256252
}
257-
const lowered = candidate.toLowerCase();
253+
const lowered = normalizeLowercaseStringOrEmpty(candidate);
258254
if (/^(imessage|sms|auto):/.test(lowered)) {
259255
return true;
260256
}
@@ -273,7 +269,7 @@ export function looksLikeBlueBubblesExplicitTargetId(raw: string, normalized?: s
273269
if (!normalizedTrimmed) {
274270
return false;
275271
}
276-
const normalizedLower = normalizedTrimmed.toLowerCase();
272+
const normalizedLower = normalizeLowercaseStringOrEmpty(normalizedTrimmed);
277273
if (
278274
/^(imessage|sms|auto):/.test(normalizedLower) ||
279275
/^(chat_id|chat_guid|chat_identifier):/.test(normalizedLower)
@@ -307,7 +303,7 @@ export function parseBlueBubblesTarget(raw: string): BlueBubblesTarget {
307303
if (!trimmed) {
308304
throw new Error("BlueBubbles target is required");
309305
}
310-
const lower = trimmed.toLowerCase();
306+
const lower = normalizeLowercaseStringOrEmpty(trimmed);
311307

312308
const servicePrefixed = resolveServicePrefixedTarget({
313309
trimmed,
@@ -358,7 +354,7 @@ export function parseBlueBubblesAllowTarget(raw: string): BlueBubblesAllowTarget
358354
if (!trimmed) {
359355
return { kind: "handle", handle: "" };
360356
}
361-
const lower = trimmed.toLowerCase();
357+
const lower = normalizeLowercaseStringOrEmpty(trimmed);
362358

363359
const servicePrefixed = resolveServicePrefixedAllowTarget({
364360
trimmed,

extensions/feishu/src/conversation-id.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
2+
13
export type FeishuGroupSessionScope =
24
| "group"
35
| "group_sender"
@@ -50,7 +52,7 @@ export function parseFeishuTargetId(raw: unknown): string | undefined {
5052
if (!withoutProvider) {
5153
return undefined;
5254
}
53-
const lowered = withoutProvider.toLowerCase();
55+
const lowered = normalizeLowercaseStringOrEmpty(withoutProvider);
5456
for (const prefix of ["chat:", "group:", "channel:", "user:", "dm:", "open_id:"]) {
5557
if (lowered.startsWith(prefix)) {
5658
return normalizeText(withoutProvider.slice(prefix.length));
@@ -68,7 +70,7 @@ export function parseFeishuDirectConversationId(raw: unknown): string | undefine
6870
if (!withoutProvider) {
6971
return undefined;
7072
}
71-
const lowered = withoutProvider.toLowerCase();
73+
const lowered = normalizeLowercaseStringOrEmpty(withoutProvider);
7274
for (const prefix of ["user:", "dm:", "open_id:"]) {
7375
if (lowered.startsWith(prefix)) {
7476
return normalizeText(withoutProvider.slice(prefix.length));
@@ -176,21 +178,21 @@ export function buildFeishuModelOverrideParentCandidates(
176178
}
177179
const topicSenderMatch = rawId.match(/^(.+):topic:([^:]+):sender:([^:]+)$/i);
178180
if (topicSenderMatch) {
179-
const chatId = topicSenderMatch[1]?.trim().toLowerCase();
180-
const topicId = topicSenderMatch[2]?.trim().toLowerCase();
181+
const chatId = normalizeLowercaseStringOrEmpty(topicSenderMatch[1]);
182+
const topicId = normalizeLowercaseStringOrEmpty(topicSenderMatch[2]);
181183
if (chatId && topicId) {
182184
return [`${chatId}:topic:${topicId}`, chatId];
183185
}
184186
return [];
185187
}
186188
const topicMatch = rawId.match(/^(.+):topic:([^:]+)$/i);
187189
if (topicMatch) {
188-
const chatId = topicMatch[1]?.trim().toLowerCase();
190+
const chatId = normalizeLowercaseStringOrEmpty(topicMatch[1]);
189191
return chatId ? [chatId] : [];
190192
}
191193
const senderMatch = rawId.match(/^(.+):sender:([^:]+)$/i);
192194
if (senderMatch) {
193-
const chatId = senderMatch[1]?.trim().toLowerCase();
195+
const chatId = normalizeLowercaseStringOrEmpty(senderMatch[1]);
194196
return chatId ? [chatId] : [];
195197
}
196198
return [];

extensions/feishu/src/directory.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ export async function listFeishuDirectoryPeersLive(params: {
4242
for (const user of response.data?.items ?? []) {
4343
if (user.open_id) {
4444
const name = user.name || "";
45-
if (!q || user.open_id.toLowerCase().includes(q) || name.toLowerCase().includes(q)) {
45+
if (
46+
!q ||
47+
normalizeLowercaseStringOrEmpty(user.open_id).includes(q) ||
48+
normalizeLowercaseStringOrEmpty(name).includes(q)
49+
) {
4650
peers.push({
4751
kind: "user",
4852
id: user.open_id,
@@ -95,7 +99,11 @@ export async function listFeishuDirectoryGroupsLive(params: {
9599
for (const chat of response.data?.items ?? []) {
96100
if (chat.chat_id) {
97101
const name = chat.name || "";
98-
if (!q || chat.chat_id.toLowerCase().includes(q) || name.toLowerCase().includes(q)) {
102+
if (
103+
!q ||
104+
normalizeLowercaseStringOrEmpty(chat.chat_id).includes(q) ||
105+
normalizeLowercaseStringOrEmpty(name).includes(q)
106+
) {
99107
groups.push({
100108
kind: "group",
101109
id: chat.chat_id,

extensions/feishu/src/media.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Readable } from "stream";
44
import type * as Lark from "@larksuiteoapi/node-sdk";
55
import { mediaKindFromMime } from "openclaw/plugin-sdk/media-runtime";
66
import { withTempDownloadPath } from "openclaw/plugin-sdk/temp-path";
7+
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
78
import type { ClawdbotConfig } from "../runtime-api.js";
89
import { resolveFeishuRuntimeAccount } from "./accounts.js";
910
import { createFeishuClient } from "./client.js";
@@ -106,9 +107,8 @@ function readHeaderValue(
106107
if (!headers) {
107108
return undefined;
108109
}
109-
const target = name.toLowerCase();
110110
for (const [key, value] of Object.entries(headers)) {
111-
if (key.toLowerCase() !== target) {
111+
if (normalizeLowercaseStringOrEmpty(key) !== normalizeLowercaseStringOrEmpty(name)) {
112112
continue;
113113
}
114114
if (typeof value === "string" && value.trim()) {
@@ -496,7 +496,7 @@ export async function sendFileFeishu(params: {
496496
export function detectFileType(
497497
fileName: string,
498498
): "opus" | "mp4" | "pdf" | "doc" | "xls" | "ppt" | "stream" {
499-
const ext = path.extname(fileName).toLowerCase();
499+
const ext = normalizeLowercaseStringOrEmpty(path.extname(fileName));
500500
switch (ext) {
501501
case ".opus":
502502
case ".ogg":
@@ -526,7 +526,7 @@ function resolveFeishuOutboundMediaKind(params: { fileName: string; contentType?
526526
msgType: "image" | "file" | "audio" | "media";
527527
} {
528528
const { fileName, contentType } = params;
529-
const ext = path.extname(fileName).toLowerCase();
529+
const ext = normalizeLowercaseStringOrEmpty(path.extname(fileName));
530530
const mimeKind = mediaKindFromMime(contentType);
531531

532532
const isImageExt = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".ico", ".tiff"].includes(

extensions/feishu/src/outbound.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import fs from "fs";
22
import path from "path";
33
import { createAttachedChannelResultAdapter } from "openclaw/plugin-sdk/channel-send-result";
4+
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
45
import { resolveFeishuAccount } from "./accounts.js";
56
import { createFeishuClient } from "./client.js";
67
import { parseFeishuCommentTarget } from "./comment-target.js";
@@ -27,7 +28,7 @@ function normalizePossibleLocalImagePath(text: string | undefined): string | nul
2728
return null;
2829
}
2930

30-
const ext = path.extname(raw).toLowerCase();
31+
const ext = normalizeLowercaseStringOrEmpty(path.extname(raw));
3132
const isImageExt = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".ico", ".tiff"].includes(
3233
ext,
3334
);

extensions/feishu/src/policy.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ export function resolveFeishuGroupConfig(params: {
7171
}
7272

7373
const lowered = normalizeOptionalLowercaseString(groupId) ?? "";
74-
const matchKey = Object.keys(groups).find((key) => key.toLowerCase() === lowered);
74+
const matchKey = Object.keys(groups).find(
75+
(key) => normalizeOptionalLowercaseString(key) === lowered,
76+
);
7577
if (matchKey) {
7678
return groups[matchKey];
7779
}

0 commit comments

Comments
 (0)