msteams-mcp 0.2.0
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/README.md +229 -0
- package/dist/__fixtures__/api-responses.d.ts +228 -0
- package/dist/__fixtures__/api-responses.js +217 -0
- package/dist/api/chatsvc-api.d.ts +171 -0
- package/dist/api/chatsvc-api.js +459 -0
- package/dist/api/csa-api.d.ts +44 -0
- package/dist/api/csa-api.js +148 -0
- package/dist/api/index.d.ts +6 -0
- package/dist/api/index.js +6 -0
- package/dist/api/substrate-api.d.ts +50 -0
- package/dist/api/substrate-api.js +305 -0
- package/dist/auth/crypto.d.ts +32 -0
- package/dist/auth/crypto.js +66 -0
- package/dist/auth/index.d.ts +6 -0
- package/dist/auth/index.js +6 -0
- package/dist/auth/session-store.d.ts +82 -0
- package/dist/auth/session-store.js +136 -0
- package/dist/auth/token-extractor.d.ts +69 -0
- package/dist/auth/token-extractor.js +330 -0
- package/dist/browser/auth.d.ts +43 -0
- package/dist/browser/auth.js +232 -0
- package/dist/browser/context.d.ts +40 -0
- package/dist/browser/context.js +121 -0
- package/dist/browser/session.d.ts +34 -0
- package/dist/browser/session.js +92 -0
- package/dist/constants.d.ts +54 -0
- package/dist/constants.js +72 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +12 -0
- package/dist/research/explore.d.ts +11 -0
- package/dist/research/explore.js +267 -0
- package/dist/research/search-research.d.ts +17 -0
- package/dist/research/search-research.js +317 -0
- package/dist/server.d.ts +64 -0
- package/dist/server.js +291 -0
- package/dist/teams/api-interceptor.d.ts +54 -0
- package/dist/teams/api-interceptor.js +391 -0
- package/dist/teams/direct-api.d.ts +321 -0
- package/dist/teams/direct-api.js +1305 -0
- package/dist/teams/messages.d.ts +14 -0
- package/dist/teams/messages.js +142 -0
- package/dist/teams/search.d.ts +40 -0
- package/dist/teams/search.js +458 -0
- package/dist/test/cli.d.ts +12 -0
- package/dist/test/cli.js +328 -0
- package/dist/test/debug-search.d.ts +10 -0
- package/dist/test/debug-search.js +147 -0
- package/dist/test/manual-test.d.ts +11 -0
- package/dist/test/manual-test.js +160 -0
- package/dist/test/mcp-harness.d.ts +17 -0
- package/dist/test/mcp-harness.js +427 -0
- package/dist/tools/auth-tools.d.ts +26 -0
- package/dist/tools/auth-tools.js +127 -0
- package/dist/tools/index.d.ts +45 -0
- package/dist/tools/index.js +12 -0
- package/dist/tools/message-tools.d.ts +139 -0
- package/dist/tools/message-tools.js +433 -0
- package/dist/tools/people-tools.d.ts +46 -0
- package/dist/tools/people-tools.js +123 -0
- package/dist/tools/registry.d.ts +23 -0
- package/dist/tools/registry.js +61 -0
- package/dist/tools/search-tools.d.ts +79 -0
- package/dist/tools/search-tools.js +168 -0
- package/dist/types/errors.d.ts +58 -0
- package/dist/types/errors.js +132 -0
- package/dist/types/result.d.ts +43 -0
- package/dist/types/result.js +51 -0
- package/dist/types/teams.d.ts +79 -0
- package/dist/types/teams.js +5 -0
- package/dist/utils/api-config.d.ts +66 -0
- package/dist/utils/api-config.js +113 -0
- package/dist/utils/auth-guards.d.ts +29 -0
- package/dist/utils/auth-guards.js +54 -0
- package/dist/utils/http.d.ts +29 -0
- package/dist/utils/http.js +111 -0
- package/dist/utils/parsers.d.ts +187 -0
- package/dist/utils/parsers.js +574 -0
- package/dist/utils/parsers.test.d.ts +7 -0
- package/dist/utils/parsers.test.js +360 -0
- package/package.json +58 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Network request interception for Teams API calls.
|
|
3
|
+
* Captures search requests and responses for direct API usage.
|
|
4
|
+
*/
|
|
5
|
+
import { stripHtml, parseV2Result } from '../utils/parsers.js';
|
|
6
|
+
// Patterns that indicate search-related API calls
|
|
7
|
+
const SEARCH_API_PATTERNS = [
|
|
8
|
+
/substrate\.office\.com\/searchservice\/api\/v2\/query/i, // v2 query (full search)
|
|
9
|
+
/substrate\.office\.com\/search\/api\/v1\/suggestions/i, // v1 suggestions (typeahead)
|
|
10
|
+
/substrate\.office\.com\/search/i, // Other substrate search
|
|
11
|
+
/\/api\/csa\/.*\/containers\/.*\/posts/i, // Channel posts
|
|
12
|
+
/\/api\/chatsvc\/.*\/messages/i, // Chat messages
|
|
13
|
+
];
|
|
14
|
+
function isSearchApiUrl(url) {
|
|
15
|
+
return SEARCH_API_PATTERNS.some(pattern => pattern.test(url));
|
|
16
|
+
}
|
|
17
|
+
// stripHtml imported from ../utils/parsers.js
|
|
18
|
+
/**
|
|
19
|
+
* Parses Substrate search API response.
|
|
20
|
+
* Handles both suggestions endpoint and full query endpoint.
|
|
21
|
+
*/
|
|
22
|
+
function parseSubstrateResponse(body) {
|
|
23
|
+
if (!body || typeof body !== 'object')
|
|
24
|
+
return [];
|
|
25
|
+
const results = [];
|
|
26
|
+
const obj = body;
|
|
27
|
+
// Try Groups[].Suggestions[] structure (suggestions endpoint)
|
|
28
|
+
const groups = obj.Groups;
|
|
29
|
+
if (Array.isArray(groups)) {
|
|
30
|
+
for (const group of groups) {
|
|
31
|
+
const g = group;
|
|
32
|
+
const suggestions = g.Suggestions;
|
|
33
|
+
const entityType = g.Type;
|
|
34
|
+
if (Array.isArray(suggestions)) {
|
|
35
|
+
for (const suggestion of suggestions) {
|
|
36
|
+
const s = suggestion;
|
|
37
|
+
// Skip people suggestions, we want messages
|
|
38
|
+
if (entityType === 'People')
|
|
39
|
+
continue;
|
|
40
|
+
const result = parseSubstrateItem(s);
|
|
41
|
+
if (result)
|
|
42
|
+
results.push(result);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Try EntitySets[].ResultSets[].Results[] structure (v2 query endpoint)
|
|
48
|
+
// Delegate to parsers.ts parseV2Result for consistency
|
|
49
|
+
const entitySets = obj.EntitySets;
|
|
50
|
+
if (Array.isArray(entitySets)) {
|
|
51
|
+
for (const entitySet of entitySets) {
|
|
52
|
+
const es = entitySet;
|
|
53
|
+
const resultSets = es.ResultSets;
|
|
54
|
+
if (Array.isArray(resultSets)) {
|
|
55
|
+
for (const resultSet of resultSets) {
|
|
56
|
+
const rs = resultSet;
|
|
57
|
+
const items = rs.Results;
|
|
58
|
+
if (Array.isArray(items)) {
|
|
59
|
+
for (const item of items) {
|
|
60
|
+
const result = parseV2Result(item);
|
|
61
|
+
if (result)
|
|
62
|
+
results.push(result);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Try value[] array (common Microsoft API pattern)
|
|
70
|
+
const value = obj.value;
|
|
71
|
+
if (Array.isArray(value)) {
|
|
72
|
+
for (const item of value) {
|
|
73
|
+
const result = parseSubstrateItem(item);
|
|
74
|
+
if (result)
|
|
75
|
+
results.push(result);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return results;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Extracts pagination metadata from v2 query response.
|
|
82
|
+
*/
|
|
83
|
+
function extractV2Pagination(body) {
|
|
84
|
+
if (!body || typeof body !== 'object')
|
|
85
|
+
return { returned: 0 };
|
|
86
|
+
const obj = body;
|
|
87
|
+
let returned = 0;
|
|
88
|
+
let total;
|
|
89
|
+
const entitySets = obj.EntitySets;
|
|
90
|
+
if (Array.isArray(entitySets)) {
|
|
91
|
+
for (const entitySet of entitySets) {
|
|
92
|
+
const es = entitySet;
|
|
93
|
+
// Try to get total from EntitySet
|
|
94
|
+
const esTotal = es.Total ?? es.TotalCount ?? es.Count;
|
|
95
|
+
if (typeof esTotal === 'number') {
|
|
96
|
+
total = esTotal;
|
|
97
|
+
}
|
|
98
|
+
const resultSets = es.ResultSets;
|
|
99
|
+
if (Array.isArray(resultSets)) {
|
|
100
|
+
for (const resultSet of resultSets) {
|
|
101
|
+
const rs = resultSet;
|
|
102
|
+
// Try to get total from ResultSet
|
|
103
|
+
const rsTotal = rs.Total ?? rs.TotalCount ?? rs.TotalEstimate;
|
|
104
|
+
if (typeof rsTotal === 'number' && total === undefined) {
|
|
105
|
+
total = rsTotal;
|
|
106
|
+
}
|
|
107
|
+
const items = rs.Results;
|
|
108
|
+
if (Array.isArray(items)) {
|
|
109
|
+
returned += items.length;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return { total, returned };
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Parses a single Substrate search result item (suggestions endpoint).
|
|
119
|
+
*/
|
|
120
|
+
function parseSubstrateItem(item) {
|
|
121
|
+
// Try to extract content from various possible locations
|
|
122
|
+
const content = getStringField(item, 'Summary') ||
|
|
123
|
+
getStringField(item, 'Body') ||
|
|
124
|
+
getStringField(item, 'Content') ||
|
|
125
|
+
getStringField(item, 'Snippet') ||
|
|
126
|
+
getNestedContent(item, 'HitHighlightedSummary') ||
|
|
127
|
+
getNestedContent(item, 'Preview') ||
|
|
128
|
+
'';
|
|
129
|
+
// Skip if no meaningful content
|
|
130
|
+
if (content.length < 5)
|
|
131
|
+
return null;
|
|
132
|
+
const id = getStringField(item, 'Id') ||
|
|
133
|
+
getStringField(item, 'DocId') ||
|
|
134
|
+
getStringField(item, 'ResourceId') ||
|
|
135
|
+
`substrate-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
136
|
+
const sender = getStringField(item, 'From') ||
|
|
137
|
+
getStringField(item, 'Author') ||
|
|
138
|
+
getStringField(item, 'DisplayName') ||
|
|
139
|
+
getNestedName(item, 'Sender') ||
|
|
140
|
+
getNestedName(item, 'From');
|
|
141
|
+
const timestamp = getStringField(item, 'ReceivedTime') ||
|
|
142
|
+
getStringField(item, 'CreatedDateTime') ||
|
|
143
|
+
getStringField(item, 'LastModifiedTime') ||
|
|
144
|
+
getStringField(item, 'SentDateTime');
|
|
145
|
+
const channelName = getStringField(item, 'ChannelName') ||
|
|
146
|
+
getStringField(item, 'Topic');
|
|
147
|
+
const teamName = getStringField(item, 'TeamName') ||
|
|
148
|
+
getStringField(item, 'GroupName');
|
|
149
|
+
return {
|
|
150
|
+
id,
|
|
151
|
+
type: 'message',
|
|
152
|
+
content: stripHtml(content),
|
|
153
|
+
sender,
|
|
154
|
+
timestamp,
|
|
155
|
+
channelName,
|
|
156
|
+
teamName,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
// Note: parseV2QueryItem functionality is now delegated to parseV2Result in parsers.ts
|
|
160
|
+
/**
|
|
161
|
+
* Parses Teams posts API response.
|
|
162
|
+
*/
|
|
163
|
+
function parsePostsResponse(body) {
|
|
164
|
+
if (!body || typeof body !== 'object')
|
|
165
|
+
return [];
|
|
166
|
+
const results = [];
|
|
167
|
+
const obj = body;
|
|
168
|
+
const posts = obj.posts;
|
|
169
|
+
if (!Array.isArray(posts))
|
|
170
|
+
return [];
|
|
171
|
+
for (const post of posts) {
|
|
172
|
+
const p = post;
|
|
173
|
+
const message = p.message;
|
|
174
|
+
if (!message)
|
|
175
|
+
continue;
|
|
176
|
+
const content = getStringField(message, 'content') || '';
|
|
177
|
+
if (content.length < 5)
|
|
178
|
+
continue;
|
|
179
|
+
const id = getStringField(p, 'id') || `post-${Date.now()}`;
|
|
180
|
+
const sender = [
|
|
181
|
+
getStringField(message, 'fromGivenNameInToken'),
|
|
182
|
+
getStringField(message, 'fromFamilyNameInToken'),
|
|
183
|
+
].filter(Boolean).join(' ') || getStringField(message, 'imdisplayname');
|
|
184
|
+
const timestamp = getStringField(p, 'latestMessageTime') || getStringField(p, 'originalArrivalTime');
|
|
185
|
+
results.push({
|
|
186
|
+
id,
|
|
187
|
+
type: 'message',
|
|
188
|
+
content: stripHtml(content),
|
|
189
|
+
sender,
|
|
190
|
+
timestamp,
|
|
191
|
+
conversationId: getStringField(p, 'containerId'),
|
|
192
|
+
messageId: id,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return results;
|
|
196
|
+
}
|
|
197
|
+
function getStringField(obj, field) {
|
|
198
|
+
const value = obj[field];
|
|
199
|
+
return typeof value === 'string' ? value : undefined;
|
|
200
|
+
}
|
|
201
|
+
function getNestedContent(obj, field) {
|
|
202
|
+
const value = obj[field];
|
|
203
|
+
if (typeof value === 'string')
|
|
204
|
+
return value;
|
|
205
|
+
if (value && typeof value === 'object') {
|
|
206
|
+
const nested = value;
|
|
207
|
+
return getStringField(nested, 'Content') || getStringField(nested, 'Text');
|
|
208
|
+
}
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
function getNestedName(obj, field) {
|
|
212
|
+
const value = obj[field];
|
|
213
|
+
if (typeof value === 'string')
|
|
214
|
+
return value;
|
|
215
|
+
if (value && typeof value === 'object') {
|
|
216
|
+
const nested = value;
|
|
217
|
+
return (getStringField(nested, 'DisplayName') ||
|
|
218
|
+
getStringField(nested, 'Name') ||
|
|
219
|
+
getStringField(nested, 'EmailAddress'));
|
|
220
|
+
}
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Parses a captured response based on its URL pattern.
|
|
225
|
+
*/
|
|
226
|
+
export function parseSearchResults(response) {
|
|
227
|
+
if (!response.body || response.status >= 400)
|
|
228
|
+
return [];
|
|
229
|
+
const url = response.url.toLowerCase();
|
|
230
|
+
if (url.includes('substrate.office.com')) {
|
|
231
|
+
return parseSubstrateResponse(response.body);
|
|
232
|
+
}
|
|
233
|
+
if (url.includes('/posts') || url.includes('/csa/')) {
|
|
234
|
+
return parsePostsResponse(response.body);
|
|
235
|
+
}
|
|
236
|
+
// Try generic parsing
|
|
237
|
+
return parseSubstrateResponse(response.body);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Parses a captured response and returns results with pagination info.
|
|
241
|
+
*/
|
|
242
|
+
export function parseSearchResultsWithPagination(response, requestedFrom = 0, requestedSize = 25) {
|
|
243
|
+
const results = parseSearchResults(response);
|
|
244
|
+
// Extract pagination from v2 query response
|
|
245
|
+
let pagination = {
|
|
246
|
+
returned: results.length,
|
|
247
|
+
from: requestedFrom,
|
|
248
|
+
size: requestedSize,
|
|
249
|
+
hasMore: results.length >= requestedSize,
|
|
250
|
+
};
|
|
251
|
+
if (response.url.includes('searchservice/api/v2/query')) {
|
|
252
|
+
const v2Pagination = extractV2Pagination(response.body);
|
|
253
|
+
pagination = {
|
|
254
|
+
returned: v2Pagination.returned,
|
|
255
|
+
from: requestedFrom,
|
|
256
|
+
size: requestedSize,
|
|
257
|
+
total: v2Pagination.total,
|
|
258
|
+
hasMore: v2Pagination.total !== undefined
|
|
259
|
+
? requestedFrom + v2Pagination.returned < v2Pagination.total
|
|
260
|
+
: v2Pagination.returned >= requestedSize,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
return { results, pagination };
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Sets up request/response interception on a page.
|
|
267
|
+
* Returns an interceptor with a promise-based wait for results.
|
|
268
|
+
*/
|
|
269
|
+
export function setupApiInterceptor(page, debug = false) {
|
|
270
|
+
const responses = [];
|
|
271
|
+
let resolveWait = null;
|
|
272
|
+
let stopped = false;
|
|
273
|
+
const responseHandler = async (response) => {
|
|
274
|
+
if (stopped)
|
|
275
|
+
return;
|
|
276
|
+
const url = response.url();
|
|
277
|
+
if (!isSearchApiUrl(url))
|
|
278
|
+
return;
|
|
279
|
+
if (debug) {
|
|
280
|
+
console.log(` [api] Captured response: ${url.substring(0, 80)}...`);
|
|
281
|
+
}
|
|
282
|
+
let body;
|
|
283
|
+
try {
|
|
284
|
+
const contentType = response.headers()['content-type'] || '';
|
|
285
|
+
if (contentType.includes('application/json')) {
|
|
286
|
+
body = await response.json();
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
// Response body may not be available
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const captured = {
|
|
294
|
+
url,
|
|
295
|
+
status: response.status(),
|
|
296
|
+
body,
|
|
297
|
+
};
|
|
298
|
+
responses.push(captured);
|
|
299
|
+
// Parse results and resolve if we got something
|
|
300
|
+
const results = parseSearchResults(captured);
|
|
301
|
+
if (results.length > 0 && resolveWait) {
|
|
302
|
+
if (debug) {
|
|
303
|
+
console.log(` [api] Parsed ${results.length} results from ${url.substring(0, 60)}...`);
|
|
304
|
+
}
|
|
305
|
+
resolveWait(results);
|
|
306
|
+
resolveWait = null;
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
page.on('response', responseHandler);
|
|
310
|
+
return {
|
|
311
|
+
responses,
|
|
312
|
+
waitForSearchResults(timeoutMs = 10000) {
|
|
313
|
+
return new Promise((resolve) => {
|
|
314
|
+
// Check if we already have results
|
|
315
|
+
for (const resp of responses) {
|
|
316
|
+
const results = parseSearchResults(resp);
|
|
317
|
+
if (results.length > 0) {
|
|
318
|
+
resolve(results);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// Wait for new results
|
|
323
|
+
resolveWait = resolve;
|
|
324
|
+
// Timeout fallback
|
|
325
|
+
setTimeout(() => {
|
|
326
|
+
if (resolveWait) {
|
|
327
|
+
resolveWait = null;
|
|
328
|
+
// Try to get any results we have
|
|
329
|
+
for (const resp of responses) {
|
|
330
|
+
const results = parseSearchResults(resp);
|
|
331
|
+
if (results.length > 0) {
|
|
332
|
+
resolve(results);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
resolve([]);
|
|
337
|
+
}
|
|
338
|
+
}, timeoutMs);
|
|
339
|
+
});
|
|
340
|
+
},
|
|
341
|
+
waitForSearchResultsWithPagination(timeoutMs = 10000) {
|
|
342
|
+
return new Promise((resolve) => {
|
|
343
|
+
const defaultPagination = {
|
|
344
|
+
returned: 0,
|
|
345
|
+
from: 0,
|
|
346
|
+
size: 25,
|
|
347
|
+
hasMore: false,
|
|
348
|
+
};
|
|
349
|
+
// Check if we already have results
|
|
350
|
+
for (const resp of responses) {
|
|
351
|
+
const parsed = parseSearchResultsWithPagination(resp);
|
|
352
|
+
if (parsed.results.length > 0) {
|
|
353
|
+
resolve(parsed);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// Wait for new results
|
|
358
|
+
const resolveWithPagination = (results) => {
|
|
359
|
+
// Find the response that gave us these results
|
|
360
|
+
for (const resp of responses) {
|
|
361
|
+
const parsed = parseSearchResultsWithPagination(resp);
|
|
362
|
+
if (parsed.results.length > 0) {
|
|
363
|
+
resolve(parsed);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
resolve({ results, pagination: { ...defaultPagination, returned: results.length } });
|
|
368
|
+
};
|
|
369
|
+
resolveWait = resolveWithPagination;
|
|
370
|
+
// Timeout fallback
|
|
371
|
+
setTimeout(() => {
|
|
372
|
+
if (resolveWait) {
|
|
373
|
+
resolveWait = null;
|
|
374
|
+
for (const resp of responses) {
|
|
375
|
+
const parsed = parseSearchResultsWithPagination(resp);
|
|
376
|
+
if (parsed.results.length > 0) {
|
|
377
|
+
resolve(parsed);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
resolve({ results: [], pagination: defaultPagination });
|
|
382
|
+
}
|
|
383
|
+
}, timeoutMs);
|
|
384
|
+
});
|
|
385
|
+
},
|
|
386
|
+
stop() {
|
|
387
|
+
stopped = true;
|
|
388
|
+
page.off('response', responseHandler);
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
}
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct API client for Teams/Substrate search.
|
|
3
|
+
*
|
|
4
|
+
* Extracts auth tokens from browser session state and makes
|
|
5
|
+
* direct HTTP calls without needing an active browser.
|
|
6
|
+
*/
|
|
7
|
+
import type { TeamsSearchResult, SearchPaginationResult } from '../types/teams.js';
|
|
8
|
+
import { buildMessageLink, type PersonSearchResult, type UserProfile } from '../utils/parsers.js';
|
|
9
|
+
interface TeamsTokenInfo {
|
|
10
|
+
token: string;
|
|
11
|
+
expiry: Date;
|
|
12
|
+
userMri: string;
|
|
13
|
+
}
|
|
14
|
+
interface MessageAuthInfo {
|
|
15
|
+
skypeToken: string;
|
|
16
|
+
authToken: string;
|
|
17
|
+
userMri: string;
|
|
18
|
+
}
|
|
19
|
+
interface DirectSearchResult {
|
|
20
|
+
results: TeamsSearchResult[];
|
|
21
|
+
pagination: SearchPaginationResult;
|
|
22
|
+
}
|
|
23
|
+
export type { UserProfile } from '../utils/parsers.js';
|
|
24
|
+
/**
|
|
25
|
+
* Gets the current user's profile from cached JWT tokens.
|
|
26
|
+
*
|
|
27
|
+
* Extracts user info from MSAL tokens stored in session state.
|
|
28
|
+
* No API call needed - just parses existing tokens.
|
|
29
|
+
*/
|
|
30
|
+
export declare function getMe(): UserProfile | null;
|
|
31
|
+
/**
|
|
32
|
+
* Extracts the Substrate search token from session state.
|
|
33
|
+
*/
|
|
34
|
+
export declare function extractSubstrateToken(): {
|
|
35
|
+
token: string;
|
|
36
|
+
expiry: Date;
|
|
37
|
+
} | null;
|
|
38
|
+
/**
|
|
39
|
+
* Gets a valid token, either from cache or by extracting from session.
|
|
40
|
+
*/
|
|
41
|
+
export declare function getValidToken(): string | null;
|
|
42
|
+
/**
|
|
43
|
+
* Clears the token cache (forces re-extraction on next call).
|
|
44
|
+
*/
|
|
45
|
+
export declare function clearTokenCache(): void;
|
|
46
|
+
/**
|
|
47
|
+
* Checks if we have a valid token for direct API calls.
|
|
48
|
+
*/
|
|
49
|
+
export declare function hasValidToken(): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Gets token status for diagnostics.
|
|
52
|
+
*/
|
|
53
|
+
export declare function getTokenStatus(): {
|
|
54
|
+
hasToken: boolean;
|
|
55
|
+
expiresAt?: string;
|
|
56
|
+
minutesRemaining?: number;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Makes a direct search API call to Substrate.
|
|
60
|
+
*/
|
|
61
|
+
export declare function directSearch(query: string, options?: {
|
|
62
|
+
from?: number;
|
|
63
|
+
size?: number;
|
|
64
|
+
maxResults?: number;
|
|
65
|
+
}): Promise<DirectSearchResult>;
|
|
66
|
+
/**
|
|
67
|
+
* Extracts the Teams API token and user MRI from session state.
|
|
68
|
+
* This is different from the Substrate token - it's used for chat APIs.
|
|
69
|
+
*
|
|
70
|
+
* The chat API requires a token with audience:
|
|
71
|
+
* - https://chatsvcagg.teams.microsoft.com (preferred)
|
|
72
|
+
* - https://api.spaces.skype.com (fallback)
|
|
73
|
+
*/
|
|
74
|
+
export declare function extractTeamsToken(): TeamsTokenInfo | null;
|
|
75
|
+
/**
|
|
76
|
+
* Extracts authentication info needed for sending messages.
|
|
77
|
+
* Uses cookies (skypetoken_asm) which are required for the chatsvc API.
|
|
78
|
+
*/
|
|
79
|
+
export declare function extractMessageAuth(): MessageAuthInfo | null;
|
|
80
|
+
/**
|
|
81
|
+
* Extracts the CSA (Chat Service Aggregator) token for the conversationFolders API.
|
|
82
|
+
* This token is different from the chatsvc token and is required for favorites operations.
|
|
83
|
+
*/
|
|
84
|
+
export declare function extractCsaToken(): string | null;
|
|
85
|
+
/**
|
|
86
|
+
* Gets user's display name from session state.
|
|
87
|
+
*/
|
|
88
|
+
export declare function getUserDisplayName(): string | null;
|
|
89
|
+
export interface SendMessageResult {
|
|
90
|
+
success: boolean;
|
|
91
|
+
messageId?: string;
|
|
92
|
+
timestamp?: number;
|
|
93
|
+
error?: string;
|
|
94
|
+
}
|
|
95
|
+
export type { PersonSearchResult } from '../utils/parsers.js';
|
|
96
|
+
/** Search people results with count. */
|
|
97
|
+
export interface PeopleSearchResults {
|
|
98
|
+
results: PersonSearchResult[];
|
|
99
|
+
returned: number;
|
|
100
|
+
}
|
|
101
|
+
/** Frequent contacts result. */
|
|
102
|
+
export interface FrequentContactsResult {
|
|
103
|
+
contacts: PersonSearchResult[];
|
|
104
|
+
returned: number;
|
|
105
|
+
}
|
|
106
|
+
/** A favourite/pinned conversation item. */
|
|
107
|
+
export interface FavoriteItem {
|
|
108
|
+
conversationId: string;
|
|
109
|
+
displayName?: string;
|
|
110
|
+
conversationType?: string;
|
|
111
|
+
createdTime?: number;
|
|
112
|
+
lastUpdatedTime?: number;
|
|
113
|
+
}
|
|
114
|
+
/** Response from getting favorites. */
|
|
115
|
+
export interface FavoritesResult {
|
|
116
|
+
success: boolean;
|
|
117
|
+
favorites: FavoriteItem[];
|
|
118
|
+
folderHierarchyVersion?: number;
|
|
119
|
+
folderId?: string;
|
|
120
|
+
error?: string;
|
|
121
|
+
}
|
|
122
|
+
/** Result of modifying favorites. */
|
|
123
|
+
export interface FavoriteModifyResult {
|
|
124
|
+
success: boolean;
|
|
125
|
+
error?: string;
|
|
126
|
+
}
|
|
127
|
+
/** Result of saving/unsaving a message. */
|
|
128
|
+
export interface SaveMessageResult {
|
|
129
|
+
success: boolean;
|
|
130
|
+
conversationId?: string;
|
|
131
|
+
messageId?: string;
|
|
132
|
+
saved?: boolean;
|
|
133
|
+
error?: string;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Sends a message to a Teams conversation.
|
|
137
|
+
*
|
|
138
|
+
* Uses the skypetoken_asm cookie for authentication, which is required
|
|
139
|
+
* by the Teams chatsvc API.
|
|
140
|
+
*
|
|
141
|
+
* @param conversationId - The conversation ID (e.g., "48:notes" for self-chat)
|
|
142
|
+
* @param content - Message content (HTML supported)
|
|
143
|
+
* @param region - Region for the API (default: "amer")
|
|
144
|
+
*/
|
|
145
|
+
export declare function sendMessage(conversationId: string, content: string, region?: string): Promise<SendMessageResult>;
|
|
146
|
+
/**
|
|
147
|
+
* Sends a message to your own notes/self-chat.
|
|
148
|
+
*/
|
|
149
|
+
export declare function sendNoteToSelf(content: string): Promise<SendMessageResult>;
|
|
150
|
+
/**
|
|
151
|
+
* Searches for people by name or email using the Substrate suggestions API.
|
|
152
|
+
*
|
|
153
|
+
* Uses the same auth token as message search.
|
|
154
|
+
*
|
|
155
|
+
* @param query - Search term (name, email, or partial match)
|
|
156
|
+
* @param limit - Maximum number of results (default: 10)
|
|
157
|
+
*/
|
|
158
|
+
export declare function searchPeople(query: string, limit?: number): Promise<PeopleSearchResults>;
|
|
159
|
+
/**
|
|
160
|
+
* Gets the user's frequently contacted people.
|
|
161
|
+
*
|
|
162
|
+
* Uses the peoplecache scenario which returns contacts ranked by
|
|
163
|
+
* interaction frequency. Useful for resolving ambiguous names
|
|
164
|
+
* (e.g., "Rob" → "Rob Smith <rob.smith@company.com>").
|
|
165
|
+
*
|
|
166
|
+
* @param limit - Maximum number of contacts to return (default: 50)
|
|
167
|
+
*/
|
|
168
|
+
export declare function getFrequentContacts(limit?: number): Promise<FrequentContactsResult>;
|
|
169
|
+
/**
|
|
170
|
+
* Gets the user's favourite/pinned conversations.
|
|
171
|
+
*
|
|
172
|
+
* Uses the conversationFolders API with CSA token authentication.
|
|
173
|
+
* Requires both the skypetoken (from cookies) and the CSA token (from MSAL).
|
|
174
|
+
*
|
|
175
|
+
* @param region - Region for the API (default: "amer")
|
|
176
|
+
*/
|
|
177
|
+
export declare function getFavorites(region?: string): Promise<FavoritesResult>;
|
|
178
|
+
/**
|
|
179
|
+
* Adds a conversation to the user's favourites.
|
|
180
|
+
*
|
|
181
|
+
* @param conversationId - The conversation ID to add
|
|
182
|
+
* @param region - Region for the API (default: "amer")
|
|
183
|
+
*/
|
|
184
|
+
export declare function addFavorite(conversationId: string, region?: string): Promise<FavoriteModifyResult>;
|
|
185
|
+
/**
|
|
186
|
+
* Removes a conversation from the user's favourites.
|
|
187
|
+
*
|
|
188
|
+
* @param conversationId - The conversation ID to remove
|
|
189
|
+
* @param region - Region for the API (default: "amer")
|
|
190
|
+
*/
|
|
191
|
+
export declare function removeFavorite(conversationId: string, region?: string): Promise<FavoriteModifyResult>;
|
|
192
|
+
/**
|
|
193
|
+
* Saves (bookmarks) a message.
|
|
194
|
+
*
|
|
195
|
+
* @param conversationId - The conversation ID containing the message
|
|
196
|
+
* @param messageId - The message ID to save (numeric string)
|
|
197
|
+
* @param region - Region for the API (default: "amer")
|
|
198
|
+
*/
|
|
199
|
+
export declare function saveMessage(conversationId: string, messageId: string, region?: string): Promise<SaveMessageResult>;
|
|
200
|
+
/**
|
|
201
|
+
* Unsaves (removes bookmark from) a message.
|
|
202
|
+
*
|
|
203
|
+
* @param conversationId - The conversation ID containing the message
|
|
204
|
+
* @param messageId - The message ID to unsave (numeric string)
|
|
205
|
+
* @param region - Region for the API (default: "amer")
|
|
206
|
+
*/
|
|
207
|
+
export declare function unsaveMessage(conversationId: string, messageId: string, region?: string): Promise<SaveMessageResult>;
|
|
208
|
+
/** A message from a thread/conversation. */
|
|
209
|
+
export interface ThreadMessage {
|
|
210
|
+
id: string;
|
|
211
|
+
content: string;
|
|
212
|
+
contentType: string;
|
|
213
|
+
sender: {
|
|
214
|
+
mri: string;
|
|
215
|
+
displayName?: string;
|
|
216
|
+
};
|
|
217
|
+
timestamp: string;
|
|
218
|
+
conversationId: string;
|
|
219
|
+
clientMessageId?: string;
|
|
220
|
+
isFromMe?: boolean;
|
|
221
|
+
messageLink?: string;
|
|
222
|
+
}
|
|
223
|
+
/** Result of getting thread messages. */
|
|
224
|
+
export interface GetThreadResult {
|
|
225
|
+
success: boolean;
|
|
226
|
+
conversationId?: string;
|
|
227
|
+
messages?: ThreadMessage[];
|
|
228
|
+
error?: string;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Gets messages from a Teams conversation/thread.
|
|
232
|
+
*
|
|
233
|
+
* This retrieves messages from a conversation, which can be:
|
|
234
|
+
* - A 1:1 or group chat
|
|
235
|
+
* - A channel thread
|
|
236
|
+
* - Self-notes (48:notes)
|
|
237
|
+
*
|
|
238
|
+
* @param conversationId - The conversation ID (e.g., "19:abc@thread.tacv2")
|
|
239
|
+
* @param options - Optional parameters for pagination
|
|
240
|
+
* @param options.limit - Maximum number of messages to return (default: 50)
|
|
241
|
+
* @param options.startTime - Only get messages after this timestamp (epoch ms)
|
|
242
|
+
* @param region - Region for the API (default: "amer")
|
|
243
|
+
*/
|
|
244
|
+
export declare function getThreadMessages(conversationId: string, options?: {
|
|
245
|
+
limit?: number;
|
|
246
|
+
startTime?: number;
|
|
247
|
+
}, region?: string): Promise<GetThreadResult>;
|
|
248
|
+
export { buildMessageLink };
|
|
249
|
+
/** A channel within a team. */
|
|
250
|
+
export interface TeamChannel {
|
|
251
|
+
id: string;
|
|
252
|
+
displayName: string;
|
|
253
|
+
description?: string;
|
|
254
|
+
isFavorite?: boolean;
|
|
255
|
+
membershipType?: string;
|
|
256
|
+
}
|
|
257
|
+
/** A team the user is a member of. */
|
|
258
|
+
export interface Team {
|
|
259
|
+
id: string;
|
|
260
|
+
displayName: string;
|
|
261
|
+
description?: string;
|
|
262
|
+
pictureETag?: string;
|
|
263
|
+
isFavorite?: boolean;
|
|
264
|
+
channels: TeamChannel[];
|
|
265
|
+
}
|
|
266
|
+
/** Result of getting joined teams. */
|
|
267
|
+
export interface JoinedTeamsResult {
|
|
268
|
+
success: boolean;
|
|
269
|
+
teams?: Team[];
|
|
270
|
+
error?: string;
|
|
271
|
+
}
|
|
272
|
+
/** A channel with its parent team info. */
|
|
273
|
+
export interface ChannelWithTeam {
|
|
274
|
+
channel: TeamChannel;
|
|
275
|
+
team: {
|
|
276
|
+
id: string;
|
|
277
|
+
displayName: string;
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
/** Result of finding channels. */
|
|
281
|
+
export interface FindChannelResult {
|
|
282
|
+
success: boolean;
|
|
283
|
+
channels?: ChannelWithTeam[];
|
|
284
|
+
error?: string;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Gets all teams and channels the user is a member of.
|
|
288
|
+
*
|
|
289
|
+
* Uses the CSA v3 teams/users/me endpoint which returns the full
|
|
290
|
+
* teams and channels structure.
|
|
291
|
+
*
|
|
292
|
+
* @param region - Region for the API (default: "amer")
|
|
293
|
+
*/
|
|
294
|
+
export declare function getJoinedTeams(region?: string): Promise<JoinedTeamsResult>;
|
|
295
|
+
/**
|
|
296
|
+
* Gets channels for a specific team.
|
|
297
|
+
*
|
|
298
|
+
* This is a convenience wrapper that calls getJoinedTeams and filters
|
|
299
|
+
* to the specified team.
|
|
300
|
+
*
|
|
301
|
+
* @param teamId - The team ID to get channels for
|
|
302
|
+
* @param region - Region for the API (default: "amer")
|
|
303
|
+
*/
|
|
304
|
+
export declare function getTeamChannels(teamId: string, region?: string): Promise<{
|
|
305
|
+
success: boolean;
|
|
306
|
+
channels?: TeamChannel[];
|
|
307
|
+
error?: string;
|
|
308
|
+
}>;
|
|
309
|
+
/**
|
|
310
|
+
* Finds channels by name across all teams the user is a member of.
|
|
311
|
+
*
|
|
312
|
+
* Performs a case-insensitive substring match on channel names.
|
|
313
|
+
*
|
|
314
|
+
* @param query - The channel name to search for (partial match)
|
|
315
|
+
* @param options - Optional filters
|
|
316
|
+
* @param options.teamName - Filter to a specific team name (partial match)
|
|
317
|
+
* @param region - Region for the API (default: "amer")
|
|
318
|
+
*/
|
|
319
|
+
export declare function findChannel(query: string, options?: {
|
|
320
|
+
teamName?: string;
|
|
321
|
+
}, region?: string): Promise<FindChannelResult>;
|