agent-messenger 2.28.0 → 2.29.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +29 -1
- package/dist/package.json +1 -1
- package/dist/src/platforms/discord/client.d.ts +6 -2
- package/dist/src/platforms/discord/client.d.ts.map +1 -1
- package/dist/src/platforms/discord/client.js +10 -3
- package/dist/src/platforms/discord/client.js.map +1 -1
- package/dist/src/platforms/instagram/client.d.ts +24 -2
- package/dist/src/platforms/instagram/client.d.ts.map +1 -1
- package/dist/src/platforms/instagram/client.js +343 -53
- package/dist/src/platforms/instagram/client.js.map +1 -1
- package/dist/src/platforms/instagram/commands/auth.d.ts +1 -0
- package/dist/src/platforms/instagram/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/instagram/commands/auth.js +123 -11
- package/dist/src/platforms/instagram/commands/auth.js.map +1 -1
- package/dist/src/platforms/instagram/credential-manager.d.ts +4 -1
- package/dist/src/platforms/instagram/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/instagram/credential-manager.js +17 -1
- package/dist/src/platforms/instagram/credential-manager.js.map +1 -1
- package/dist/src/platforms/instagram/types.d.ts +10 -8
- package/dist/src/platforms/instagram/types.d.ts.map +1 -1
- package/dist/src/platforms/instagram/types.js.map +1 -1
- package/dist/src/platforms/teams/app-config.d.ts +36 -0
- package/dist/src/platforms/teams/app-config.d.ts.map +1 -0
- package/dist/src/platforms/teams/app-config.js +69 -0
- package/dist/src/platforms/teams/app-config.js.map +1 -0
- package/dist/src/platforms/teams/client.d.ts +20 -2
- package/dist/src/platforms/teams/client.d.ts.map +1 -1
- package/dist/src/platforms/teams/client.js +311 -7
- package/dist/src/platforms/teams/client.js.map +1 -1
- package/dist/src/platforms/teams/commands/auth.d.ts +10 -0
- package/dist/src/platforms/teams/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/auth.js +137 -3
- package/dist/src/platforms/teams/commands/auth.js.map +1 -1
- package/dist/src/platforms/teams/commands/file.d.ts +3 -0
- package/dist/src/platforms/teams/commands/file.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/file.js +59 -2
- package/dist/src/platforms/teams/commands/file.js.map +1 -1
- package/dist/src/platforms/teams/commands/message.d.ts +11 -0
- package/dist/src/platforms/teams/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/message.js +121 -2
- package/dist/src/platforms/teams/commands/message.js.map +1 -1
- package/dist/src/platforms/teams/credential-manager.d.ts +23 -1
- package/dist/src/platforms/teams/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/teams/credential-manager.js +46 -2
- package/dist/src/platforms/teams/credential-manager.js.map +1 -1
- package/dist/src/platforms/teams/device-code.d.ts +48 -0
- package/dist/src/platforms/teams/device-code.d.ts.map +1 -0
- package/dist/src/platforms/teams/device-code.js +199 -0
- package/dist/src/platforms/teams/device-code.js.map +1 -0
- package/dist/src/platforms/teams/device-login.d.ts +37 -0
- package/dist/src/platforms/teams/device-login.d.ts.map +1 -0
- package/dist/src/platforms/teams/device-login.js +129 -0
- package/dist/src/platforms/teams/device-login.js.map +1 -0
- package/dist/src/platforms/teams/ensure-auth.d.ts.map +1 -1
- package/dist/src/platforms/teams/ensure-auth.js +12 -0
- package/dist/src/platforms/teams/ensure-auth.js.map +1 -1
- package/dist/src/platforms/teams/index.d.ts +7 -3
- package/dist/src/platforms/teams/index.d.ts.map +1 -1
- package/dist/src/platforms/teams/index.js +5 -2
- package/dist/src/platforms/teams/index.js.map +1 -1
- package/dist/src/platforms/teams/listener.d.ts +40 -0
- package/dist/src/platforms/teams/listener.d.ts.map +1 -0
- package/dist/src/platforms/teams/listener.js +286 -0
- package/dist/src/platforms/teams/listener.js.map +1 -0
- package/dist/src/platforms/teams/realm-discovery.d.ts +3 -0
- package/dist/src/platforms/teams/realm-discovery.d.ts.map +1 -0
- package/dist/src/platforms/teams/realm-discovery.js +33 -0
- package/dist/src/platforms/teams/realm-discovery.js.map +1 -0
- package/dist/src/platforms/teams/token-extractor.d.ts +3 -0
- package/dist/src/platforms/teams/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/teams/token-extractor.js +60 -0
- package/dist/src/platforms/teams/token-extractor.js.map +1 -1
- package/dist/src/platforms/teams/token-provider.d.ts +25 -0
- package/dist/src/platforms/teams/token-provider.d.ts.map +1 -0
- package/dist/src/platforms/teams/token-provider.js +190 -0
- package/dist/src/platforms/teams/token-provider.js.map +1 -0
- package/dist/src/platforms/teams/trouter.d.ts +38 -0
- package/dist/src/platforms/teams/trouter.d.ts.map +1 -0
- package/dist/src/platforms/teams/trouter.js +266 -0
- package/dist/src/platforms/teams/trouter.js.map +1 -0
- package/dist/src/platforms/teams/types.d.ts +103 -0
- package/dist/src/platforms/teams/types.d.ts.map +1 -1
- package/dist/src/platforms/teams/types.js +30 -0
- package/dist/src/platforms/teams/types.js.map +1 -1
- package/dist/src/platforms/webex/types.d.ts +1 -1
- package/dist/src/tui/adapters/instagram-adapter.d.ts.map +1 -1
- package/dist/src/tui/adapters/instagram-adapter.js +16 -0
- package/dist/src/tui/adapters/instagram-adapter.js.map +1 -1
- package/dist/src/tui/adapters/teams-adapter.d.ts +3 -0
- package/dist/src/tui/adapters/teams-adapter.d.ts.map +1 -1
- package/dist/src/tui/adapters/teams-adapter.js +21 -0
- package/dist/src/tui/adapters/teams-adapter.js.map +1 -1
- package/dist/src/tui/app.js +3 -3
- package/docs/content/docs/cli/instagram.mdx +1 -0
- package/docs/content/docs/cli/teams.mdx +36 -0
- package/docs/content/docs/sdk/teams.mdx +30 -0
- package/package.json +1 -1
- package/skills/agent-channeltalk/SKILL.md +1 -1
- package/skills/agent-channeltalkbot/SKILL.md +1 -1
- package/skills/agent-discord/SKILL.md +1 -1
- package/skills/agent-discordbot/SKILL.md +1 -1
- package/skills/agent-imessage/SKILL.md +1 -1
- package/skills/agent-instagram/SKILL.md +1 -1
- package/skills/agent-instagram/references/authentication.md +11 -0
- package/skills/agent-kakaotalk/SKILL.md +1 -1
- package/skills/agent-line/SKILL.md +1 -1
- package/skills/agent-slack/SKILL.md +1 -1
- package/skills/agent-slackbot/SKILL.md +1 -1
- package/skills/agent-teams/SKILL.md +82 -6
- package/skills/agent-teams/references/authentication.md +31 -1
- package/skills/agent-telegram/SKILL.md +1 -1
- package/skills/agent-telegrambot/SKILL.md +1 -1
- package/skills/agent-webex/SKILL.md +1 -1
- package/skills/agent-webexbot/SKILL.md +1 -1
- package/skills/agent-wechatbot/SKILL.md +1 -1
- package/skills/agent-whatsapp/SKILL.md +1 -1
- package/skills/agent-whatsappbot/SKILL.md +1 -1
- package/src/platforms/discord/client.test.ts +86 -0
- package/src/platforms/discord/client.ts +10 -3
- package/src/platforms/instagram/client.test.ts +478 -33
- package/src/platforms/instagram/client.ts +449 -68
- package/src/platforms/instagram/commands/auth.test.ts +177 -1
- package/src/platforms/instagram/commands/auth.ts +182 -11
- package/src/platforms/instagram/credential-manager.test.ts +30 -0
- package/src/platforms/instagram/credential-manager.ts +23 -1
- package/src/platforms/instagram/types.ts +11 -8
- package/src/platforms/teams/app-config.ts +92 -0
- package/src/platforms/teams/client.test.ts +335 -1
- package/src/platforms/teams/client.ts +370 -7
- package/src/platforms/teams/commands/auth.test.ts +93 -1
- package/src/platforms/teams/commands/auth.ts +182 -3
- package/src/platforms/teams/commands/file.test.ts +92 -1
- package/src/platforms/teams/commands/file.ts +76 -1
- package/src/platforms/teams/commands/message.test.ts +95 -1
- package/src/platforms/teams/commands/message.ts +145 -3
- package/src/platforms/teams/credential-manager.test.ts +20 -0
- package/src/platforms/teams/credential-manager.ts +68 -3
- package/src/platforms/teams/device-code.test.ts +311 -0
- package/src/platforms/teams/device-code.ts +277 -0
- package/src/platforms/teams/device-login.test.ts +288 -0
- package/src/platforms/teams/device-login.ts +206 -0
- package/src/platforms/teams/ensure-auth.ts +11 -0
- package/src/platforms/teams/index.test.ts +10 -0
- package/src/platforms/teams/index.ts +17 -1
- package/src/platforms/teams/listener.test.ts +243 -0
- package/src/platforms/teams/listener.ts +351 -0
- package/src/platforms/teams/realm-discovery.test.ts +73 -0
- package/src/platforms/teams/realm-discovery.ts +42 -0
- package/src/platforms/teams/token-extractor.ts +69 -0
- package/src/platforms/teams/token-provider.test.ts +227 -0
- package/src/platforms/teams/token-provider.ts +284 -0
- package/src/platforms/teams/trouter.test.ts +174 -0
- package/src/platforms/teams/trouter.ts +323 -0
- package/src/platforms/teams/types.test.ts +17 -0
- package/src/platforms/teams/types.ts +96 -0
- package/src/tui/adapters/instagram-adapter.ts +13 -0
- package/src/tui/adapters/teams-adapter.ts +23 -0
- package/src/tui/app.ts +3 -3
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export const TEAMS_DESKTOP_CLIENT_ID = '1fec8e78-bce4-4aaf-ab1b-5451cc387264';
|
|
2
|
+
export const TEAMS_WEB_CLIENT_ID = '5e3ce6c0-2b1f-4285-8d4b-75ee78787346';
|
|
3
|
+
export const CONSUMER_TENANT_ID = '9188040d-6c67-4c5b-b112-36a304b66dad';
|
|
4
|
+
// The Microsoft Services (MSA passthrough) tenant. Consumer/MSA tokens for the
|
|
5
|
+
// Skype resource carry this `tid`, so it is a placeholder that still requires
|
|
6
|
+
// tenant discovery before the skype-token exchange.
|
|
7
|
+
export const MICROSOFT_SERVICES_TENANT_ID = 'f8cdef31-a31e-4b4a-93e4-5f571e91255a';
|
|
8
|
+
export const WORK_TENANT_ID = 'organizations';
|
|
9
|
+
export const DEVICE_CODE_SCOPE_TEAMS = 'service::api.fl.teams.microsoft.com::MBI_SSL openid profile offline_access';
|
|
10
|
+
export const DEVICE_CODE_SCOPE_SKYPE = 'service::api.fl.spaces.skype.com::MBI_SSL openid profile offline_access';
|
|
11
|
+
export const WORK_DEVICE_CODE_SCOPE_TEAMS = 'https://api.spaces.skype.com/.default openid profile offline_access';
|
|
12
|
+
export const WORK_DEVICE_CODE_SCOPE_SKYPE = 'https://api.spaces.skype.com/.default openid profile offline_access';
|
|
13
|
+
export const SUBSTRATE_SEARCH_URL = 'https://substrate.office.com/searchservice/api/v2/query';
|
|
14
|
+
export const TEAMS_WEB_ORIGIN = 'https://teams.microsoft.com';
|
|
15
|
+
export const AAD_SCOPE_SUBSTRATE = 'https://substrate.office.com/.default offline_access';
|
|
16
|
+
export const AAD_SCOPE_GRAPH = 'https://graph.microsoft.com/.default offline_access';
|
|
17
|
+
export const AAD_AUDIENCE_SUBSTRATE = 'https://substrate.office.com';
|
|
18
|
+
export const AAD_AUDIENCE_GRAPH = 'https://graph.microsoft.com';
|
|
19
|
+
export const AUTHZ_CONSUMER_URL = 'https://teams.live.com/api/auth/v1.0/authz/consumer';
|
|
20
|
+
export const AUTHZ_WORK_URL = 'https://teams.microsoft.com/api/authsvc/v1.0/authz';
|
|
21
|
+
// Control-plane tenant-discovery endpoint. The region segment is not
|
|
22
|
+
// authoritative for this call (it lists every tenant the user belongs to
|
|
23
|
+
// regardless of home region), so we pin the value fossteams uses.
|
|
24
|
+
export const TEAMS_TENANTS_URL = 'https://teams.microsoft.com/api/mt/emea/beta/users/tenants';
|
|
25
|
+
export const GET_CREDENTIAL_TYPE_URL = 'https://login.microsoftonline.com/common/GetCredentialType';
|
|
26
|
+
export function consumerDeviceCodeUrl() {
|
|
27
|
+
return `https://login.microsoftonline.com/${CONSUMER_TENANT_ID}/oauth2/v2.0/devicecode`;
|
|
28
|
+
}
|
|
29
|
+
export function consumerTokenUrl() {
|
|
30
|
+
return `https://login.microsoftonline.com/${CONSUMER_TENANT_ID}/oauth2/v2.0/token`;
|
|
31
|
+
}
|
|
32
|
+
export function organizationsDeviceCodeUrl() {
|
|
33
|
+
return `https://login.microsoftonline.com/${WORK_TENANT_ID}/oauth2/v2.0/devicecode`;
|
|
34
|
+
}
|
|
35
|
+
export function organizationsTokenUrl() {
|
|
36
|
+
return `https://login.microsoftonline.com/${WORK_TENANT_ID}/oauth2/v2.0/token`;
|
|
37
|
+
}
|
|
38
|
+
export function tenantTokenUrl(tenantId) {
|
|
39
|
+
return `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
|
|
40
|
+
}
|
|
41
|
+
export function deviceCodeUrl(accountType) {
|
|
42
|
+
return accountType === 'personal' ? consumerDeviceCodeUrl() : organizationsDeviceCodeUrl();
|
|
43
|
+
}
|
|
44
|
+
export function deviceCodeTokenUrl(accountType, tenantId) {
|
|
45
|
+
if (tenantId)
|
|
46
|
+
return tenantTokenUrl(tenantId);
|
|
47
|
+
return accountType === 'personal' ? consumerTokenUrl() : organizationsTokenUrl();
|
|
48
|
+
}
|
|
49
|
+
export function deviceCodeTeamsScope(accountType) {
|
|
50
|
+
return accountType === 'personal' ? DEVICE_CODE_SCOPE_TEAMS : WORK_DEVICE_CODE_SCOPE_TEAMS;
|
|
51
|
+
}
|
|
52
|
+
export function deviceCodeSkypeScope(accountType) {
|
|
53
|
+
return accountType === 'personal' ? DEVICE_CODE_SCOPE_SKYPE : WORK_DEVICE_CODE_SCOPE_SKYPE;
|
|
54
|
+
}
|
|
55
|
+
export function authzUrl(accountType) {
|
|
56
|
+
return accountType === 'personal' ? AUTHZ_CONSUMER_URL : AUTHZ_WORK_URL;
|
|
57
|
+
}
|
|
58
|
+
function parseTrimmed(value) {
|
|
59
|
+
const normalized = value?.trim();
|
|
60
|
+
return normalized ? normalized : undefined;
|
|
61
|
+
}
|
|
62
|
+
export function getTeamsAppClientId(accountType = 'personal') {
|
|
63
|
+
const envClientId = parseTrimmed(process.env.AGENT_TEAMS_CLIENT_ID);
|
|
64
|
+
if (envClientId) {
|
|
65
|
+
return { clientId: envClientId, source: 'env' };
|
|
66
|
+
}
|
|
67
|
+
return { clientId: accountType === 'work' ? TEAMS_DESKTOP_CLIENT_ID : TEAMS_WEB_CLIENT_ID, source: 'builtin' };
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=app-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-config.js","sourceRoot":"","sources":["../../../../src/platforms/teams/app-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { TeamsCredentialManager } from './credential-manager.js';
|
|
2
|
+
import type { TeamsAccountType, TeamsChannel, TeamsChat, TeamsFile, TeamsMessage, TeamsRegion, TeamsSearchResult, TeamsTeam, TeamsUser } from './types.js';
|
|
2
3
|
export declare class TeamsClient {
|
|
4
|
+
private credManager;
|
|
3
5
|
private token;
|
|
4
6
|
private tokenExpiresAt?;
|
|
5
7
|
private isPersonalAccount;
|
|
6
8
|
private region;
|
|
7
9
|
private regionDiscovered;
|
|
10
|
+
private tokenProvider?;
|
|
8
11
|
private buckets;
|
|
9
12
|
private globalRateLimitUntil;
|
|
13
|
+
constructor(credManager?: TeamsCredentialManager);
|
|
10
14
|
login(credentials?: {
|
|
11
15
|
token: string;
|
|
12
16
|
tokenExpiresAt?: string;
|
|
@@ -14,6 +18,9 @@ export declare class TeamsClient {
|
|
|
14
18
|
region?: TeamsRegion;
|
|
15
19
|
}): Promise<this>;
|
|
16
20
|
getRegion(): TeamsRegion;
|
|
21
|
+
getToken(): string;
|
|
22
|
+
getAccountType(): TeamsAccountType;
|
|
23
|
+
getIdToken(): Promise<string | null>;
|
|
17
24
|
private ensureAuth;
|
|
18
25
|
private isTokenExpired;
|
|
19
26
|
private getBucketKey;
|
|
@@ -27,14 +34,21 @@ export declare class TeamsClient {
|
|
|
27
34
|
private requestFormData;
|
|
28
35
|
testAuth(): Promise<TeamsUser>;
|
|
29
36
|
listTeams(): Promise<TeamsTeam[]>;
|
|
37
|
+
buildChannelTeamMap(): Promise<Map<string, string>>;
|
|
30
38
|
listChats(): Promise<TeamsChat[]>;
|
|
31
39
|
getChatMessages(chatId: string, limit?: number): Promise<TeamsMessage[]>;
|
|
32
40
|
sendChatMessage(chatId: string, content: string): Promise<TeamsMessage>;
|
|
33
41
|
getTeam(teamId: string): Promise<TeamsTeam>;
|
|
34
42
|
listChannels(teamId: string): Promise<TeamsChannel[]>;
|
|
35
43
|
getChannel(teamId: string, channelId: string): Promise<TeamsChannel>;
|
|
36
|
-
sendMessage(teamId: string, channelId: string, content: string): Promise<TeamsMessage>;
|
|
44
|
+
sendMessage(teamId: string, channelId: string, content: string, rootMessageId?: string): Promise<TeamsMessage>;
|
|
37
45
|
getMessages(teamId: string, channelId: string, limit?: number): Promise<TeamsMessage[]>;
|
|
46
|
+
getThreadReplies(teamId: string, channelId: string, rootMessageId: string, limit?: number): Promise<TeamsMessage[]>;
|
|
47
|
+
searchMessages(query: string, opts?: {
|
|
48
|
+
limit?: number;
|
|
49
|
+
from?: number;
|
|
50
|
+
}): Promise<TeamsSearchResult[]>;
|
|
51
|
+
private getTokenProvider;
|
|
38
52
|
getMessage(teamId: string, channelId: string, messageId: string): Promise<TeamsMessage>;
|
|
39
53
|
deleteMessage(teamId: string, channelId: string, messageId: string): Promise<void>;
|
|
40
54
|
addReaction(teamId: string, channelId: string, messageId: string, emoji: string): Promise<void>;
|
|
@@ -43,5 +57,9 @@ export declare class TeamsClient {
|
|
|
43
57
|
getUser(userId: string): Promise<TeamsUser>;
|
|
44
58
|
uploadFile(teamId: string, channelId: string, filePath: string): Promise<TeamsFile>;
|
|
45
59
|
listFiles(teamId: string, channelId: string): Promise<TeamsFile[]>;
|
|
60
|
+
downloadFile(teamId: string, channelId: string, fileId: string): Promise<{
|
|
61
|
+
buffer: Buffer;
|
|
62
|
+
file: TeamsFile;
|
|
63
|
+
}>;
|
|
46
64
|
}
|
|
47
65
|
//# sourceMappingURL=client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../../src/platforms/teams/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../../src/platforms/teams/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
1
2
|
import { readFile } from 'node:fs/promises';
|
|
2
3
|
import { basename } from 'node:path';
|
|
4
|
+
import { SUBSTRATE_SEARCH_URL } from './app-config.js';
|
|
3
5
|
import { TeamsCredentialManager } from './credential-manager.js';
|
|
6
|
+
import { TeamsTokenProvider } from './token-provider.js';
|
|
4
7
|
import { TeamsError } from './types.js';
|
|
5
8
|
const PERSONAL_MSG_API_BASE = 'https://msgapi.teams.live.com/v1';
|
|
6
9
|
const CSA_API_BASE = 'https://teams.microsoft.com/api';
|
|
@@ -8,6 +11,20 @@ const MAX_RETRIES = 3;
|
|
|
8
11
|
const BASE_BACKOFF_MS = 100;
|
|
9
12
|
const DEFAULT_REGION = 'amer';
|
|
10
13
|
const REGIONS = ['amer', 'emea', 'apac'];
|
|
14
|
+
const GRAPH_API_BASE = 'https://graph.microsoft.com/v1.0';
|
|
15
|
+
// Personal (Teams for Life) skypetokens carry a consumer `skypeid` (e.g.
|
|
16
|
+
// "live:..." or "8:live:..."); work/school tokens carry an org identity. Used
|
|
17
|
+
// only to guess the account type when a caller logs in with a bare token.
|
|
18
|
+
function isPersonalToken(token) {
|
|
19
|
+
try {
|
|
20
|
+
const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString('utf8'));
|
|
21
|
+
const skypeId = payload.skypeid ?? '';
|
|
22
|
+
return skypeId.includes('live:') || skypeId.startsWith('8:live:');
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
11
28
|
function stripHtml(content) {
|
|
12
29
|
if (content === undefined)
|
|
13
30
|
return undefined;
|
|
@@ -21,6 +38,162 @@ function stripHtml(content) {
|
|
|
21
38
|
.replace(/\s+/g, ' ')
|
|
22
39
|
.trim();
|
|
23
40
|
}
|
|
41
|
+
function isRecord(value) {
|
|
42
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
43
|
+
}
|
|
44
|
+
function stringFrom(record, keys) {
|
|
45
|
+
for (const key of keys) {
|
|
46
|
+
const value = record[key];
|
|
47
|
+
if (typeof value === 'string' && value.length > 0)
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
function recordFrom(record, keys) {
|
|
53
|
+
for (const key of keys) {
|
|
54
|
+
const value = record[key];
|
|
55
|
+
if (isRecord(value))
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
function arrayFrom(record, keys) {
|
|
61
|
+
for (const key of keys) {
|
|
62
|
+
const value = record[key];
|
|
63
|
+
if (Array.isArray(value))
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
function propertyValue(record, names) {
|
|
69
|
+
const properties = arrayFrom(record, ['Properties', 'properties']);
|
|
70
|
+
const normalized = names.map((name) => name.toLowerCase());
|
|
71
|
+
for (const property of properties) {
|
|
72
|
+
if (!isRecord(property))
|
|
73
|
+
continue;
|
|
74
|
+
const name = stringFrom(property, ['Name', 'name', 'Key', 'key']);
|
|
75
|
+
if (!name || !normalized.includes(name.toLowerCase()))
|
|
76
|
+
continue;
|
|
77
|
+
const value = property.Value ?? property.value;
|
|
78
|
+
if (typeof value === 'string' && value.length > 0)
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
function resultString(record, keys) {
|
|
84
|
+
return stringFrom(record, keys) ?? propertyValue(record, keys);
|
|
85
|
+
}
|
|
86
|
+
function parseSubstrateResult(value) {
|
|
87
|
+
if (!isRecord(value))
|
|
88
|
+
return null;
|
|
89
|
+
const author = recordFrom(value, ['Author', 'author', 'From', 'from']);
|
|
90
|
+
const id = resultString(value, ['id', 'Id', 'ReferenceId', 'MessageId']);
|
|
91
|
+
const channelId = resultString(value, ['channel_id', 'ChannelId', 'ConversationId', 'ThreadId']);
|
|
92
|
+
if (!id || !channelId)
|
|
93
|
+
return null;
|
|
94
|
+
return {
|
|
95
|
+
id,
|
|
96
|
+
content: stripHtml(resultString(value, ['content', 'Content', 'HitHighlightedSummary', 'Summary', 'Preview'])) ?? '',
|
|
97
|
+
author: {
|
|
98
|
+
id: author ? (stringFrom(author, ['id', 'Id', 'ObjectId']) ?? '') : (propertyValue(value, ['AuthorId']) ?? ''),
|
|
99
|
+
displayName: author
|
|
100
|
+
? (stringFrom(author, ['displayName', 'DisplayName', 'Name']) ?? 'Unknown')
|
|
101
|
+
: (propertyValue(value, ['AuthorDisplayName', 'Author']) ?? 'Unknown'),
|
|
102
|
+
},
|
|
103
|
+
channel_id: channelId,
|
|
104
|
+
thread_id: resultString(value, ['thread_id', 'ThreadId']),
|
|
105
|
+
team_name: resultString(value, ['team_name', 'TeamName']),
|
|
106
|
+
channel_name: resultString(value, ['channel_name', 'ChannelName']),
|
|
107
|
+
timestamp: resultString(value, ['timestamp', 'Timestamp', 'DateTimeSent', 'LastModifiedTime']) ?? '',
|
|
108
|
+
permalink: resultString(value, ['permalink', 'Permalink', 'WebUrl', 'Url']),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function parseSubstrateResults(data) {
|
|
112
|
+
if (!isRecord(data))
|
|
113
|
+
return [];
|
|
114
|
+
const results = [];
|
|
115
|
+
for (const entitySet of arrayFrom(data, ['EntitySets', 'entitySets'])) {
|
|
116
|
+
if (!isRecord(entitySet))
|
|
117
|
+
continue;
|
|
118
|
+
for (const resultSet of arrayFrom(entitySet, ['ResultSets', 'resultSets'])) {
|
|
119
|
+
if (!isRecord(resultSet))
|
|
120
|
+
continue;
|
|
121
|
+
for (const rawResult of arrayFrom(resultSet, ['Results', 'results'])) {
|
|
122
|
+
const result = parseSubstrateResult(rawResult);
|
|
123
|
+
if (result)
|
|
124
|
+
results.push(result);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return results;
|
|
129
|
+
}
|
|
130
|
+
function validateSearchLimit(value) {
|
|
131
|
+
if (value === undefined)
|
|
132
|
+
return 20;
|
|
133
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
134
|
+
throw new TeamsError('Search limit must be a positive integer.', 'invalid_pagination');
|
|
135
|
+
}
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
138
|
+
function validateSearchFrom(value) {
|
|
139
|
+
if (value === undefined)
|
|
140
|
+
return 0;
|
|
141
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
142
|
+
throw new TeamsError('Search from offset must be a non-negative integer.', 'invalid_pagination');
|
|
143
|
+
}
|
|
144
|
+
return value;
|
|
145
|
+
}
|
|
146
|
+
function isSharePointOrOneDriveUrl(url) {
|
|
147
|
+
try {
|
|
148
|
+
const { hostname } = new URL(url);
|
|
149
|
+
const normalizedHost = hostname.toLowerCase();
|
|
150
|
+
return (normalizedHost.includes('sharepoint.com') ||
|
|
151
|
+
normalizedHost.includes('-my.sharepoint') ||
|
|
152
|
+
normalizedHost === '1drv.ms' ||
|
|
153
|
+
normalizedHost.endsWith('.1drv.ms') ||
|
|
154
|
+
normalizedHost === 'onedrive.live.com' ||
|
|
155
|
+
normalizedHost.endsWith('.onedrive.live.com'));
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Only these hosts receive the Skype token on a raw download fetch. Teams file
|
|
162
|
+
// metadata can carry arbitrary URLs, so we never attach credentials to a host
|
|
163
|
+
// outside this allowlist — that would leak the token to a third party.
|
|
164
|
+
function isTrustedSkypeDownloadHost(url) {
|
|
165
|
+
try {
|
|
166
|
+
const host = new URL(url).hostname.toLowerCase();
|
|
167
|
+
return (host === 'teams.microsoft.com' ||
|
|
168
|
+
host.endsWith('.teams.microsoft.com') ||
|
|
169
|
+
host.endsWith('.asm.skype.com') ||
|
|
170
|
+
host.endsWith('.asyncgw.teams.microsoft.com') ||
|
|
171
|
+
host === 'substrate.office.com' ||
|
|
172
|
+
host.endsWith('.substrate.office.com'));
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function getFileDownloadSource(file) {
|
|
179
|
+
const shareUrl = [file.sharepoint_url, file.url, file.object_url].find((candidate) => Boolean(candidate && isSharePointOrOneDriveUrl(candidate)));
|
|
180
|
+
if (shareUrl)
|
|
181
|
+
return { route: 'graph', url: shareUrl };
|
|
182
|
+
const directUrl = file.object_url ?? file.url;
|
|
183
|
+
if (!directUrl) {
|
|
184
|
+
throw new TeamsError(`File has no downloadable URL: ${file.id}`, 'file_url_missing');
|
|
185
|
+
}
|
|
186
|
+
if (!isTrustedSkypeDownloadHost(directUrl)) {
|
|
187
|
+
throw new TeamsError(`Refusing to download ${file.id} from an untrusted host: ${directUrl}`, 'file_url_untrusted');
|
|
188
|
+
}
|
|
189
|
+
return { route: 'skype', url: directUrl };
|
|
190
|
+
}
|
|
191
|
+
async function readDownloadResponse(response, codePrefix) {
|
|
192
|
+
if (!response.ok) {
|
|
193
|
+
throw new TeamsError(`File download failed with HTTP ${response.status}`, `${codePrefix}_${response.status}`);
|
|
194
|
+
}
|
|
195
|
+
return Buffer.from(await response.arrayBuffer());
|
|
196
|
+
}
|
|
24
197
|
function escapeHtml(value) {
|
|
25
198
|
return value
|
|
26
199
|
.replaceAll('&', '&')
|
|
@@ -29,6 +202,19 @@ function escapeHtml(value) {
|
|
|
29
202
|
.replaceAll('"', '"')
|
|
30
203
|
.replaceAll("'", ''');
|
|
31
204
|
}
|
|
205
|
+
function withThreadMetadata(message, rootMessageId) {
|
|
206
|
+
const { rootMessageId: messageRootMessageId, parentMessageId, ...teamsMessage } = message;
|
|
207
|
+
const rawRootMessageId = rootMessageId ?? messageRootMessageId;
|
|
208
|
+
const isThreadReply = Boolean(rootMessageId ||
|
|
209
|
+
(messageRootMessageId !== undefined && messageRootMessageId !== message.id) ||
|
|
210
|
+
(parentMessageId !== undefined && parentMessageId !== message.id));
|
|
211
|
+
return {
|
|
212
|
+
...teamsMessage,
|
|
213
|
+
root_message_id: isThreadReply ? rawRootMessageId : undefined,
|
|
214
|
+
parent_message_id: isThreadReply ? (parentMessageId ?? rawRootMessageId) : undefined,
|
|
215
|
+
is_thread_reply: isThreadReply ? true : undefined,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
32
218
|
// groupId => Teams/channel thread (handled by listTeams). "48:notes"/
|
|
33
219
|
// streamofnotes => the user's self ("to me") chat. Anything else without a
|
|
34
220
|
// non-chat threadType is a normal 1:1 (no topic) or group (has topic) chat.
|
|
@@ -42,13 +228,18 @@ function classifyChat(id, tp) {
|
|
|
42
228
|
return tp?.topic ? 'group' : 'oneOnOne';
|
|
43
229
|
}
|
|
44
230
|
export class TeamsClient {
|
|
231
|
+
credManager;
|
|
45
232
|
token = null;
|
|
46
233
|
tokenExpiresAt;
|
|
47
234
|
isPersonalAccount = false;
|
|
48
235
|
region = DEFAULT_REGION;
|
|
49
236
|
regionDiscovered = false;
|
|
237
|
+
tokenProvider;
|
|
50
238
|
buckets = new Map();
|
|
51
239
|
globalRateLimitUntil = 0;
|
|
240
|
+
constructor(credManager = new TeamsCredentialManager()) {
|
|
241
|
+
this.credManager = credManager;
|
|
242
|
+
}
|
|
52
243
|
async login(credentials) {
|
|
53
244
|
if (credentials) {
|
|
54
245
|
if (!credentials.token) {
|
|
@@ -58,17 +249,21 @@ export class TeamsClient {
|
|
|
58
249
|
if (credentials.tokenExpiresAt) {
|
|
59
250
|
this.tokenExpiresAt = new Date(credentials.tokenExpiresAt);
|
|
60
251
|
}
|
|
61
|
-
this.isPersonalAccount = credentials.accountType
|
|
252
|
+
this.isPersonalAccount = credentials.accountType
|
|
253
|
+
? credentials.accountType === 'personal'
|
|
254
|
+
: isPersonalToken(credentials.token);
|
|
62
255
|
if (credentials.region) {
|
|
63
256
|
this.region = credentials.region;
|
|
64
257
|
this.regionDiscovered = true;
|
|
65
258
|
}
|
|
259
|
+
if (credentials.accountType) {
|
|
260
|
+
this.getTokenProvider().bindAccount(credentials.accountType);
|
|
261
|
+
}
|
|
66
262
|
return this;
|
|
67
263
|
}
|
|
68
264
|
const { ensureTeamsAuth } = await import('./ensure-auth.js');
|
|
69
265
|
await ensureTeamsAuth();
|
|
70
|
-
const
|
|
71
|
-
const creds = await credManager.getTokenWithExpiry();
|
|
266
|
+
const creds = await this.credManager.getTokenWithExpiry();
|
|
72
267
|
if (!creds) {
|
|
73
268
|
throw new TeamsError('No Teams credentials found. Make sure Microsoft Teams is logged in via the desktop app or a supported Chromium browser.', 'no_credentials');
|
|
74
269
|
}
|
|
@@ -82,6 +277,17 @@ export class TeamsClient {
|
|
|
82
277
|
getRegion() {
|
|
83
278
|
return this.region;
|
|
84
279
|
}
|
|
280
|
+
getToken() {
|
|
281
|
+
return this.ensureAuth();
|
|
282
|
+
}
|
|
283
|
+
getAccountType() {
|
|
284
|
+
return this.isPersonalAccount ? 'personal' : 'work';
|
|
285
|
+
}
|
|
286
|
+
async getIdToken() {
|
|
287
|
+
const { TeamsTokenExtractor } = await import('./token-extractor.js');
|
|
288
|
+
const extractor = new TeamsTokenExtractor();
|
|
289
|
+
return extractor.extractIdToken(this.getAccountType());
|
|
290
|
+
}
|
|
85
291
|
ensureAuth() {
|
|
86
292
|
if (this.token === null) {
|
|
87
293
|
throw new TeamsError('Not authenticated. Call .login() first.', 'not_authenticated');
|
|
@@ -162,7 +368,7 @@ export class TeamsClient {
|
|
|
162
368
|
}
|
|
163
369
|
async request(method, path, body, baseUrl) {
|
|
164
370
|
if (this.isTokenExpired()) {
|
|
165
|
-
throw new TeamsError('Token has expired. Run "auth extract" to refresh.', 'token_expired');
|
|
371
|
+
throw new TeamsError('Token has expired. Run "auth login" or "auth extract" to refresh.', 'token_expired');
|
|
166
372
|
}
|
|
167
373
|
if (baseUrl === undefined && !this.regionDiscovered) {
|
|
168
374
|
await this.discoverRegion();
|
|
@@ -209,7 +415,7 @@ export class TeamsClient {
|
|
|
209
415
|
}
|
|
210
416
|
async requestFormData(path, formData, baseUrl) {
|
|
211
417
|
if (this.isTokenExpired()) {
|
|
212
|
-
throw new TeamsError('Token has expired. Run "auth extract" to refresh.', 'token_expired');
|
|
418
|
+
throw new TeamsError('Token has expired. Run "auth login" or "auth extract" to refresh.', 'token_expired');
|
|
213
419
|
}
|
|
214
420
|
if (baseUrl === undefined && !this.regionDiscovered) {
|
|
215
421
|
await this.discoverRegion();
|
|
@@ -257,6 +463,22 @@ export class TeamsClient {
|
|
|
257
463
|
}
|
|
258
464
|
return Array.from(teamsMap.values());
|
|
259
465
|
}
|
|
466
|
+
// Realtime messages only carry a conversation id; a channel's parent teamId
|
|
467
|
+
// (== groupId) lives on the conversation, so the listener resolves it through
|
|
468
|
+
// this channelId -> teamId map.
|
|
469
|
+
async buildChannelTeamMap() {
|
|
470
|
+
const data = await this.request('GET', '/users/ME/conversations');
|
|
471
|
+
const channelToTeam = new Map();
|
|
472
|
+
for (const conv of data.conversations ?? []) {
|
|
473
|
+
const tp = conv.threadProperties;
|
|
474
|
+
if (!tp?.groupId)
|
|
475
|
+
continue;
|
|
476
|
+
if (!tp.productThreadType?.includes('Teams') && tp.threadType !== 'space')
|
|
477
|
+
continue;
|
|
478
|
+
channelToTeam.set(conv.id, tp.groupId);
|
|
479
|
+
}
|
|
480
|
+
return channelToTeam;
|
|
481
|
+
}
|
|
260
482
|
async listChats() {
|
|
261
483
|
const data = await this.request('GET', '/users/ME/conversations?view=msnp24Equivalent&pageSize=500');
|
|
262
484
|
const chats = [];
|
|
@@ -317,11 +539,62 @@ export class TeamsClient {
|
|
|
317
539
|
async getChannel(teamId, channelId) {
|
|
318
540
|
return this.request('GET', `/csa/api/v1/teams/${teamId}/channels/${channelId}`, undefined, CSA_API_BASE);
|
|
319
541
|
}
|
|
320
|
-
async sendMessage(teamId, channelId, content) {
|
|
542
|
+
async sendMessage(teamId, channelId, content, rootMessageId) {
|
|
543
|
+
if (rootMessageId) {
|
|
544
|
+
const response = await this.request('POST', `/csa/${this.region}/api/v2/teams/${teamId}/channels/${channelId}/messages/${rootMessageId}/replies`, { content, parentMessageId: rootMessageId }, CSA_API_BASE);
|
|
545
|
+
return withThreadMetadata(response, rootMessageId);
|
|
546
|
+
}
|
|
321
547
|
return this.request('POST', `/csa/${this.region}/api/v2/teams/${teamId}/channels/${channelId}/messages`, { content }, CSA_API_BASE);
|
|
322
548
|
}
|
|
323
549
|
async getMessages(teamId, channelId, limit = 50) {
|
|
324
|
-
|
|
550
|
+
const messages = await this.request('GET', `/csa/${this.region}/api/v2/teams/${teamId}/channels/${channelId}/messages?limit=${limit}`, undefined, CSA_API_BASE);
|
|
551
|
+
return messages.map((message) => withThreadMetadata(message));
|
|
552
|
+
}
|
|
553
|
+
async getThreadReplies(teamId, channelId, rootMessageId, limit = 50) {
|
|
554
|
+
const replies = await this.request('GET', `/csa/${this.region}/api/v2/teams/${teamId}/channels/${channelId}/messages/${rootMessageId}/replies?limit=${limit}`, undefined, CSA_API_BASE);
|
|
555
|
+
return replies.map((reply) => withThreadMetadata(reply, rootMessageId));
|
|
556
|
+
}
|
|
557
|
+
async searchMessages(query, opts = {}) {
|
|
558
|
+
const size = validateSearchLimit(opts.limit);
|
|
559
|
+
const from = validateSearchFrom(opts.from);
|
|
560
|
+
const tokenProvider = this.getTokenProvider();
|
|
561
|
+
const substrateToken = await tokenProvider.getSubstrateToken();
|
|
562
|
+
const tenantId = await tokenProvider.getTenantId();
|
|
563
|
+
const userId = await tokenProvider.getUserId();
|
|
564
|
+
const response = await fetch(SUBSTRATE_SEARCH_URL, {
|
|
565
|
+
method: 'POST',
|
|
566
|
+
headers: {
|
|
567
|
+
Authorization: `Bearer ${substrateToken}`,
|
|
568
|
+
'Content-Type': 'application/json',
|
|
569
|
+
'x-anchormailbox': `Oid:${userId}@${tenantId}`,
|
|
570
|
+
},
|
|
571
|
+
body: JSON.stringify({
|
|
572
|
+
cvid: randomUUID(),
|
|
573
|
+
logicalId: randomUUID(),
|
|
574
|
+
query: { queryString: query },
|
|
575
|
+
entityRequests: [
|
|
576
|
+
{
|
|
577
|
+
entityType: 'Message',
|
|
578
|
+
contentSources: ['Teams'],
|
|
579
|
+
from,
|
|
580
|
+
size,
|
|
581
|
+
query: { queryString: query },
|
|
582
|
+
},
|
|
583
|
+
],
|
|
584
|
+
}),
|
|
585
|
+
});
|
|
586
|
+
const data = (await response.json().catch(() => ({})));
|
|
587
|
+
if (!response.ok) {
|
|
588
|
+
const message = isRecord(data)
|
|
589
|
+
? stringFrom(data, ['message', 'Message', 'error_description', 'error'])
|
|
590
|
+
: undefined;
|
|
591
|
+
throw new TeamsError(message ?? `HTTP ${response.status}`, `substrate_${response.status}`);
|
|
592
|
+
}
|
|
593
|
+
return parseSubstrateResults(data);
|
|
594
|
+
}
|
|
595
|
+
getTokenProvider() {
|
|
596
|
+
this.tokenProvider ??= new TeamsTokenProvider(this.credManager);
|
|
597
|
+
return this.tokenProvider;
|
|
325
598
|
}
|
|
326
599
|
async getMessage(teamId, channelId, messageId) {
|
|
327
600
|
return this.request('GET', `/csa/${this.region}/api/v2/teams/${teamId}/channels/${channelId}/messages/${messageId}`, undefined, CSA_API_BASE);
|
|
@@ -351,5 +624,36 @@ export class TeamsClient {
|
|
|
351
624
|
async listFiles(teamId, channelId) {
|
|
352
625
|
return this.request('GET', `/csa/${this.region}/api/v2/teams/${teamId}/channels/${channelId}/files`, undefined, CSA_API_BASE);
|
|
353
626
|
}
|
|
627
|
+
async downloadFile(teamId, channelId, fileId) {
|
|
628
|
+
const files = await this.listFiles(teamId, channelId);
|
|
629
|
+
const file = files.find((candidate) => candidate.id === fileId);
|
|
630
|
+
if (!file) {
|
|
631
|
+
throw new TeamsError(`File not found: ${fileId}`, 'file_not_found');
|
|
632
|
+
}
|
|
633
|
+
const source = getFileDownloadSource(file);
|
|
634
|
+
if (source.route === 'graph') {
|
|
635
|
+
const graphToken = await new TeamsTokenProvider(this.credManager).getGraphToken();
|
|
636
|
+
const shareId = `u!${Buffer.from(source.url).toString('base64url').replace(/=+$/, '')}`;
|
|
637
|
+
const response = await fetch(`${GRAPH_API_BASE}/shares/${shareId}/driveItem/content`, {
|
|
638
|
+
headers: {
|
|
639
|
+
Authorization: `Bearer ${graphToken}`,
|
|
640
|
+
},
|
|
641
|
+
redirect: 'follow',
|
|
642
|
+
});
|
|
643
|
+
return { buffer: await readDownloadResponse(response, 'graph_download'), file };
|
|
644
|
+
}
|
|
645
|
+
if (this.isTokenExpired()) {
|
|
646
|
+
throw new TeamsError('Token has expired. Run "auth login" or "auth extract" to refresh.', 'token_expired');
|
|
647
|
+
}
|
|
648
|
+
const skypeToken = this.getToken();
|
|
649
|
+
const response = await fetch(source.url, {
|
|
650
|
+
headers: {
|
|
651
|
+
Authorization: `Bearer ${skypeToken}`,
|
|
652
|
+
'X-Skypetoken': skypeToken,
|
|
653
|
+
},
|
|
654
|
+
redirect: 'follow',
|
|
655
|
+
});
|
|
656
|
+
return { buffer: await readDownloadResponse(response, 'skype_download'), file };
|
|
657
|
+
}
|
|
354
658
|
}
|
|
355
659
|
//# sourceMappingURL=client.js.map
|