refactor(channels): share allowlist + resolver helpers
This commit is contained in:
@@ -9,6 +9,10 @@ import { msteamsOnboardingAdapter } from "./onboarding.js";
|
||||
import { msteamsOutbound } from "./outbound.js";
|
||||
import { probeMSTeams } from "./probe.js";
|
||||
import {
|
||||
normalizeMSTeamsMessagingTarget,
|
||||
normalizeMSTeamsUserInput,
|
||||
parseMSTeamsConversationId,
|
||||
parseMSTeamsTeamChannelInput,
|
||||
resolveMSTeamsChannelAllowlist,
|
||||
resolveMSTeamsUserAllowlist,
|
||||
} from "./resolve-allowlist.js";
|
||||
@@ -36,21 +40,6 @@ const meta = {
|
||||
order: 60,
|
||||
} as const;
|
||||
|
||||
function normalizeMSTeamsMessagingTarget(raw: string): string | undefined {
|
||||
let trimmed = raw.trim();
|
||||
if (!trimmed) return undefined;
|
||||
if (/^(msteams|teams):/i.test(trimmed)) {
|
||||
trimmed = trimmed.replace(/^(msteams|teams):/i, "");
|
||||
}
|
||||
if (/^conversation:/i.test(trimmed)) {
|
||||
return `conversation:${trimmed.slice("conversation:".length).trim()}`;
|
||||
}
|
||||
if (/^user:/i.test(trimmed)) {
|
||||
return `user:${trimmed.slice("user:".length).trim()}`;
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount> = {
|
||||
id: "msteams",
|
||||
meta: {
|
||||
@@ -214,10 +203,7 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount> = {
|
||||
}));
|
||||
|
||||
const stripPrefix = (value: string) =>
|
||||
value
|
||||
.replace(/^(msteams|teams):/i, "")
|
||||
.replace(/^(user|conversation):/i, "")
|
||||
.trim();
|
||||
normalizeMSTeamsUserInput(value);
|
||||
|
||||
if (kind === "user") {
|
||||
const pending: Array<{ input: string; query: string; index: number }> = [];
|
||||
@@ -269,25 +255,20 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount> = {
|
||||
entry.note = "empty input";
|
||||
return;
|
||||
}
|
||||
if (/^conversation:/i.test(trimmed)) {
|
||||
const id = trimmed.replace(/^conversation:/i, "").trim();
|
||||
if (id) {
|
||||
entry.resolved = true;
|
||||
entry.id = id;
|
||||
entry.note = "conversation id";
|
||||
} else {
|
||||
entry.note = "empty conversation id";
|
||||
}
|
||||
const conversationId = parseMSTeamsConversationId(trimmed);
|
||||
if (conversationId !== null) {
|
||||
entry.resolved = Boolean(conversationId);
|
||||
entry.id = conversationId || undefined;
|
||||
entry.note = conversationId ? "conversation id" : "empty conversation id";
|
||||
return;
|
||||
}
|
||||
pending.push({
|
||||
input: entry.input,
|
||||
query: trimmed
|
||||
.replace(/^(msteams|teams):/i, "")
|
||||
.replace(/^team:/i, "")
|
||||
.trim(),
|
||||
index,
|
||||
});
|
||||
const parsed = parseMSTeamsTeamChannelInput(trimmed);
|
||||
if (!parsed.team) {
|
||||
entry.note = "missing team";
|
||||
return;
|
||||
}
|
||||
const query = parsed.channel ? `${parsed.team}/${parsed.channel}` : parsed.team;
|
||||
pending.push({ input: entry.input, query, index });
|
||||
});
|
||||
|
||||
if (pending.length > 0) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Request, Response } from "express";
|
||||
import { resolveTextChunkLimit } from "../../../src/auto-reply/chunk.js";
|
||||
import { mergeAllowlist, summarizeMapping } from "../../../src/channels/allowlists/resolve-utils.js";
|
||||
import type { ClawdbotConfig } from "../../../src/config/types.js";
|
||||
import { getChildLogger } from "../../../src/logging.js";
|
||||
import type { RuntimeEnv } from "../../../src/runtime.js";
|
||||
@@ -18,52 +19,6 @@ import { resolveMSTeamsCredentials } from "./token.js";
|
||||
|
||||
const log = getChildLogger({ name: "msteams" });
|
||||
|
||||
function mergeAllowlist(params: {
|
||||
existing?: Array<string | number>;
|
||||
additions: string[];
|
||||
}): string[] {
|
||||
const seen = new Set<string>();
|
||||
const merged: string[] = [];
|
||||
const push = (value: string) => {
|
||||
const normalized = value.trim();
|
||||
if (!normalized) return;
|
||||
const key = normalized.toLowerCase();
|
||||
if (seen.has(key)) return;
|
||||
seen.add(key);
|
||||
merged.push(normalized);
|
||||
};
|
||||
for (const entry of params.existing ?? []) {
|
||||
push(String(entry));
|
||||
}
|
||||
for (const entry of params.additions) {
|
||||
push(entry);
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
function summarizeMapping(
|
||||
label: string,
|
||||
mapping: string[],
|
||||
unresolved: string[],
|
||||
runtime: RuntimeEnv,
|
||||
) {
|
||||
const lines: string[] = [];
|
||||
if (mapping.length > 0) {
|
||||
const sample = mapping.slice(0, 6);
|
||||
const suffix = mapping.length > sample.length ? ` (+${mapping.length - sample.length})` : "";
|
||||
lines.push(`${label} resolved: ${sample.join(", ")}${suffix}`);
|
||||
}
|
||||
if (unresolved.length > 0) {
|
||||
const sample = unresolved.slice(0, 6);
|
||||
const suffix =
|
||||
unresolved.length > sample.length ? ` (+${unresolved.length - sample.length})` : "";
|
||||
lines.push(`${label} unresolved: ${sample.join(", ")}${suffix}`);
|
||||
}
|
||||
if (lines.length > 0) {
|
||||
runtime.log?.(lines.join("\n"));
|
||||
}
|
||||
}
|
||||
|
||||
export type MonitorMSTeamsOpts = {
|
||||
cfg: ClawdbotConfig;
|
||||
runtime?: RuntimeEnv;
|
||||
|
||||
@@ -11,7 +11,10 @@ import { promptChannelAccessConfig } from "../../../src/channels/plugins/onboard
|
||||
import { addWildcardAllowFrom } from "../../../src/channels/plugins/onboarding/helpers.js";
|
||||
|
||||
import { resolveMSTeamsCredentials } from "./token.js";
|
||||
import { resolveMSTeamsChannelAllowlist } from "./resolve-allowlist.js";
|
||||
import {
|
||||
parseMSTeamsTeamEntry,
|
||||
resolveMSTeamsChannelAllowlist,
|
||||
} from "./resolve-allowlist.js";
|
||||
|
||||
const channel = "msteams" as const;
|
||||
|
||||
@@ -94,18 +97,6 @@ function setMSTeamsTeamsAllowlist(
|
||||
};
|
||||
}
|
||||
|
||||
function parseMSTeamsTeamEntry(raw: string): { teamKey: string; channelKey?: string } | null {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) return null;
|
||||
const parts = trimmed.split("/");
|
||||
const teamPart = parts[0]?.trim();
|
||||
if (!teamPart) return null;
|
||||
const channelPart = parts.length > 1 ? parts.slice(1).join("/").trim() : undefined;
|
||||
const teamKey = teamPart.replace(/^team:/i, "").trim();
|
||||
const channelKey = channelPart ? channelPart.replace(/^#/, "").trim() : undefined;
|
||||
return { teamKey, ...(channelKey ? { channelKey } : {}) };
|
||||
}
|
||||
|
||||
const dmPolicy: ChannelOnboardingDmPolicy = {
|
||||
label: "MS Teams",
|
||||
channel,
|
||||
|
||||
@@ -49,6 +49,69 @@ function readAccessToken(value: unknown): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
function stripProviderPrefix(raw: string): string {
|
||||
return raw.replace(/^(msteams|teams):/i, "");
|
||||
}
|
||||
|
||||
export function normalizeMSTeamsMessagingTarget(raw: string): string | undefined {
|
||||
let trimmed = raw.trim();
|
||||
if (!trimmed) return undefined;
|
||||
trimmed = stripProviderPrefix(trimmed).trim();
|
||||
if (/^conversation:/i.test(trimmed)) {
|
||||
const id = trimmed.slice("conversation:".length).trim();
|
||||
return id ? `conversation:${id}` : undefined;
|
||||
}
|
||||
if (/^user:/i.test(trimmed)) {
|
||||
const id = trimmed.slice("user:".length).trim();
|
||||
return id ? `user:${id}` : undefined;
|
||||
}
|
||||
return trimmed || undefined;
|
||||
}
|
||||
|
||||
export function normalizeMSTeamsUserInput(raw: string): string {
|
||||
return stripProviderPrefix(raw).replace(/^(user|conversation):/i, "").trim();
|
||||
}
|
||||
|
||||
export function parseMSTeamsConversationId(raw: string): string | null {
|
||||
const trimmed = stripProviderPrefix(raw).trim();
|
||||
if (!/^conversation:/i.test(trimmed)) return null;
|
||||
const id = trimmed.slice("conversation:".length).trim();
|
||||
return id;
|
||||
}
|
||||
|
||||
function normalizeMSTeamsTeamKey(raw: string): string | undefined {
|
||||
const trimmed = stripProviderPrefix(raw).replace(/^team:/i, "").trim();
|
||||
return trimmed || undefined;
|
||||
}
|
||||
|
||||
function normalizeMSTeamsChannelKey(raw?: string | null): string | undefined {
|
||||
const trimmed = raw?.trim().replace(/^#/, "").trim() ?? "";
|
||||
return trimmed || undefined;
|
||||
}
|
||||
|
||||
export function parseMSTeamsTeamChannelInput(raw: string): { team?: string; channel?: string } {
|
||||
const trimmed = stripProviderPrefix(raw).trim();
|
||||
if (!trimmed) return {};
|
||||
const parts = trimmed.split("/");
|
||||
const team = normalizeMSTeamsTeamKey(parts[0] ?? "");
|
||||
const channel = parts.length > 1 ? normalizeMSTeamsChannelKey(parts.slice(1).join("/")) : undefined;
|
||||
return {
|
||||
...(team ? { team } : {}),
|
||||
...(channel ? { channel } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export function parseMSTeamsTeamEntry(
|
||||
raw: string,
|
||||
): { teamKey: string; channelKey?: string } | null {
|
||||
const { team, channel } = parseMSTeamsTeamChannelInput(raw);
|
||||
if (!team) return null;
|
||||
return {
|
||||
teamKey: team,
|
||||
...(channel ? { channelKey: channel } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeQuery(value?: string | null): string {
|
||||
return value?.trim() ?? "";
|
||||
}
|
||||
@@ -86,15 +149,6 @@ async function resolveGraphToken(cfg: unknown): Promise<string> {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
function parseTeamChannelInput(raw: string): { team?: string; channel?: string } {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) return {};
|
||||
const parts = trimmed.split("/");
|
||||
const team = parts[0]?.trim();
|
||||
const channel = parts.length > 1 ? parts.slice(1).join("/").trim() : undefined;
|
||||
return { team: team || undefined, channel: channel || undefined };
|
||||
}
|
||||
|
||||
async function listTeamsByName(token: string, query: string): Promise<GraphGroup[]> {
|
||||
const escaped = escapeOData(query);
|
||||
const filter = `resourceProvisioningOptions/Any(x:x eq 'Team') and startsWith(displayName,'${escaped}')`;
|
||||
@@ -117,7 +171,7 @@ export async function resolveMSTeamsChannelAllowlist(params: {
|
||||
const results: MSTeamsChannelResolution[] = [];
|
||||
|
||||
for (const input of params.entries) {
|
||||
const { team, channel } = parseTeamChannelInput(input);
|
||||
const { team, channel } = parseMSTeamsTeamChannelInput(input);
|
||||
if (!team) {
|
||||
results.push({ input, resolved: false });
|
||||
continue;
|
||||
@@ -180,7 +234,7 @@ export async function resolveMSTeamsUserAllowlist(params: {
|
||||
const results: MSTeamsUserResolution[] = [];
|
||||
|
||||
for (const input of params.entries) {
|
||||
const query = normalizeQuery(input);
|
||||
const query = normalizeQuery(normalizeMSTeamsUserInput(input));
|
||||
if (!query) {
|
||||
results.push({ input, resolved: false });
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user