mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 09:41:11 +00:00
refactor: dedupe line qqbot slack lowercase helpers
This commit is contained in:
@@ -13,7 +13,10 @@ import type {
|
||||
ModelDefinitionConfig,
|
||||
ModelProviderConfig,
|
||||
} from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalLowercaseString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
const log = createSubsystemLogger("bedrock-discovery");
|
||||
|
||||
@@ -69,7 +72,7 @@ function buildCacheKey(params: {
|
||||
}
|
||||
|
||||
function includesTextModalities(modalities?: Array<string>): boolean {
|
||||
return (modalities ?? []).some((entry) => entry.toLowerCase() === "text");
|
||||
return (modalities ?? []).some((entry) => normalizeOptionalLowercaseString(entry) === "text");
|
||||
}
|
||||
|
||||
function isActive(summary: BedrockModelSummary): boolean {
|
||||
@@ -81,7 +84,7 @@ function mapInputModalities(summary: BedrockModelSummary): Array<"text" | "image
|
||||
const inputs = summary.inputModalities ?? [];
|
||||
const mapped = new Set<"text" | "image">();
|
||||
for (const modality of inputs) {
|
||||
const lower = modality.toLowerCase();
|
||||
const lower = normalizeOptionalLowercaseString(modality);
|
||||
if (lower === "text") {
|
||||
mapped.add("text");
|
||||
}
|
||||
@@ -96,7 +99,9 @@ function mapInputModalities(summary: BedrockModelSummary): Array<"text" | "image
|
||||
}
|
||||
|
||||
function inferReasoningSupport(summary: BedrockModelSummary): boolean {
|
||||
const haystack = `${summary.modelId ?? ""} ${summary.modelName ?? ""}`.toLowerCase();
|
||||
const haystack = normalizeLowercaseStringOrEmpty(
|
||||
`${summary.modelId ?? ""} ${summary.modelName ?? ""}`,
|
||||
);
|
||||
return haystack.includes("reasoning") || haystack.includes("thinking");
|
||||
}
|
||||
|
||||
@@ -256,7 +261,9 @@ function resolveInferenceProfiles(
|
||||
const models = profile.models ?? [];
|
||||
const matchesFilter = models.some((m) => {
|
||||
const provider = m.modelArn?.split("/")?.[1]?.split(".")?.[0];
|
||||
return provider ? providerFilter.includes(provider.toLowerCase()) : false;
|
||||
return provider
|
||||
? providerFilter.includes(normalizeOptionalLowercaseString(provider) ?? "")
|
||||
: false;
|
||||
});
|
||||
if (!matchesFilter) {
|
||||
continue;
|
||||
@@ -265,7 +272,9 @@ function resolveInferenceProfiles(
|
||||
|
||||
// Look up the underlying foundation model to inherit its capabilities.
|
||||
const baseModelId = resolveBaseModelId(profile);
|
||||
const baseModel = baseModelId ? foundationModels.get(baseModelId.toLowerCase()) : undefined;
|
||||
const baseModel = baseModelId
|
||||
? foundationModels.get(normalizeLowercaseStringOrEmpty(baseModelId))
|
||||
: undefined;
|
||||
|
||||
discovered.push({
|
||||
id: profile.inferenceProfileId,
|
||||
@@ -356,8 +365,9 @@ export async function discoverBedrockModels(params: {
|
||||
maxTokens: defaultMaxTokens,
|
||||
});
|
||||
discovered.push(def);
|
||||
seenIds.add(def.id.toLowerCase());
|
||||
foundationModels.set(def.id.toLowerCase(), def);
|
||||
const normalizedId = normalizeLowercaseStringOrEmpty(def.id);
|
||||
seenIds.add(normalizedId);
|
||||
foundationModels.set(normalizedId, def);
|
||||
}
|
||||
|
||||
// Merge inference profiles — inherit capabilities from foundation models.
|
||||
@@ -368,9 +378,10 @@ export async function discoverBedrockModels(params: {
|
||||
foundationModels,
|
||||
);
|
||||
for (const profile of inferenceProfiles) {
|
||||
if (!seenIds.has(profile.id.toLowerCase())) {
|
||||
const normalizedId = normalizeLowercaseStringOrEmpty(profile.id);
|
||||
if (!seenIds.has(normalizedId)) {
|
||||
discovered.push(profile);
|
||||
seenIds.add(profile.id.toLowerCase());
|
||||
seenIds.add(normalizedId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
createActionCard,
|
||||
createImageCard,
|
||||
@@ -136,7 +137,7 @@ function parseCardArgs(argsStr: string): {
|
||||
// Extract type (first word)
|
||||
const typeMatch = argsStr.match(/^(\w+)/);
|
||||
if (typeMatch) {
|
||||
result.type = typeMatch[1].toLowerCase();
|
||||
result.type = normalizeLowercaseStringOrEmpty(typeMatch[1]);
|
||||
argsStr = argsStr.slice(typeMatch[0].length).trim();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
export type LineOutboundMediaKind = "image" | "video" | "audio";
|
||||
|
||||
export type LineOutboundMediaResolved = {
|
||||
@@ -31,7 +33,7 @@ export function validateLineMediaUrl(url: string): void {
|
||||
}
|
||||
|
||||
export function detectLineMediaKind(mimeType: string): LineOutboundMediaKind {
|
||||
const normalized = mimeType.toLowerCase();
|
||||
const normalized = normalizeLowercaseStringOrEmpty(mimeType);
|
||||
if (normalized.startsWith("image/")) {
|
||||
return "image";
|
||||
}
|
||||
@@ -54,7 +56,7 @@ function isHttpsUrl(url: string): boolean {
|
||||
|
||||
function detectLineMediaKindFromUrl(url: string): LineOutboundMediaKind | undefined {
|
||||
try {
|
||||
const pathname = new URL(url).pathname.toLowerCase();
|
||||
const pathname = normalizeLowercaseStringOrEmpty(new URL(url).pathname);
|
||||
if (/\.(png|jpe?g|gif|webp|bmp|heic|heif|avif)$/i.test(pathname)) {
|
||||
return "image";
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import type { ReplyPayload } from "../runtime-api.js";
|
||||
import {
|
||||
createAgendaCard,
|
||||
@@ -33,8 +34,7 @@ export function parseLineDirectives(payload: ReplyPayload): ReplyPayload {
|
||||
...(result.channelData?.line as LineChannelData | undefined),
|
||||
};
|
||||
const toSlug = (value: string): string =>
|
||||
value
|
||||
.toLowerCase()
|
||||
normalizeLowercaseStringOrEmpty(value)
|
||||
.replace(/[^a-z0-9]+/g, "_")
|
||||
.replace(/^_+|_+$/g, "") || "device";
|
||||
const lineActionData = (action: string, extras?: Record<string, string>): string => {
|
||||
@@ -85,10 +85,10 @@ export function parseLineDirectives(payload: ReplyPayload): ReplyPayload {
|
||||
const [question, yesPart, noPart] = parts;
|
||||
const [yesLabel, yesData] = yesPart.includes(":")
|
||||
? yesPart.split(":").map((s) => s.trim())
|
||||
: [yesPart, yesPart.toLowerCase()];
|
||||
: [yesPart, normalizeLowercaseStringOrEmpty(yesPart)];
|
||||
const [noLabel, noData] = noPart.includes(":")
|
||||
? noPart.split(":").map((s) => s.trim())
|
||||
: [noPart, noPart.toLowerCase()];
|
||||
: [noPart, normalizeLowercaseStringOrEmpty(noPart)];
|
||||
|
||||
lineData.templateMessage = {
|
||||
type: "confirm",
|
||||
@@ -116,7 +116,7 @@ export function parseLineDirectives(payload: ReplyPayload): ReplyPayload {
|
||||
if (index === -1) {
|
||||
return -1;
|
||||
}
|
||||
const lower = trimmed.toLowerCase();
|
||||
const lower = normalizeLowercaseStringOrEmpty(trimmed);
|
||||
if (lower.startsWith("http://") || lower.startsWith("https://")) {
|
||||
return -1;
|
||||
}
|
||||
@@ -161,7 +161,7 @@ export function parseLineDirectives(payload: ReplyPayload): ReplyPayload {
|
||||
const parts = mediaPlayerMatch[1].split("|").map((s) => s.trim());
|
||||
if (parts.length >= 1) {
|
||||
const [title, artist, source, imageUrl, statusStr] = parts;
|
||||
const isPlaying = statusStr?.toLowerCase() === "playing";
|
||||
const isPlaying = normalizeLowercaseStringOrEmpty(statusStr) === "playing";
|
||||
const validImageUrl = imageUrl?.startsWith("https://") ? imageUrl : undefined;
|
||||
const deviceKey = toSlug(source || title || "media");
|
||||
const card = createMediaPlayerCard({
|
||||
@@ -281,7 +281,7 @@ export function parseLineDirectives(payload: ReplyPayload): ReplyPayload {
|
||||
const controls = controlsStr
|
||||
? controlsStr.split(",").map((ctrlStr) => {
|
||||
const [label, data] = ctrlStr.split(":").map((s) => s.trim());
|
||||
const action = data || label.toLowerCase().replace(/\s+/g, "_");
|
||||
const action = data || normalizeLowercaseStringOrEmpty(label).replace(/\s+/g, "_");
|
||||
return { label, data: lineActionData(action, { "line.device": deviceKey }) };
|
||||
})
|
||||
: [];
|
||||
|
||||
@@ -2,6 +2,7 @@ import { readFile } from "node:fs/promises";
|
||||
import { messagingApi } from "@line/bot-sdk";
|
||||
import { loadConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { resolveLineAccount } from "./accounts.js";
|
||||
import { datetimePickerAction, messageAction, postbackAction, uriAction } from "./actions.js";
|
||||
import { resolveLineChannelAccessToken } from "./channel-access-token.js";
|
||||
@@ -104,7 +105,9 @@ export async function uploadRichMenuImage(
|
||||
const blobClient = getBlobClient(opts);
|
||||
|
||||
const imageData = await readFile(imagePath);
|
||||
const contentType = imagePath.toLowerCase().endsWith(".png") ? "image/png" : "image/jpeg";
|
||||
const contentType = normalizeLowercaseStringOrEmpty(imagePath).endsWith(".png")
|
||||
? "image/png"
|
||||
: "image/jpeg";
|
||||
|
||||
await blobClient.setRichMenuImage(richMenuId, new Blob([imageData], { type: contentType }));
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -59,8 +59,8 @@ export function resolveSlackAllowListMatch(params: {
|
||||
allowNameMatching?: boolean;
|
||||
}): SlackAllowListMatch {
|
||||
const compiledAllowList = compileAllowlist(params.allowList);
|
||||
const id = params.id?.toLowerCase();
|
||||
const name = params.name?.toLowerCase();
|
||||
const id = normalizeOptionalLowercaseString(params.id);
|
||||
const name = normalizeOptionalLowercaseString(params.name);
|
||||
const slug = normalizeSlackSlug(name);
|
||||
const candidates: Array<{ value?: string; source: SlackAllowListSource }> = [
|
||||
{ value: id, source: "id" },
|
||||
|
||||
@@ -5,7 +5,10 @@ import type { FetchLike } from "openclaw/plugin-sdk/media-runtime";
|
||||
import { fetchRemoteMedia } from "openclaw/plugin-sdk/media-runtime";
|
||||
import { saveMediaBuffer } from "openclaw/plugin-sdk/media-runtime";
|
||||
import { resolveRequestUrl } from "openclaw/plugin-sdk/request-url";
|
||||
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalLowercaseString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import type { SlackAttachment, SlackFile } from "../types.js";
|
||||
|
||||
function isSlackHostname(hostname: string): boolean {
|
||||
@@ -135,7 +138,9 @@ function resolveSlackMediaMimetype(
|
||||
}
|
||||
|
||||
function looksLikeHtmlBuffer(buffer: Buffer): boolean {
|
||||
const head = buffer.subarray(0, 512).toString("utf-8").replace(/^\s+/, "").toLowerCase();
|
||||
const head = normalizeLowercaseStringOrEmpty(
|
||||
buffer.subarray(0, 512).toString("utf-8").replace(/^\s+/, ""),
|
||||
);
|
||||
return head.startsWith("<!doctype html") || head.startsWith("<html");
|
||||
}
|
||||
|
||||
@@ -235,8 +240,8 @@ export async function resolveSlackMedia(params: {
|
||||
|
||||
// Guard against auth/login HTML pages returned instead of binary media.
|
||||
// Allow user-provided HTML files through.
|
||||
const fileMime = file.mimetype?.toLowerCase();
|
||||
const fileName = file.name?.toLowerCase() ?? "";
|
||||
const fileMime = normalizeOptionalLowercaseString(file.mimetype);
|
||||
const fileName = normalizeLowercaseStringOrEmpty(file.name);
|
||||
const isExpectedHtml =
|
||||
fileMime === "text/html" || fileName.endsWith(".html") || fileName.endsWith(".htm");
|
||||
if (!isExpectedHtml) {
|
||||
|
||||
@@ -771,7 +771,7 @@ export async function registerSlackMonitorSlashCommands(params: {
|
||||
}
|
||||
const query = normalizeLowercaseStringOrEmpty(typedBody.value);
|
||||
const options = entry.choices
|
||||
.filter((choice) => !query || choice.label.toLowerCase().includes(query))
|
||||
.filter((choice) => !query || normalizeLowercaseStringOrEmpty(choice.label).includes(query))
|
||||
.slice(0, SLACK_COMMAND_ARG_SELECT_OPTIONS_MAX)
|
||||
.map((choice) => ({
|
||||
text: { type: "plain_text", text: choice.label.slice(0, 75) },
|
||||
|
||||
Reference in New Issue
Block a user