mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-01 23:16:27 +02:00
msteams: add member-info action via Graph API (#57528)
This commit is contained in:
@@ -2,6 +2,7 @@ import {
|
||||
listMSTeamsDirectoryGroupsLive as listMSTeamsDirectoryGroupsLiveImpl,
|
||||
listMSTeamsDirectoryPeersLive as listMSTeamsDirectoryPeersLiveImpl,
|
||||
} from "./directory-live.js";
|
||||
import { getMemberInfoMSTeams as getMemberInfoMSTeamsImpl } from "./graph-members.js";
|
||||
import {
|
||||
getMessageMSTeams as getMessageMSTeamsImpl,
|
||||
listPinsMSTeams as listPinsMSTeamsImpl,
|
||||
@@ -23,6 +24,7 @@ import {
|
||||
export const msTeamsChannelRuntime = {
|
||||
deleteMessageMSTeams: deleteMessageMSTeamsImpl,
|
||||
editMessageMSTeams: editMessageMSTeamsImpl,
|
||||
getMemberInfoMSTeams: getMemberInfoMSTeamsImpl,
|
||||
getMessageMSTeams: getMessageMSTeamsImpl,
|
||||
listPinsMSTeams: listPinsMSTeamsImpl,
|
||||
listReactionsMSTeams: listReactionsMSTeamsImpl,
|
||||
|
||||
@@ -329,6 +329,7 @@ function describeMSTeamsMessageTool({
|
||||
"react",
|
||||
"reactions",
|
||||
"search",
|
||||
"member-info",
|
||||
] satisfies ChannelMessageActionName[])
|
||||
: [],
|
||||
capabilities: enabled ? ["cards"] : [],
|
||||
@@ -842,6 +843,16 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount, ProbeMSTeamsRe
|
||||
});
|
||||
}
|
||||
|
||||
if (ctx.action === "member-info") {
|
||||
const userId = typeof ctx.params.userId === "string" ? ctx.params.userId.trim() : "";
|
||||
if (!userId) {
|
||||
return actionError("member-info requires a userId.");
|
||||
}
|
||||
const { getMemberInfoMSTeams } = await loadMSTeamsChannelRuntime();
|
||||
const result = await getMemberInfoMSTeams({ cfg: ctx.cfg, userId });
|
||||
return jsonMSTeamsOkActionResult("member-info", result);
|
||||
}
|
||||
|
||||
// Return null to fall through to default handler
|
||||
return null as never;
|
||||
},
|
||||
|
||||
91
extensions/msteams/src/graph-members.test.ts
Normal file
91
extensions/msteams/src/graph-members.test.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../runtime-api.js";
|
||||
import { getMemberInfoMSTeams } from "./graph-members.js";
|
||||
|
||||
const mockState = vi.hoisted(() => ({
|
||||
resolveGraphToken: vi.fn(),
|
||||
fetchGraphJson: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./graph.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("./graph.js")>();
|
||||
return {
|
||||
...actual,
|
||||
resolveGraphToken: mockState.resolveGraphToken,
|
||||
fetchGraphJson: mockState.fetchGraphJson,
|
||||
};
|
||||
});
|
||||
|
||||
const TOKEN = "test-graph-token";
|
||||
|
||||
describe("getMemberInfoMSTeams", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockState.resolveGraphToken.mockResolvedValue(TOKEN);
|
||||
});
|
||||
|
||||
it("fetches user profile and maps all fields", async () => {
|
||||
mockState.fetchGraphJson.mockResolvedValue({
|
||||
id: "user-123",
|
||||
displayName: "Alice Smith",
|
||||
mail: "alice@contoso.com",
|
||||
jobTitle: "Engineer",
|
||||
userPrincipalName: "alice@contoso.com",
|
||||
officeLocation: "Building 1",
|
||||
});
|
||||
|
||||
const result = await getMemberInfoMSTeams({
|
||||
cfg: {} as OpenClawConfig,
|
||||
userId: "user-123",
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
user: {
|
||||
id: "user-123",
|
||||
displayName: "Alice Smith",
|
||||
mail: "alice@contoso.com",
|
||||
jobTitle: "Engineer",
|
||||
userPrincipalName: "alice@contoso.com",
|
||||
officeLocation: "Building 1",
|
||||
},
|
||||
});
|
||||
expect(mockState.fetchGraphJson).toHaveBeenCalledWith({
|
||||
token: TOKEN,
|
||||
path: `/users/${encodeURIComponent("user-123")}?$select=id,displayName,mail,jobTitle,userPrincipalName,officeLocation`,
|
||||
});
|
||||
});
|
||||
|
||||
it("handles sparse data with some fields undefined", async () => {
|
||||
mockState.fetchGraphJson.mockResolvedValue({
|
||||
id: "user-456",
|
||||
displayName: "Bob",
|
||||
});
|
||||
|
||||
const result = await getMemberInfoMSTeams({
|
||||
cfg: {} as OpenClawConfig,
|
||||
userId: "user-456",
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
user: {
|
||||
id: "user-456",
|
||||
displayName: "Bob",
|
||||
mail: undefined,
|
||||
jobTitle: undefined,
|
||||
userPrincipalName: undefined,
|
||||
officeLocation: undefined,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("propagates Graph API errors", async () => {
|
||||
mockState.fetchGraphJson.mockRejectedValue(new Error("Graph API 404: user not found"));
|
||||
|
||||
await expect(
|
||||
getMemberInfoMSTeams({
|
||||
cfg: {} as OpenClawConfig,
|
||||
userId: "nonexistent-user",
|
||||
}),
|
||||
).rejects.toThrow("Graph API 404: user not found");
|
||||
});
|
||||
});
|
||||
48
extensions/msteams/src/graph-members.ts
Normal file
48
extensions/msteams/src/graph-members.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import type { OpenClawConfig } from "../runtime-api.js";
|
||||
import { fetchGraphJson, resolveGraphToken } from "./graph.js";
|
||||
|
||||
type GraphUserProfile = {
|
||||
id?: string;
|
||||
displayName?: string;
|
||||
mail?: string;
|
||||
jobTitle?: string;
|
||||
userPrincipalName?: string;
|
||||
officeLocation?: string;
|
||||
};
|
||||
|
||||
export type GetMemberInfoMSTeamsParams = {
|
||||
cfg: OpenClawConfig;
|
||||
userId: string;
|
||||
};
|
||||
|
||||
export type GetMemberInfoMSTeamsResult = {
|
||||
user: {
|
||||
id: string | undefined;
|
||||
displayName: string | undefined;
|
||||
mail: string | undefined;
|
||||
jobTitle: string | undefined;
|
||||
userPrincipalName: string | undefined;
|
||||
officeLocation: string | undefined;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch a user profile from Microsoft Graph by user ID.
|
||||
*/
|
||||
export async function getMemberInfoMSTeams(
|
||||
params: GetMemberInfoMSTeamsParams,
|
||||
): Promise<GetMemberInfoMSTeamsResult> {
|
||||
const token = await resolveGraphToken(params.cfg);
|
||||
const path = `/users/${encodeURIComponent(params.userId)}?$select=id,displayName,mail,jobTitle,userPrincipalName,officeLocation`;
|
||||
const user = await fetchGraphJson<GraphUserProfile>({ token, path });
|
||||
return {
|
||||
user: {
|
||||
id: user.id,
|
||||
displayName: user.displayName,
|
||||
mail: user.mail,
|
||||
jobTitle: user.jobTitle,
|
||||
userPrincipalName: user.userPrincipalName,
|
||||
officeLocation: user.officeLocation,
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user