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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,4 @@
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
import { import {
createChannelReplyPipeline, createChannelReplyPipeline,
logTypingFailure, logTypingFailure,
@@ -47,7 +48,9 @@ export function createMSTeamsReplyDispatcher(params: {
}) { }) {
const core = getMSTeamsRuntime(); const core = getMSTeamsRuntime();
const msteamsCfg = params.cfg.channels?.msteams; 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 isTypingSupported = conversationType === "personal" || conversationType === "groupchat";
const sendTypingIndicator = isTypingSupported 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 type { ReplyPayload } from "../runtime-api.js";
import { formatUnknownError } from "./errors.js"; import { formatUnknownError } from "./errors.js";
import type { MSTeamsMonitorLogger } from "./monitor-types.js"; import type { MSTeamsMonitorLogger } from "./monitor-types.js";
@@ -23,7 +24,7 @@ export function createTeamsReplyStreamController(params: {
log: MSTeamsMonitorLogger; log: MSTeamsMonitorLogger;
random?: () => number; random?: () => number;
}) { }) {
const isPersonal = params.conversationType?.toLowerCase() === "personal"; const isPersonal = normalizeOptionalLowercaseString(params.conversationType) === "personal";
const stream = isPersonal const stream = isPersonal
? new TeamsHttpStream({ ? new TeamsHttpStream({
sendActivity: (activity) => params.context.sendActivity(activity), sendActivity: (activity) => params.context.sendActivity(activity),

View File

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

View File

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