refactor: dedupe line qqbot slack lowercase helpers

This commit is contained in:
Peter Steinberger
2026-04-07 20:33:42 +01:00
parent 5b090561fb
commit ba68537d9d
16 changed files with 79 additions and 47 deletions

View File

@@ -1,5 +1,6 @@
import path from "node:path";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import WebSocket from "ws";
import {
clearTokenCache,
@@ -242,10 +243,11 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
return;
}
const contentLower = content.toLowerCase();
const contentLower = normalizeLowercaseStringOrEmpty(content);
const isUrgentCommand = URGENT_COMMANDS.some(
(cmd) =>
contentLower === cmd.toLowerCase() || contentLower.startsWith(cmd.toLowerCase() + " "),
contentLower === normalizeLowercaseStringOrEmpty(cmd) ||
contentLower.startsWith(normalizeLowercaseStringOrEmpty(cmd) + " "),
);
if (isUrgentCommand) {
log?.info(

View File

@@ -6,6 +6,7 @@
* 2. `sendPlainReply` handles plain replies, including markdown images and mixed text/media.
*/
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import {
sendC2CMessage,
sendDmMessage,
@@ -151,7 +152,7 @@ export async function parseAndSendMediaTags(
const tagCounts = mediaTagMatches.reduce(
(acc, m) => {
const t = m[1].toLowerCase();
const t = normalizeLowercaseStringOrEmpty(m[1]);
acc[t] = (acc[t] ?? 0) + 1;
return acc;
},
@@ -184,7 +185,7 @@ export async function parseAndSendMediaTags(
sendQueue.push({ type: "text", content: filterInternalMarkers(textBefore) });
}
const tagName = match[1].toLowerCase();
const tagName = normalizeLowercaseStringOrEmpty(match[1]);
let mediaPath = decodeMediaPath(match[2]?.trim() ?? "", log, prefix);
if (mediaPath) {

View File

@@ -1,5 +1,6 @@
import * as path from "path";
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import {
getAccessToken,
sendC2CFileMessage,
@@ -303,7 +304,7 @@ export async function sendPhoto(
return { channel: "qqbot", error: sizeCheck.error! };
}
const fileBuffer = await readFileAsync(mediaPath);
const ext = path.extname(mediaPath).toLowerCase();
const ext = normalizeLowercaseStringOrEmpty(path.extname(mediaPath));
const mimeTypes: Record<string, string> = {
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
@@ -482,7 +483,7 @@ async function sendVoiceFromLocal(
const needsTranscode = shouldTranscodeVoice(mediaPath);
if (needsTranscode && !transcodeEnabled) {
const ext = path.extname(mediaPath).toLowerCase();
const ext = normalizeLowercaseStringOrEmpty(path.extname(mediaPath));
debugLog(
`${prefix} sendVoice: transcode disabled, format ${ext} needs transcode, returning error for fallback`,
);
@@ -886,7 +887,7 @@ export async function sendText(ctx: OutboundContext): Promise<OutboundResult> {
sendQueue.push({ type: "text", content: textBefore });
}
const tagName = match[1].toLowerCase();
const tagName = normalizeLowercaseStringOrEmpty(match[1]);
let mediaPath = match[2]?.trim() ?? "";
if (mediaPath.startsWith("MEDIA:")) {
@@ -1368,7 +1369,7 @@ async function sendTextAfterMedia(ctx: MediaTargetContext, text: string): Promis
/** Extract a lowercase extension from a path or URL, ignoring query and hash segments. */
function getCleanExt(filePath: string): string {
const cleanPath = filePath.split("?")[0].split("#")[0];
return path.extname(cleanPath).toLowerCase();
return normalizeLowercaseStringOrEmpty(path.extname(cleanPath));
}
/** Check whether a file is an image using MIME first and extension as fallback. */

View File

@@ -11,6 +11,7 @@ import fs from "node:fs";
import { createRequire } from "node:module";
import path from "node:path";
import { resolveRuntimeServiceVersion } from "openclaw/plugin-sdk/cli-runtime";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import type { QQBotAccountConfig } from "./types.js";
import { debugLog } from "./utils/debug-log.js";
import { getHomeDir, getQQBotDataDir, isWindows } from "./utils/platform.js";
@@ -134,9 +135,9 @@ const frameworkCommands: Map<string, SlashCommand> = new Map();
function registerCommand(cmd: SlashCommand): void {
if (cmd.requireAuth) {
frameworkCommands.set(cmd.name.toLowerCase(), cmd);
frameworkCommands.set(normalizeLowercaseStringOrEmpty(cmd.name), cmd);
} else {
commands.set(cmd.name.toLowerCase(), cmd);
commands.set(normalizeLowercaseStringOrEmpty(cmd.name), cmd);
}
}
@@ -604,7 +605,9 @@ export async function matchSlashCommand(ctx: SlashCommandContext): Promise<Slash
// Parse the command name and trailing arguments.
const spaceIdx = content.indexOf(" ");
const cmdName = (spaceIdx === -1 ? content.slice(1) : content.slice(1, spaceIdx)).toLowerCase();
const cmdName = normalizeLowercaseStringOrEmpty(
spaceIdx === -1 ? content.slice(1) : content.slice(1, spaceIdx),
);
const args = spaceIdx === -1 ? "" : content.slice(spaceIdx + 1).trim();
const cmd = commands.get(cmdName);

View File

@@ -117,7 +117,7 @@ export function isVoiceAttachment(att: { content_type?: string; filename?: strin
if (att.content_type === "voice" || att.content_type?.startsWith("audio/")) {
return true;
}
const ext = att.filename ? path.extname(att.filename).toLowerCase() : "";
const ext = att.filename ? normalizeLowercaseStringOrEmpty(path.extname(att.filename)) : "";
return [".amr", ".silk", ".slk", ".slac"].includes(ext);
}
@@ -139,7 +139,7 @@ export function isAudioFile(filePath: string, mimeType?: string): boolean {
return true;
}
}
const ext = path.extname(filePath).toLowerCase();
const ext = normalizeLowercaseStringOrEmpty(path.extname(filePath));
return [
".silk",
".slk",
@@ -175,10 +175,10 @@ const QQ_NATIVE_VOICE_EXTS = new Set([".silk", ".slk", ".amr", ".wav", ".mp3"]);
*/
export function shouldTranscodeVoice(filePath: string, mimeType?: string): boolean {
// Prefer MIME when it is available.
if (mimeType && QQ_NATIVE_VOICE_MIMES.has(mimeType.toLowerCase())) {
if (mimeType && QQ_NATIVE_VOICE_MIMES.has(normalizeLowercaseStringOrEmpty(mimeType))) {
return false;
}
const ext = path.extname(filePath).toLowerCase();
const ext = normalizeLowercaseStringOrEmpty(path.extname(filePath));
if (QQ_NATIVE_VOICE_EXTS.has(ext)) {
return false;
}
@@ -510,7 +510,7 @@ export async function audioFileToSilkBase64(
return null;
}
const ext = path.extname(filePath).toLowerCase();
const ext = normalizeLowercaseStringOrEmpty(path.extname(filePath));
const uploadFormats = directUploadFormats
? normalizeFormats(directUploadFormats)

View File

@@ -4,6 +4,7 @@ import * as path from "node:path";
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import { fetchRemoteMedia } from "openclaw/plugin-sdk/media-runtime";
import type { SsrFPolicy } from "openclaw/plugin-sdk/ssrf-runtime";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
/** Maximum file size accepted by the QQ Bot API. */
export const MAX_UPLOAD_SIZE = 20 * 1024 * 1024;
@@ -92,7 +93,7 @@ export function formatFileSize(bytes: number): string {
/** Infer a MIME type from the file extension. */
export function getMimeType(filePath: string): string {
const ext = path.extname(filePath).toLowerCase();
const ext = normalizeLowercaseStringOrEmpty(path.extname(filePath));
const mimeTypes: Record<string, string> = {
".jpg": "image/jpeg",
".jpeg": "image/jpeg",

View File

@@ -1,3 +1,4 @@
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { expandTilde } from "./platform.js";
// Canonical media tags. `qqmedia` is the generic auto-routing tag.
@@ -84,7 +85,7 @@ const FUZZY_MEDIA_TAG_REGEX = new RegExp(
/** Normalize a raw tag name into the canonical tag set. */
function resolveTagName(raw: string): (typeof VALID_TAGS)[number] {
const lower = raw.toLowerCase();
const lower = normalizeLowercaseStringOrEmpty(raw);
if ((VALID_TAGS as readonly string[]).includes(lower)) {
return lower as (typeof VALID_TAGS)[number];
}

View File

@@ -1,3 +1,4 @@
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import type { RefAttachmentSummary } from "../ref-index-store.js";
/** Replace QQ face tags with readable text labels. */
@@ -62,7 +63,7 @@ export function buildAttachmentSummaries(
return undefined;
}
return attachments.map((att, idx) => {
const ct = att.content_type?.toLowerCase() ?? "";
const ct = normalizeLowercaseStringOrEmpty(att.content_type);
let type: RefAttachmentSummary["type"] = "unknown";
if (ct.startsWith("image/")) {
type = "image";