mirror of
https://github.com/farcasclaudiu/openclaw.git
synced 2026-06-29 05:02:04 +03:00
refactor(msteams): share Graph helpers
This commit is contained in:
@@ -1,95 +1,16 @@
|
|||||||
import type { ChannelDirectoryEntry, MSTeamsConfig } from "openclaw/plugin-sdk";
|
import type { ChannelDirectoryEntry } from "openclaw/plugin-sdk";
|
||||||
import { GRAPH_ROOT } from "./attachments/shared.js";
|
import {
|
||||||
import { loadMSTeamsSdkWithAuth } from "./sdk.js";
|
escapeOData,
|
||||||
import { resolveMSTeamsCredentials } from "./token.js";
|
fetchGraphJson,
|
||||||
|
type GraphChannel,
|
||||||
type GraphUser = {
|
type GraphGroup,
|
||||||
id?: string;
|
type GraphResponse,
|
||||||
displayName?: string;
|
type GraphUser,
|
||||||
userPrincipalName?: string;
|
listChannelsForTeam,
|
||||||
mail?: string;
|
listTeamsByName,
|
||||||
};
|
normalizeQuery,
|
||||||
|
resolveGraphToken,
|
||||||
type GraphGroup = {
|
} from "./graph.js";
|
||||||
id?: string;
|
|
||||||
displayName?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type GraphChannel = {
|
|
||||||
id?: string;
|
|
||||||
displayName?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type GraphResponse<T> = { value?: T[] };
|
|
||||||
|
|
||||||
function readAccessToken(value: unknown): string | null {
|
|
||||||
if (typeof value === "string") {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
if (value && typeof value === "object") {
|
|
||||||
const token =
|
|
||||||
(value as { accessToken?: unknown }).accessToken ?? (value as { token?: unknown }).token;
|
|
||||||
return typeof token === "string" ? token : null;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeQuery(value?: string | null): string {
|
|
||||||
return value?.trim() ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeOData(value: string): string {
|
|
||||||
return value.replace(/'/g, "''");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchGraphJson<T>(params: {
|
|
||||||
token: string;
|
|
||||||
path: string;
|
|
||||||
headers?: Record<string, string>;
|
|
||||||
}): Promise<T> {
|
|
||||||
const res = await fetch(`${GRAPH_ROOT}${params.path}`, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${params.token}`,
|
|
||||||
...params.headers,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!res.ok) {
|
|
||||||
const text = await res.text().catch(() => "");
|
|
||||||
throw new Error(`Graph ${params.path} failed (${res.status}): ${text || "unknown error"}`);
|
|
||||||
}
|
|
||||||
return (await res.json()) as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function resolveGraphToken(cfg: unknown): Promise<string> {
|
|
||||||
const creds = resolveMSTeamsCredentials(
|
|
||||||
(cfg as { channels?: { msteams?: unknown } })?.channels?.msteams as MSTeamsConfig | undefined,
|
|
||||||
);
|
|
||||||
if (!creds) {
|
|
||||||
throw new Error("MS Teams credentials missing");
|
|
||||||
}
|
|
||||||
const { sdk, authConfig } = await loadMSTeamsSdkWithAuth(creds);
|
|
||||||
const tokenProvider = new sdk.MsalTokenProvider(authConfig);
|
|
||||||
const token = await tokenProvider.getAccessToken("https://graph.microsoft.com");
|
|
||||||
const accessToken = readAccessToken(token);
|
|
||||||
if (!accessToken) {
|
|
||||||
throw new Error("MS Teams graph token unavailable");
|
|
||||||
}
|
|
||||||
return accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
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}')`;
|
|
||||||
const path = `/groups?$filter=${encodeURIComponent(filter)}&$select=id,displayName`;
|
|
||||||
const res = await fetchGraphJson<GraphResponse<GraphGroup>>({ token, path });
|
|
||||||
return res.value ?? [];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function listChannelsForTeam(token: string, teamId: string): Promise<GraphChannel[]> {
|
|
||||||
const path = `/teams/${encodeURIComponent(teamId)}/channels?$select=id,displayName`;
|
|
||||||
const res = await fetchGraphJson<GraphResponse<GraphChannel>>({ token, path });
|
|
||||||
return res.value ?? [];
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function listMSTeamsDirectoryPeersLive(params: {
|
export async function listMSTeamsDirectoryPeersLive(params: {
|
||||||
cfg: unknown;
|
cfg: unknown;
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import type { MSTeamsConfig } from "openclaw/plugin-sdk";
|
||||||
|
import { GRAPH_ROOT } from "./attachments/shared.js";
|
||||||
|
import { loadMSTeamsSdkWithAuth } from "./sdk.js";
|
||||||
|
import { resolveMSTeamsCredentials } from "./token.js";
|
||||||
|
|
||||||
|
export type GraphUser = {
|
||||||
|
id?: string;
|
||||||
|
displayName?: string;
|
||||||
|
userPrincipalName?: string;
|
||||||
|
mail?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GraphGroup = {
|
||||||
|
id?: string;
|
||||||
|
displayName?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GraphChannel = {
|
||||||
|
id?: string;
|
||||||
|
displayName?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GraphResponse<T> = { value?: T[] };
|
||||||
|
|
||||||
|
function readAccessToken(value: unknown): string | null {
|
||||||
|
if (typeof value === "string") {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (value && typeof value === "object") {
|
||||||
|
const token =
|
||||||
|
(value as { accessToken?: unknown }).accessToken ?? (value as { token?: unknown }).token;
|
||||||
|
return typeof token === "string" ? token : null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeQuery(value?: string | null): string {
|
||||||
|
return value?.trim() ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function escapeOData(value: string): string {
|
||||||
|
return value.replace(/'/g, "''");
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchGraphJson<T>(params: {
|
||||||
|
token: string;
|
||||||
|
path: string;
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
}): Promise<T> {
|
||||||
|
const res = await fetch(`${GRAPH_ROOT}${params.path}`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${params.token}`,
|
||||||
|
...params.headers,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
const text = await res.text().catch(() => "");
|
||||||
|
throw new Error(`Graph ${params.path} failed (${res.status}): ${text || "unknown error"}`);
|
||||||
|
}
|
||||||
|
return (await res.json()) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resolveGraphToken(cfg: unknown): Promise<string> {
|
||||||
|
const creds = resolveMSTeamsCredentials(
|
||||||
|
(cfg as { channels?: { msteams?: unknown } })?.channels?.msteams as MSTeamsConfig | undefined,
|
||||||
|
);
|
||||||
|
if (!creds) {
|
||||||
|
throw new Error("MS Teams credentials missing");
|
||||||
|
}
|
||||||
|
const { sdk, authConfig } = await loadMSTeamsSdkWithAuth(creds);
|
||||||
|
const tokenProvider = new sdk.MsalTokenProvider(authConfig);
|
||||||
|
const token = await tokenProvider.getAccessToken("https://graph.microsoft.com");
|
||||||
|
const accessToken = readAccessToken(token);
|
||||||
|
if (!accessToken) {
|
||||||
|
throw new Error("MS Teams graph token unavailable");
|
||||||
|
}
|
||||||
|
return accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
export 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}')`;
|
||||||
|
const path = `/groups?$filter=${encodeURIComponent(filter)}&$select=id,displayName`;
|
||||||
|
const res = await fetchGraphJson<GraphResponse<GraphGroup>>({ token, path });
|
||||||
|
return res.value ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listChannelsForTeam(token: string, teamId: string): Promise<GraphChannel[]> {
|
||||||
|
const path = `/teams/${encodeURIComponent(teamId)}/channels?$select=id,displayName`;
|
||||||
|
const res = await fetchGraphJson<GraphResponse<GraphChannel>>({ token, path });
|
||||||
|
return res.value ?? [];
|
||||||
|
}
|
||||||
@@ -1,26 +1,13 @@
|
|||||||
import type { MSTeamsConfig } from "openclaw/plugin-sdk";
|
import {
|
||||||
import { GRAPH_ROOT } from "./attachments/shared.js";
|
escapeOData,
|
||||||
import { loadMSTeamsSdkWithAuth } from "./sdk.js";
|
fetchGraphJson,
|
||||||
import { resolveMSTeamsCredentials } from "./token.js";
|
type GraphResponse,
|
||||||
|
type GraphUser,
|
||||||
type GraphUser = {
|
listChannelsForTeam,
|
||||||
id?: string;
|
listTeamsByName,
|
||||||
displayName?: string;
|
normalizeQuery,
|
||||||
userPrincipalName?: string;
|
resolveGraphToken,
|
||||||
mail?: string;
|
} from "./graph.js";
|
||||||
};
|
|
||||||
|
|
||||||
type GraphGroup = {
|
|
||||||
id?: string;
|
|
||||||
displayName?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type GraphChannel = {
|
|
||||||
id?: string;
|
|
||||||
displayName?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type GraphResponse<T> = { value?: T[] };
|
|
||||||
|
|
||||||
export type MSTeamsChannelResolution = {
|
export type MSTeamsChannelResolution = {
|
||||||
input: string;
|
input: string;
|
||||||
@@ -40,18 +27,6 @@ export type MSTeamsUserResolution = {
|
|||||||
note?: string;
|
note?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function readAccessToken(value: unknown): string | null {
|
|
||||||
if (typeof value === "string") {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
if (value && typeof value === "object") {
|
|
||||||
const token =
|
|
||||||
(value as { accessToken?: unknown }).accessToken ?? (value as { token?: unknown }).token;
|
|
||||||
return typeof token === "string" ? token : null;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function stripProviderPrefix(raw: string): string {
|
function stripProviderPrefix(raw: string): string {
|
||||||
return raw.replace(/^(msteams|teams):/i, "");
|
return raw.replace(/^(msteams|teams):/i, "");
|
||||||
}
|
}
|
||||||
@@ -128,63 +103,6 @@ export function parseMSTeamsTeamEntry(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeQuery(value?: string | null): string {
|
|
||||||
return value?.trim() ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeOData(value: string): string {
|
|
||||||
return value.replace(/'/g, "''");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchGraphJson<T>(params: {
|
|
||||||
token: string;
|
|
||||||
path: string;
|
|
||||||
headers?: Record<string, string>;
|
|
||||||
}): Promise<T> {
|
|
||||||
const res = await fetch(`${GRAPH_ROOT}${params.path}`, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${params.token}`,
|
|
||||||
...params.headers,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!res.ok) {
|
|
||||||
const text = await res.text().catch(() => "");
|
|
||||||
throw new Error(`Graph ${params.path} failed (${res.status}): ${text || "unknown error"}`);
|
|
||||||
}
|
|
||||||
return (await res.json()) as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function resolveGraphToken(cfg: unknown): Promise<string> {
|
|
||||||
const creds = resolveMSTeamsCredentials(
|
|
||||||
(cfg as { channels?: { msteams?: unknown } })?.channels?.msteams as MSTeamsConfig | undefined,
|
|
||||||
);
|
|
||||||
if (!creds) {
|
|
||||||
throw new Error("MS Teams credentials missing");
|
|
||||||
}
|
|
||||||
const { sdk, authConfig } = await loadMSTeamsSdkWithAuth(creds);
|
|
||||||
const tokenProvider = new sdk.MsalTokenProvider(authConfig);
|
|
||||||
const token = await tokenProvider.getAccessToken("https://graph.microsoft.com");
|
|
||||||
const accessToken = readAccessToken(token);
|
|
||||||
if (!accessToken) {
|
|
||||||
throw new Error("MS Teams graph token unavailable");
|
|
||||||
}
|
|
||||||
return accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
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}')`;
|
|
||||||
const path = `/groups?$filter=${encodeURIComponent(filter)}&$select=id,displayName`;
|
|
||||||
const res = await fetchGraphJson<GraphResponse<GraphGroup>>({ token, path });
|
|
||||||
return res.value ?? [];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function listChannelsForTeam(token: string, teamId: string): Promise<GraphChannel[]> {
|
|
||||||
const path = `/teams/${encodeURIComponent(teamId)}/channels?$select=id,displayName`;
|
|
||||||
const res = await fetchGraphJson<GraphResponse<GraphChannel>>({ token, path });
|
|
||||||
return res.value ?? [];
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function resolveMSTeamsChannelAllowlist(params: {
|
export async function resolveMSTeamsChannelAllowlist(params: {
|
||||||
cfg: unknown;
|
cfg: unknown;
|
||||||
entries: string[];
|
entries: string[];
|
||||||
|
|||||||
Reference in New Issue
Block a user