refactor: dedupe msteams lowercase helpers

This commit is contained in:
Peter Steinberger
2026-04-07 12:54:37 +01:00
parent a33dd445b2
commit 18acfe7352
8 changed files with 41 additions and 15 deletions

View File

@@ -1,3 +1,7 @@
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalLowercaseString,
} from "openclaw/plugin-sdk/text-runtime";
import { fetchWithSsrFGuard, type SsrFPolicy } from "../../runtime-api.js";
import { getMSTeamsRuntime } from "../runtime.js";
import { ensureUserAgentHeader } from "../user-agent.js";
@@ -46,7 +50,7 @@ export function buildMSTeamsGraphMessageUrls(params: {
conversationMessageId?: string | null;
channelData?: unknown;
}): string[] {
const conversationType = params.conversationType?.trim().toLowerCase() ?? "";
const conversationType = normalizeLowercaseStringOrEmpty(params.conversationType ?? "");
const messageIdCandidates = new Set<string>();
const pushCandidate = (value: string | null | undefined) => {
const trimmed = typeof value === "string" ? value.trim() : "";
@@ -382,7 +386,7 @@ export async function downloadMSTeamsGraphMedia(params: {
const filteredAttachments =
sharePointMedia.length > 0
? normalizedAttachments.filter((att) => {
const contentType = att.contentType?.toLowerCase();
const contentType = normalizeOptionalLowercaseString(att.contentType);
if (contentType !== "reference") {
return true;
}

View File

@@ -7,7 +7,11 @@ import {
normalizeHostnameSuffixAllowlist,
type SsrFPolicy,
} from "openclaw/plugin-sdk/ssrf-policy";
import { isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import {
isRecord,
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "openclaw/plugin-sdk/text-runtime";
import type { MSTeamsAttachmentLike } from "./types.js";
type InlineImageCandidate =
@@ -119,9 +123,9 @@ export function inferPlaceholder(params: {
fileName?: string;
fileType?: string;
}): string {
const mime = params.contentType?.toLowerCase() ?? "";
const name = params.fileName?.toLowerCase() ?? "";
const fileType = params.fileType?.toLowerCase() ?? "";
const mime = normalizeLowercaseStringOrEmpty(params.contentType ?? "");
const name = normalizeLowercaseStringOrEmpty(params.fileName ?? "");
const fileType = normalizeLowercaseStringOrEmpty(params.fileType ?? "");
const looksLikeImage =
mime.startsWith("image/") || IMAGE_EXT_RE.test(name) || IMAGE_EXT_RE.test(`x.${fileType}`);
@@ -232,7 +236,7 @@ function decodeDataImageWithLimits(
if (!match) {
return { candidate: null, estimatedBytes: 0 };
}
const contentType = match[1]?.toLowerCase();
const contentType = normalizeLowercaseStringOrEmpty(match[1] ?? "");
const isBase64 = Boolean(match[2]);
if (!isBase64) {
return { candidate: null, estimatedBytes: 0 };

View File

@@ -10,6 +10,7 @@
* 6. Optionally sends a proactive follow-up to the user
*/
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
import {
dispatchReplyFromConfigWithSettledDispatcher,
type OpenClawConfig,
@@ -241,7 +242,9 @@ export async function runFeedbackReflection(params: RunFeedbackReflectionParams)
log.debug?.("failed to store reflection learning", { error: formatUnknownError(err) });
}
const conversationType = params.conversationRef.conversation?.conversationType?.toLowerCase();
const conversationType = normalizeOptionalLowercaseString(
params.conversationRef.conversation?.conversationType,
);
const shouldNotify =
conversationType === "personal" &&
parsedReflection.followUp &&

View File

@@ -9,6 +9,7 @@
* and messenger.ts (reply path) to avoid duplication.
*/
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
import { buildFileConsentCard } from "./file-consent.js";
import { storePendingUpload } from "./pending-uploads.js";
@@ -66,7 +67,7 @@ export function requiresFileConsent(params: {
bufferSize: number;
thresholdBytes: number;
}): boolean {
const isPersonal = params.conversationType?.toLowerCase() === "personal";
const isPersonal = normalizeOptionalLowercaseString(params.conversationType) === "personal";
const isImage = params.contentType?.startsWith("image/") ?? false;
const isLargeFile = params.bufferSize >= params.thresholdBytes;
return isPersonal && (isLargeFile || !isImage);

View File

@@ -1,3 +1,4 @@
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
import {
createChannelReplyPipeline,
logTypingFailure,
@@ -47,7 +48,9 @@ export function createMSTeamsReplyDispatcher(params: {
}) {
const core = getMSTeamsRuntime();
const msteamsCfg = params.cfg.channels?.msteams;
const conversationType = params.conversationRef.conversation?.conversationType?.toLowerCase();
const conversationType = normalizeOptionalLowercaseString(
params.conversationRef.conversation?.conversationType,
);
const isTypingSupported = conversationType === "personal" || conversationType === "groupchat";
const sendTypingIndicator = isTypingSupported

View File

@@ -1,3 +1,4 @@
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
import type { ReplyPayload } from "../runtime-api.js";
import { formatUnknownError } from "./errors.js";
import type { MSTeamsMonitorLogger } from "./monitor-types.js";
@@ -23,7 +24,7 @@ export function createTeamsReplyStreamController(params: {
log: MSTeamsMonitorLogger;
random?: () => number;
}) {
const isPersonal = params.conversationType?.toLowerCase() === "personal";
const isPersonal = normalizeOptionalLowercaseString(params.conversationType) === "personal";
const stream = isPersonal
? new TeamsHttpStream({
sendActivity: (activity) => params.context.sendActivity(activity),

View File

@@ -1,4 +1,8 @@
import { mapAllowlistResolutionInputs } from "openclaw/plugin-sdk/allow-from";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalLowercaseString,
} from "openclaw/plugin-sdk/text-runtime";
import { searchGraphUsers } from "./graph-users.js";
import {
listChannelsForTeam,
@@ -135,7 +139,9 @@ export async function resolveMSTeamsChannelAllowlist(params: {
} catch {
// API failure (rate limit, network error) — fall back to Graph GUID as team key
}
const generalChannel = teamChannels.find((ch) => ch.displayName?.toLowerCase() === "general");
const generalChannel = teamChannels.find(
(ch) => normalizeOptionalLowercaseString(ch.displayName) === "general",
);
// Use the General channel's conversation ID as the team key — this
// matches what Bot Framework sends at runtime. Fall back to the Graph
// GUID if the General channel isn't found (renamed or deleted).
@@ -150,11 +156,14 @@ export async function resolveMSTeamsChannelAllowlist(params: {
};
}
// Reuse teamChannels — already fetched above
const normalizedChannel = normalizeOptionalLowercaseString(channel);
const channelMatch =
teamChannels.find((item) => item.id === channel) ??
teamChannels.find((item) => item.displayName?.toLowerCase() === channel.toLowerCase()) ??
teamChannels.find(
(item) => normalizeOptionalLowercaseString(item.displayName) === normalizedChannel,
) ??
teamChannels.find((item) =>
item.displayName?.toLowerCase().includes(channel.toLowerCase() ?? ""),
normalizeLowercaseStringOrEmpty(item.displayName ?? "").includes(normalizedChannel ?? ""),
);
if (!channelMatch?.id) {
return { input, resolved: false, note: "channel not found" };

View File

@@ -4,6 +4,7 @@ import {
stripTargetKindPrefix,
type ChannelOutboundSessionRouteParams,
} from "openclaw/plugin-sdk/core";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
export function resolveMSTeamsOutboundSessionRoute(params: ChannelOutboundSessionRouteParams) {
let trimmed = stripChannelTargetPrefix(params.target, "msteams", "teams");
@@ -11,7 +12,7 @@ export function resolveMSTeamsOutboundSessionRoute(params: ChannelOutboundSessio
return null;
}
const lower = trimmed.toLowerCase();
const lower = normalizeLowercaseStringOrEmpty(trimmed);
const isUser = lower.startsWith("user:");
const rawId = stripTargetKindPrefix(trimmed);
if (!rawId) {