msteams-mcp 0.30.2 ā 0.30.3
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/dist/auth/session-store.d.ts +0 -5
- package/dist/auth/session-store.js +0 -7
- package/dist/browser/auth.js +4 -6
- package/dist/browser/auth.test.js +1 -20
- package/dist/browser/context.test.d.ts +8 -0
- package/dist/browser/context.test.js +55 -0
- package/dist/cli.js +0 -0
- package/dist/index.js +0 -0
- package/dist/tools/auth-tools.js +27 -26
- package/dist/tools/auth-tools.test.js +4 -18
- package/package.json +1 -1
- package/dist/api/index.d.ts +0 -10
- package/dist/api/index.js +0 -10
- package/dist/auth/index.d.ts +0 -7
- package/dist/auth/index.js +0 -7
- package/dist/research/auth-research.d.ts +0 -10
- package/dist/research/auth-research.js +0 -175
- package/dist/research/explore.d.ts +0 -11
- package/dist/research/explore.js +0 -287
- package/dist/research/search-research.d.ts +0 -17
- package/dist/research/search-research.js +0 -317
- package/dist/test/debug-search.d.ts +0 -10
- package/dist/test/debug-search.js +0 -147
- package/dist/test/mcp-harness.d.ts +0 -17
- package/dist/test/mcp-harness.js +0 -476
package/dist/research/explore.js
DELETED
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Research script to explore Teams web app behaviour.
|
|
3
|
-
*
|
|
4
|
-
* This script:
|
|
5
|
-
* 1. Launches a browser with persistent context
|
|
6
|
-
* 2. Navigates to Teams and handles authentication
|
|
7
|
-
* 3. Monitors network requests to discover API endpoints
|
|
8
|
-
* 4. Allows manual interaction to trigger searches
|
|
9
|
-
* 5. Logs discovered endpoints and data structures
|
|
10
|
-
*/
|
|
11
|
-
import { chromium } from 'playwright';
|
|
12
|
-
import * as fs from 'fs';
|
|
13
|
-
import * as path from 'path';
|
|
14
|
-
import { PROJECT_ROOT, USER_DATA_DIR, readSessionState, writeSessionState, } from '../auth/session-store.js';
|
|
15
|
-
const FINDINGS_PATH = path.join(PROJECT_ROOT, 'research-findings.json');
|
|
16
|
-
// Track interesting API calls
|
|
17
|
-
const capturedNetworkData = {
|
|
18
|
-
requests: [],
|
|
19
|
-
responses: [],
|
|
20
|
-
};
|
|
21
|
-
// Patterns that indicate interesting API calls
|
|
22
|
-
const SEARCH_PATTERNS = [
|
|
23
|
-
/search/i,
|
|
24
|
-
/query/i,
|
|
25
|
-
/find/i,
|
|
26
|
-
/substrate/i,
|
|
27
|
-
/graph\.microsoft/i,
|
|
28
|
-
/teams.*api/i,
|
|
29
|
-
/chatservice/i,
|
|
30
|
-
/emea\.ng\.msg/i,
|
|
31
|
-
/transcript/i,
|
|
32
|
-
/recording/i,
|
|
33
|
-
/onlineMeeting/i,
|
|
34
|
-
/callRecord/i,
|
|
35
|
-
/\.vtt/i,
|
|
36
|
-
/asyncgw/i,
|
|
37
|
-
/mediaservice/i,
|
|
38
|
-
];
|
|
39
|
-
function isInterestingUrl(url) {
|
|
40
|
-
// Skip static assets, telemetry, and auth-related noise
|
|
41
|
-
const skipPatterns = [
|
|
42
|
-
/\.(js|css|png|jpg|jpeg|gif|svg|woff|woff2|ttf|ico)(\?|$)/i,
|
|
43
|
-
/telemetry/i,
|
|
44
|
-
/analytics/i,
|
|
45
|
-
/logging/i,
|
|
46
|
-
/beacon/i,
|
|
47
|
-
/\.clarity\./i,
|
|
48
|
-
/fonts\./i,
|
|
49
|
-
/static/i,
|
|
50
|
-
];
|
|
51
|
-
if (skipPatterns.some(pattern => pattern.test(url))) {
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
// Include if it matches search patterns or is a potential API call
|
|
55
|
-
return SEARCH_PATTERNS.some(pattern => pattern.test(url)) ||
|
|
56
|
-
url.includes('/api/') ||
|
|
57
|
-
url.includes('/v1/') ||
|
|
58
|
-
url.includes('/v2/');
|
|
59
|
-
}
|
|
60
|
-
async function captureRequest(request) {
|
|
61
|
-
const url = request.url();
|
|
62
|
-
if (!isInterestingUrl(url)) {
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
const captured = {
|
|
66
|
-
url,
|
|
67
|
-
method: request.method(),
|
|
68
|
-
headers: request.headers(),
|
|
69
|
-
postData: request.postData() ?? undefined,
|
|
70
|
-
timestamp: new Date().toISOString(),
|
|
71
|
-
resourceType: request.resourceType(),
|
|
72
|
-
};
|
|
73
|
-
capturedNetworkData.requests.push(captured);
|
|
74
|
-
console.log(`\nš¤ REQUEST: ${request.method()} ${url}`);
|
|
75
|
-
if (captured.postData) {
|
|
76
|
-
try {
|
|
77
|
-
const parsed = JSON.parse(captured.postData);
|
|
78
|
-
// Show full body for message sends (to see mention format)
|
|
79
|
-
const isMessageSend = url.includes('/messages') && request.method() === 'POST';
|
|
80
|
-
const limit = isMessageSend ? 10000 : 500;
|
|
81
|
-
console.log(' Body:', JSON.stringify(parsed, null, 2).substring(0, limit));
|
|
82
|
-
}
|
|
83
|
-
catch {
|
|
84
|
-
console.log(' Body:', captured.postData.substring(0, 200));
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
async function captureResponse(response) {
|
|
89
|
-
const url = response.url();
|
|
90
|
-
if (!isInterestingUrl(url)) {
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
let body;
|
|
94
|
-
try {
|
|
95
|
-
const contentType = response.headers()['content-type'] || '';
|
|
96
|
-
if (contentType.includes('application/json') || contentType.includes('text/vtt') || contentType.includes('text/plain')) {
|
|
97
|
-
body = await response.text();
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
catch {
|
|
101
|
-
// Response body may not be available
|
|
102
|
-
}
|
|
103
|
-
const captured = {
|
|
104
|
-
url,
|
|
105
|
-
status: response.status(),
|
|
106
|
-
statusText: response.statusText(),
|
|
107
|
-
headers: response.headers(),
|
|
108
|
-
body,
|
|
109
|
-
timestamp: new Date().toISOString(),
|
|
110
|
-
};
|
|
111
|
-
capturedNetworkData.responses.push(captured);
|
|
112
|
-
console.log(`\nš„ RESPONSE: ${response.status()} ${url}`);
|
|
113
|
-
if (body) {
|
|
114
|
-
try {
|
|
115
|
-
const parsed = JSON.parse(body);
|
|
116
|
-
console.log(' Body preview:', JSON.stringify(parsed, null, 2).substring(0, 500));
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
console.log(' Body preview:', body.substring(0, 200));
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
async function saveFindings() {
|
|
124
|
-
// Deduplicate and summarise findings
|
|
125
|
-
const uniqueEndpoints = new Map();
|
|
126
|
-
for (const req of capturedNetworkData.requests) {
|
|
127
|
-
const key = `${req.method}:${new URL(req.url).pathname}`;
|
|
128
|
-
if (!uniqueEndpoints.has(key)) {
|
|
129
|
-
uniqueEndpoints.set(key, req);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
const findings = {
|
|
133
|
-
discoveredAt: new Date().toISOString(),
|
|
134
|
-
uniqueEndpoints: Array.from(uniqueEndpoints.values()).map(req => ({
|
|
135
|
-
method: req.method,
|
|
136
|
-
url: req.url,
|
|
137
|
-
hasPostData: !!req.postData,
|
|
138
|
-
postDataSample: req.postData?.substring(0, 500),
|
|
139
|
-
})),
|
|
140
|
-
allRequests: capturedNetworkData.requests,
|
|
141
|
-
allResponses: capturedNetworkData.responses,
|
|
142
|
-
};
|
|
143
|
-
fs.writeFileSync(FINDINGS_PATH, JSON.stringify(findings, null, 2));
|
|
144
|
-
console.log(`\nš¾ Findings saved to ${FINDINGS_PATH}`);
|
|
145
|
-
}
|
|
146
|
-
async function checkAuthentication(page) {
|
|
147
|
-
const url = page.url();
|
|
148
|
-
// Check if we're on a login page
|
|
149
|
-
if (url.includes('login.microsoftonline.com') ||
|
|
150
|
-
url.includes('login.live.com') ||
|
|
151
|
-
url.includes('login.microsoft.com')) {
|
|
152
|
-
return false;
|
|
153
|
-
}
|
|
154
|
-
// Check if we're on the Teams app
|
|
155
|
-
if (url.includes('teams.microsoft.com')) {
|
|
156
|
-
// Look for indicators that we're logged in
|
|
157
|
-
try {
|
|
158
|
-
// Wait briefly for the app to load
|
|
159
|
-
await page.waitForTimeout(2000);
|
|
160
|
-
// Check for common authenticated elements
|
|
161
|
-
const hasAppBar = await page.locator('[data-tid="app-bar"]').count() > 0;
|
|
162
|
-
const hasSearchBox = await page.locator('[data-tid="search-box"]').count() > 0 ||
|
|
163
|
-
await page.locator('input[placeholder*="Search"]').count() > 0;
|
|
164
|
-
return hasAppBar || hasSearchBox;
|
|
165
|
-
}
|
|
166
|
-
catch {
|
|
167
|
-
return false;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
return false;
|
|
171
|
-
}
|
|
172
|
-
async function waitForAuthentication(page) {
|
|
173
|
-
console.log('\nš Please log in to Microsoft Teams in the browser window...');
|
|
174
|
-
console.log(' The script will continue once you are authenticated.\n');
|
|
175
|
-
// Poll for authentication
|
|
176
|
-
while (true) {
|
|
177
|
-
if (await checkAuthentication(page)) {
|
|
178
|
-
console.log('ā
Authentication detected!');
|
|
179
|
-
break;
|
|
180
|
-
}
|
|
181
|
-
await page.waitForTimeout(2000);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
async function main() {
|
|
185
|
-
console.log('š Teams Research Script');
|
|
186
|
-
console.log('========================\n');
|
|
187
|
-
console.log('This script will help discover how Teams web app works.\n');
|
|
188
|
-
// Ensure user data directory exists
|
|
189
|
-
if (!fs.existsSync(USER_DATA_DIR)) {
|
|
190
|
-
fs.mkdirSync(USER_DATA_DIR, { recursive: true });
|
|
191
|
-
}
|
|
192
|
-
let browser;
|
|
193
|
-
let context;
|
|
194
|
-
try {
|
|
195
|
-
// Launch browser with persistent context
|
|
196
|
-
const channel = process.platform === 'win32' ? 'msedge' : 'chrome';
|
|
197
|
-
browser = await chromium.launch({
|
|
198
|
-
headless: false, // Must be visible for manual login
|
|
199
|
-
channel,
|
|
200
|
-
});
|
|
201
|
-
// Check if we have saved session state (uses encrypted store)
|
|
202
|
-
const existingState = readSessionState();
|
|
203
|
-
if (existingState) {
|
|
204
|
-
console.log('š Found existing session state, attempting to restore...');
|
|
205
|
-
context = await browser.newContext({
|
|
206
|
-
storageState: existingState,
|
|
207
|
-
viewport: { width: 1280, height: 800 },
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
console.log('š No session state found, starting fresh...');
|
|
212
|
-
context = await browser.newContext({
|
|
213
|
-
viewport: { width: 1280, height: 800 },
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
const page = await context.newPage();
|
|
217
|
-
// Set up network interception
|
|
218
|
-
page.on('request', captureRequest);
|
|
219
|
-
page.on('response', captureResponse);
|
|
220
|
-
// Navigate to Teams
|
|
221
|
-
console.log('š Navigating to Teams...');
|
|
222
|
-
await page.goto('https://teams.microsoft.com', { waitUntil: 'domcontentloaded' });
|
|
223
|
-
// Check if authentication is needed
|
|
224
|
-
const isAuthenticated = await checkAuthentication(page);
|
|
225
|
-
if (!isAuthenticated) {
|
|
226
|
-
await waitForAuthentication(page);
|
|
227
|
-
// Save session state after successful authentication
|
|
228
|
-
console.log('š¾ Saving session state...');
|
|
229
|
-
const state = await context.storageState();
|
|
230
|
-
writeSessionState(state);
|
|
231
|
-
console.log('ā
Session state saved!');
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
234
|
-
console.log('ā
Already authenticated!');
|
|
235
|
-
}
|
|
236
|
-
// Instructions for the user
|
|
237
|
-
console.log('\n' + '='.repeat(60));
|
|
238
|
-
console.log('š¬ RESEARCH MODE ACTIVE');
|
|
239
|
-
console.log('='.repeat(60));
|
|
240
|
-
console.log('\nThe browser is now monitoring network requests.');
|
|
241
|
-
console.log('Try the following to discover API endpoints:\n');
|
|
242
|
-
console.log('1. Use the search bar (Cmd/Ctrl+E or click the search icon)');
|
|
243
|
-
console.log('2. Search for some text');
|
|
244
|
-
console.log('3. Click on search results');
|
|
245
|
-
console.log('4. Navigate between chats and channels');
|
|
246
|
-
console.log('\nPress Ctrl+C in this terminal when done to save findings.');
|
|
247
|
-
console.log('='.repeat(60) + '\n');
|
|
248
|
-
// Keep the browser open until interrupted
|
|
249
|
-
await new Promise((resolve) => {
|
|
250
|
-
process.on('SIGINT', () => {
|
|
251
|
-
console.log('\n\nā¹ļø Stopping research...');
|
|
252
|
-
resolve();
|
|
253
|
-
});
|
|
254
|
-
});
|
|
255
|
-
// Save research findings FIRST (before browser might close)
|
|
256
|
-
await saveFindings();
|
|
257
|
-
// Try to save session state (may fail if browser already closed)
|
|
258
|
-
try {
|
|
259
|
-
console.log('š¾ Saving final session state...');
|
|
260
|
-
const finalState = await context.storageState();
|
|
261
|
-
writeSessionState(finalState);
|
|
262
|
-
}
|
|
263
|
-
catch {
|
|
264
|
-
console.log('ā ļø Could not save session state (browser already closed)');
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
catch (error) {
|
|
268
|
-
// Still try to save findings on error
|
|
269
|
-
await saveFindings();
|
|
270
|
-
console.error('ā Error:', error);
|
|
271
|
-
}
|
|
272
|
-
finally {
|
|
273
|
-
try {
|
|
274
|
-
if (context)
|
|
275
|
-
await context.close();
|
|
276
|
-
}
|
|
277
|
-
catch { /* already closed */ }
|
|
278
|
-
try {
|
|
279
|
-
if (browser)
|
|
280
|
-
await browser.close();
|
|
281
|
-
}
|
|
282
|
-
catch { /* already closed */ }
|
|
283
|
-
}
|
|
284
|
-
console.log('\nā
Research session complete!');
|
|
285
|
-
console.log(` Review findings in: ${FINDINGS_PATH}`);
|
|
286
|
-
}
|
|
287
|
-
main().catch(console.error);
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Focused research script for Teams search API.
|
|
3
|
-
*
|
|
4
|
-
* Captures full request/response bodies for Substrate search endpoints
|
|
5
|
-
* to understand pagination, query format, and response structure.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* npm run research:search
|
|
9
|
-
*
|
|
10
|
-
* Instructions:
|
|
11
|
-
* 1. Run this script
|
|
12
|
-
* 2. Perform a search in Teams
|
|
13
|
-
* 3. Click "Messages" tab or "See more results"
|
|
14
|
-
* 4. Scroll through results to trigger pagination
|
|
15
|
-
* 5. Press Ctrl+C to stop and save findings
|
|
16
|
-
*/
|
|
17
|
-
export {};
|
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Focused research script for Teams search API.
|
|
3
|
-
*
|
|
4
|
-
* Captures full request/response bodies for Substrate search endpoints
|
|
5
|
-
* to understand pagination, query format, and response structure.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* npm run research:search
|
|
9
|
-
*
|
|
10
|
-
* Instructions:
|
|
11
|
-
* 1. Run this script
|
|
12
|
-
* 2. Perform a search in Teams
|
|
13
|
-
* 3. Click "Messages" tab or "See more results"
|
|
14
|
-
* 4. Scroll through results to trigger pagination
|
|
15
|
-
* 5. Press Ctrl+C to stop and save findings
|
|
16
|
-
*/
|
|
17
|
-
import { chromium } from 'playwright';
|
|
18
|
-
import * as fs from 'fs';
|
|
19
|
-
import * as path from 'path';
|
|
20
|
-
import { fileURLToPath } from 'url';
|
|
21
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
-
const PROJECT_ROOT = path.resolve(__dirname, '../..');
|
|
23
|
-
const SESSION_STATE_PATH = path.join(PROJECT_ROOT, 'session-state.json');
|
|
24
|
-
const SEARCH_FINDINGS_PATH = path.join(PROJECT_ROOT, 'search-api-findings.json');
|
|
25
|
-
const captures = [];
|
|
26
|
-
// Focus specifically on search-related endpoints
|
|
27
|
-
function isSearchEndpoint(url) {
|
|
28
|
-
const searchPatterns = [
|
|
29
|
-
/substrate\.office\.com\/search/i,
|
|
30
|
-
/\/api\/v\d+\/query/i,
|
|
31
|
-
/\/api\/v\d+\/suggestions/i,
|
|
32
|
-
/\/search\//i,
|
|
33
|
-
];
|
|
34
|
-
return searchPatterns.some(p => p.test(url));
|
|
35
|
-
}
|
|
36
|
-
async function captureSearchRequest(request) {
|
|
37
|
-
const url = request.url();
|
|
38
|
-
if (!isSearchEndpoint(url))
|
|
39
|
-
return;
|
|
40
|
-
const postData = request.postData();
|
|
41
|
-
let body;
|
|
42
|
-
if (postData) {
|
|
43
|
-
try {
|
|
44
|
-
body = JSON.parse(postData);
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
body = postData;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
const capture = {
|
|
51
|
-
timestamp: new Date().toISOString(),
|
|
52
|
-
type: 'request',
|
|
53
|
-
url,
|
|
54
|
-
method: request.method(),
|
|
55
|
-
body,
|
|
56
|
-
headers: request.headers(),
|
|
57
|
-
};
|
|
58
|
-
captures.push(capture);
|
|
59
|
-
console.log('\n' + 'ā'.repeat(80));
|
|
60
|
-
console.log(`š¤ SEARCH REQUEST: ${request.method()} ${url}`);
|
|
61
|
-
console.log('ā'.repeat(80));
|
|
62
|
-
if (body) {
|
|
63
|
-
console.log('\nš Request Body:');
|
|
64
|
-
console.log(JSON.stringify(body, null, 2));
|
|
65
|
-
}
|
|
66
|
-
// Highlight pagination-related fields
|
|
67
|
-
if (body && typeof body === 'object') {
|
|
68
|
-
const bodyObj = body;
|
|
69
|
-
const paginationFields = ['Size', 'From', 'Skip', 'Top', 'Offset', 'PageSize', 'Page', 'MaxResults'];
|
|
70
|
-
const foundPagination = {};
|
|
71
|
-
function findPaginationFields(obj, prefix = '') {
|
|
72
|
-
if (!obj || typeof obj !== 'object')
|
|
73
|
-
return;
|
|
74
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
75
|
-
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
76
|
-
if (paginationFields.some(p => key.toLowerCase().includes(p.toLowerCase()))) {
|
|
77
|
-
foundPagination[fullKey] = value;
|
|
78
|
-
}
|
|
79
|
-
if (typeof value === 'object') {
|
|
80
|
-
findPaginationFields(value, fullKey);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
findPaginationFields(bodyObj);
|
|
85
|
-
if (Object.keys(foundPagination).length > 0) {
|
|
86
|
-
console.log('\nš Pagination Fields Found:');
|
|
87
|
-
console.log(JSON.stringify(foundPagination, null, 2));
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
async function captureSearchResponse(response) {
|
|
92
|
-
const url = response.url();
|
|
93
|
-
if (!isSearchEndpoint(url))
|
|
94
|
-
return;
|
|
95
|
-
let body;
|
|
96
|
-
try {
|
|
97
|
-
const contentType = response.headers()['content-type'] || '';
|
|
98
|
-
if (contentType.includes('application/json')) {
|
|
99
|
-
const text = await response.text();
|
|
100
|
-
body = JSON.parse(text);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
// Response body may not be available
|
|
105
|
-
}
|
|
106
|
-
const capture = {
|
|
107
|
-
timestamp: new Date().toISOString(),
|
|
108
|
-
type: 'response',
|
|
109
|
-
url,
|
|
110
|
-
status: response.status(),
|
|
111
|
-
body,
|
|
112
|
-
};
|
|
113
|
-
captures.push(capture);
|
|
114
|
-
console.log('\n' + 'ā'.repeat(80));
|
|
115
|
-
console.log(`š„ SEARCH RESPONSE: ${response.status()} ${url}`);
|
|
116
|
-
console.log('ā'.repeat(80));
|
|
117
|
-
if (body && typeof body === 'object') {
|
|
118
|
-
const bodyObj = body;
|
|
119
|
-
// Show structure summary
|
|
120
|
-
console.log('\nš Response Structure:');
|
|
121
|
-
const summarise = (obj, depth = 0) => {
|
|
122
|
-
if (depth > 2)
|
|
123
|
-
return '...';
|
|
124
|
-
if (Array.isArray(obj)) {
|
|
125
|
-
return `Array[${obj.length}]${obj.length > 0 ? ` of ${summarise(obj[0], depth + 1)}` : ''}`;
|
|
126
|
-
}
|
|
127
|
-
if (obj && typeof obj === 'object') {
|
|
128
|
-
const keys = Object.keys(obj);
|
|
129
|
-
if (keys.length > 8) {
|
|
130
|
-
return `{${keys.slice(0, 5).join(', ')}, ... +${keys.length - 5} more}`;
|
|
131
|
-
}
|
|
132
|
-
return `{${keys.join(', ')}}`;
|
|
133
|
-
}
|
|
134
|
-
return typeof obj;
|
|
135
|
-
};
|
|
136
|
-
console.log(summarise(bodyObj));
|
|
137
|
-
// Show pagination-related response fields
|
|
138
|
-
const paginationResponseFields = ['Total', 'TotalCount', 'Count', 'HasMore', 'HasNext', 'NextLink', 'SkipToken', 'ContinuationToken'];
|
|
139
|
-
const foundPagination = {};
|
|
140
|
-
function findPaginationFields(obj, prefix = '') {
|
|
141
|
-
if (!obj || typeof obj !== 'object')
|
|
142
|
-
return;
|
|
143
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
144
|
-
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
145
|
-
if (paginationResponseFields.some(p => key.toLowerCase().includes(p.toLowerCase()))) {
|
|
146
|
-
foundPagination[fullKey] = value;
|
|
147
|
-
}
|
|
148
|
-
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
149
|
-
findPaginationFields(value, fullKey);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
findPaginationFields(bodyObj);
|
|
154
|
-
if (Object.keys(foundPagination).length > 0) {
|
|
155
|
-
console.log('\nš Pagination Response Fields:');
|
|
156
|
-
console.log(JSON.stringify(foundPagination, null, 2));
|
|
157
|
-
}
|
|
158
|
-
// Show results count
|
|
159
|
-
const countArrays = (obj) => {
|
|
160
|
-
const results = [];
|
|
161
|
-
function traverse(o, path) {
|
|
162
|
-
if (Array.isArray(o) && o.length > 0) {
|
|
163
|
-
results.push({ path: path || 'root', count: o.length });
|
|
164
|
-
}
|
|
165
|
-
if (o && typeof o === 'object' && !Array.isArray(o)) {
|
|
166
|
-
for (const [key, value] of Object.entries(o)) {
|
|
167
|
-
traverse(value, path ? `${path}.${key}` : key);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
traverse(obj, '');
|
|
172
|
-
return results;
|
|
173
|
-
};
|
|
174
|
-
const arrays = countArrays(bodyObj);
|
|
175
|
-
if (arrays.length > 0) {
|
|
176
|
-
console.log('\nš Result Counts:');
|
|
177
|
-
arrays.forEach(a => console.log(` ${a.path}: ${a.count} items`));
|
|
178
|
-
}
|
|
179
|
-
// Show first result sample if available
|
|
180
|
-
const findFirstResult = (obj) => {
|
|
181
|
-
if (Array.isArray(obj) && obj.length > 0) {
|
|
182
|
-
const first = obj[0];
|
|
183
|
-
if (first && typeof first === 'object' && Object.keys(first).length > 2) {
|
|
184
|
-
return first;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
if (obj && typeof obj === 'object') {
|
|
188
|
-
for (const value of Object.values(obj)) {
|
|
189
|
-
const found = findFirstResult(value);
|
|
190
|
-
if (found)
|
|
191
|
-
return found;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return null;
|
|
195
|
-
};
|
|
196
|
-
const firstResult = findFirstResult(bodyObj);
|
|
197
|
-
if (firstResult) {
|
|
198
|
-
console.log('\nš First Result Sample:');
|
|
199
|
-
console.log(JSON.stringify(firstResult, null, 2).substring(0, 1000));
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
async function checkAuthentication(page) {
|
|
204
|
-
const url = page.url();
|
|
205
|
-
if (url.includes('login.microsoftonline.com') ||
|
|
206
|
-
url.includes('login.live.com') ||
|
|
207
|
-
url.includes('login.microsoft.com')) {
|
|
208
|
-
return false;
|
|
209
|
-
}
|
|
210
|
-
if (url.includes('teams.microsoft.com')) {
|
|
211
|
-
try {
|
|
212
|
-
await page.waitForTimeout(2000);
|
|
213
|
-
const hasAppBar = await page.locator('[data-tid="app-bar"]').count() > 0;
|
|
214
|
-
const hasSearchBox = await page.locator('[data-tid="search-box"]').count() > 0 ||
|
|
215
|
-
await page.locator('input[placeholder*="Search"]').count() > 0;
|
|
216
|
-
return hasAppBar || hasSearchBox;
|
|
217
|
-
}
|
|
218
|
-
catch {
|
|
219
|
-
return false;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
return false;
|
|
223
|
-
}
|
|
224
|
-
async function waitForAuthentication(page) {
|
|
225
|
-
console.log('\nš Please log in to Microsoft Teams...');
|
|
226
|
-
while (true) {
|
|
227
|
-
if (await checkAuthentication(page)) {
|
|
228
|
-
console.log('ā
Authentication detected!');
|
|
229
|
-
break;
|
|
230
|
-
}
|
|
231
|
-
await page.waitForTimeout(2000);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
async function saveFindings() {
|
|
235
|
-
const findings = {
|
|
236
|
-
capturedAt: new Date().toISOString(),
|
|
237
|
-
totalCaptures: captures.length,
|
|
238
|
-
requests: captures.filter(c => c.type === 'request'),
|
|
239
|
-
responses: captures.filter(c => c.type === 'response'),
|
|
240
|
-
};
|
|
241
|
-
fs.writeFileSync(SEARCH_FINDINGS_PATH, JSON.stringify(findings, null, 2));
|
|
242
|
-
console.log(`\nš¾ Search findings saved to ${SEARCH_FINDINGS_PATH}`);
|
|
243
|
-
}
|
|
244
|
-
async function main() {
|
|
245
|
-
console.log('š Teams Search API Research');
|
|
246
|
-
console.log('============================\n');
|
|
247
|
-
let browser;
|
|
248
|
-
let context;
|
|
249
|
-
try {
|
|
250
|
-
browser = await chromium.launch({ headless: false });
|
|
251
|
-
const hasSessionState = fs.existsSync(SESSION_STATE_PATH);
|
|
252
|
-
if (hasSessionState) {
|
|
253
|
-
console.log('š Restoring session state...');
|
|
254
|
-
context = await browser.newContext({
|
|
255
|
-
storageState: SESSION_STATE_PATH,
|
|
256
|
-
viewport: { width: 1400, height: 900 },
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
else {
|
|
260
|
-
console.log('š No session state found, starting fresh...');
|
|
261
|
-
context = await browser.newContext({
|
|
262
|
-
viewport: { width: 1400, height: 900 },
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
const page = await context.newPage();
|
|
266
|
-
// Set up network interception for search endpoints only
|
|
267
|
-
page.on('request', captureSearchRequest);
|
|
268
|
-
page.on('response', captureSearchResponse);
|
|
269
|
-
console.log('š Navigating to Teams...');
|
|
270
|
-
await page.goto('https://teams.microsoft.com', { waitUntil: 'domcontentloaded' });
|
|
271
|
-
const isAuthenticated = await checkAuthentication(page);
|
|
272
|
-
if (!isAuthenticated) {
|
|
273
|
-
await waitForAuthentication(page);
|
|
274
|
-
await context.storageState({ path: SESSION_STATE_PATH });
|
|
275
|
-
console.log('ā
Session state saved!');
|
|
276
|
-
}
|
|
277
|
-
else {
|
|
278
|
-
console.log('ā
Already authenticated!');
|
|
279
|
-
}
|
|
280
|
-
console.log('\n' + 'ā'.repeat(60));
|
|
281
|
-
console.log('š¬ SEARCH RESEARCH MODE');
|
|
282
|
-
console.log('ā'.repeat(60));
|
|
283
|
-
console.log('\nTo capture search API data:');
|
|
284
|
-
console.log('');
|
|
285
|
-
console.log(' 1. Press Cmd/Ctrl+E to open search');
|
|
286
|
-
console.log(' 2. Type a search query and press Enter');
|
|
287
|
-
console.log(' 3. Click "Messages" tab to see message results');
|
|
288
|
-
console.log(' 4. Click "See all results" if available');
|
|
289
|
-
console.log(' 5. Scroll through results to trigger pagination');
|
|
290
|
-
console.log('');
|
|
291
|
-
console.log('Watch for š Pagination Fields in the output!');
|
|
292
|
-
console.log('');
|
|
293
|
-
console.log('Press Ctrl+C when done to save findings.');
|
|
294
|
-
console.log('ā'.repeat(60) + '\n');
|
|
295
|
-
await new Promise((resolve) => {
|
|
296
|
-
process.on('SIGINT', () => {
|
|
297
|
-
console.log('\n\nā¹ļø Stopping research...');
|
|
298
|
-
resolve();
|
|
299
|
-
});
|
|
300
|
-
});
|
|
301
|
-
await context.storageState({ path: SESSION_STATE_PATH });
|
|
302
|
-
await saveFindings();
|
|
303
|
-
}
|
|
304
|
-
catch (error) {
|
|
305
|
-
console.error('ā Error:', error);
|
|
306
|
-
throw error;
|
|
307
|
-
}
|
|
308
|
-
finally {
|
|
309
|
-
if (context)
|
|
310
|
-
await context.close();
|
|
311
|
-
if (browser)
|
|
312
|
-
await browser.close();
|
|
313
|
-
}
|
|
314
|
-
console.log('\nā
Search research complete!');
|
|
315
|
-
console.log(` Review findings in: ${SEARCH_FINDINGS_PATH}`);
|
|
316
|
-
}
|
|
317
|
-
main().catch(console.error);
|