@roomi-fields/notebooklm-mcp 1.3.4 → 1.3.6
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 +164 -715
- package/dist/__tests__/cleanup-manager.test.d.ts +2 -0
- package/dist/__tests__/cleanup-manager.test.d.ts.map +1 -0
- package/dist/__tests__/cleanup-manager.test.js +341 -0
- package/dist/__tests__/cleanup-manager.test.js.map +1 -0
- package/dist/__tests__/config-parsing.test.d.ts +2 -0
- package/dist/__tests__/config-parsing.test.d.ts.map +1 -0
- package/dist/__tests__/config-parsing.test.js +338 -0
- package/dist/__tests__/config-parsing.test.js.map +1 -0
- package/dist/__tests__/config.test.d.ts +2 -0
- package/dist/__tests__/config.test.d.ts.map +1 -0
- package/dist/__tests__/config.test.js +267 -0
- package/dist/__tests__/config.test.js.map +1 -0
- package/dist/__tests__/errors.test.d.ts +2 -0
- package/dist/__tests__/errors.test.d.ts.map +1 -0
- package/dist/__tests__/errors.test.js +166 -0
- package/dist/__tests__/errors.test.js.map +1 -0
- package/dist/__tests__/logger.test.d.ts +2 -0
- package/dist/__tests__/logger.test.d.ts.map +1 -0
- package/dist/__tests__/logger.test.js +324 -0
- package/dist/__tests__/logger.test.js.map +1 -0
- package/dist/__tests__/page-utils.test.d.ts +2 -0
- package/dist/__tests__/page-utils.test.d.ts.map +1 -0
- package/dist/__tests__/page-utils.test.js +349 -0
- package/dist/__tests__/page-utils.test.js.map +1 -0
- package/dist/__tests__/setup-verification.test.d.ts +2 -0
- package/dist/__tests__/setup-verification.test.d.ts.map +1 -0
- package/dist/__tests__/setup-verification.test.js +15 -0
- package/dist/__tests__/setup-verification.test.js.map +1 -0
- package/dist/__tests__/stealth-utils.test.d.ts +2 -0
- package/dist/__tests__/stealth-utils.test.d.ts.map +1 -0
- package/dist/__tests__/stealth-utils.test.js +413 -0
- package/dist/__tests__/stealth-utils.test.js.map +1 -0
- package/dist/__tests__/types.test.d.ts +2 -0
- package/dist/__tests__/types.test.d.ts.map +1 -0
- package/dist/__tests__/types.test.js +461 -0
- package/dist/__tests__/types.test.js.map +1 -0
- package/dist/auth/auth-manager.d.ts +2 -2
- package/dist/auth/auth-manager.d.ts.map +1 -1
- package/dist/auth/auth-manager.js +147 -145
- package/dist/auth/auth-manager.js.map +1 -1
- package/dist/auto-discovery/auto-discovery.d.ts.map +1 -1
- package/dist/auto-discovery/auto-discovery.js +17 -17
- package/dist/auto-discovery/auto-discovery.js.map +1 -1
- package/dist/cli/de-auth.js +19 -19
- package/dist/cli/help.js +60 -60
- package/dist/cli/setup-auth.js +39 -39
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +35 -20
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +3 -3
- package/dist/errors.js.map +1 -1
- package/dist/http-wrapper.js +36 -30
- package/dist/http-wrapper.js.map +1 -1
- package/dist/index.js +104 -102
- package/dist/index.js.map +1 -1
- package/dist/library/notebook-library.d.ts +2 -2
- package/dist/library/notebook-library.d.ts.map +1 -1
- package/dist/library/notebook-library.js +24 -24
- package/dist/library/notebook-library.js.map +1 -1
- package/dist/session/browser-session.d.ts +4 -4
- package/dist/session/browser-session.d.ts.map +1 -1
- package/dist/session/browser-session.js +59 -55
- package/dist/session/browser-session.js.map +1 -1
- package/dist/session/session-manager.d.ts +3 -3
- package/dist/session/session-manager.d.ts.map +1 -1
- package/dist/session/session-manager.js +14 -14
- package/dist/session/session-manager.js.map +1 -1
- package/dist/session/shared-context-manager.d.ts +2 -2
- package/dist/session/shared-context-manager.d.ts.map +1 -1
- package/dist/session/shared-context-manager.js +60 -56
- package/dist/session/shared-context-manager.js.map +1 -1
- package/dist/tools/index.d.ts +6 -6
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +505 -505
- package/dist/tools/index.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/utils/cleanup-manager.d.ts +1 -1
- package/dist/utils/cleanup-manager.d.ts.map +1 -1
- package/dist/utils/cleanup-manager.js +90 -92
- package/dist/utils/cleanup-manager.js.map +1 -1
- package/dist/utils/logger.d.ts +1 -2
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +15 -15
- package/dist/utils/page-utils.d.ts +1 -1
- package/dist/utils/page-utils.d.ts.map +1 -1
- package/dist/utils/page-utils.js +30 -30
- package/dist/utils/page-utils.js.map +1 -1
- package/dist/utils/stealth-utils.d.ts +2 -2
- package/dist/utils/stealth-utils.d.ts.map +1 -1
- package/dist/utils/stealth-utils.js +13 -13
- package/dist/utils/stealth-utils.js.map +1 -1
- package/docs/CHROME_PROFILE_LIMITATION.md +229 -212
- package/package.json +107 -79
|
@@ -11,32 +11,32 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Based on the Python implementation from auth.py
|
|
13
13
|
*/
|
|
14
|
-
import fs from
|
|
15
|
-
import { existsSync } from
|
|
16
|
-
import path from
|
|
17
|
-
import { CONFIG, NOTEBOOKLM_AUTH_URL } from
|
|
18
|
-
import { log } from
|
|
19
|
-
import { humanType, randomDelay, realisticClick, randomMouseMovement, } from
|
|
14
|
+
import fs from 'fs/promises';
|
|
15
|
+
import { existsSync } from 'fs';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import { CONFIG, NOTEBOOKLM_AUTH_URL } from '../config.js';
|
|
18
|
+
import { log } from '../utils/logger.js';
|
|
19
|
+
import { humanType, randomDelay, realisticClick, randomMouseMovement, } from '../utils/stealth-utils.js';
|
|
20
20
|
/**
|
|
21
21
|
* Critical cookie names for Google authentication
|
|
22
22
|
*/
|
|
23
23
|
const CRITICAL_COOKIE_NAMES = [
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
'SID',
|
|
25
|
+
'HSID',
|
|
26
|
+
'SSID', // Google session
|
|
27
|
+
'APISID',
|
|
28
|
+
'SAPISID', // API auth
|
|
29
|
+
'OSID',
|
|
30
|
+
'__Secure-OSID', // NotebookLM-specific
|
|
31
|
+
'__Secure-1PSID',
|
|
32
|
+
'__Secure-3PSID', // Secure variants
|
|
33
33
|
];
|
|
34
34
|
export class AuthManager {
|
|
35
35
|
stateFilePath;
|
|
36
36
|
sessionFilePath;
|
|
37
37
|
constructor() {
|
|
38
|
-
this.stateFilePath = path.join(CONFIG.browserStateDir,
|
|
39
|
-
this.sessionFilePath = path.join(CONFIG.browserStateDir,
|
|
38
|
+
this.stateFilePath = path.join(CONFIG.browserStateDir, 'state.json');
|
|
39
|
+
this.sessionFilePath = path.join(CONFIG.browserStateDir, 'session.json');
|
|
40
40
|
}
|
|
41
41
|
// ============================================================================
|
|
42
42
|
// Browser State Management
|
|
@@ -66,7 +66,7 @@ export class AuthManager {
|
|
|
66
66
|
return JSON.stringify(storage);
|
|
67
67
|
});
|
|
68
68
|
await fs.writeFile(this.sessionFilePath, sessionStorageData, {
|
|
69
|
-
encoding:
|
|
69
|
+
encoding: 'utf-8',
|
|
70
70
|
});
|
|
71
71
|
const entries = Object.keys(JSON.parse(sessionStorageData)).length;
|
|
72
72
|
log.success(`✅ Browser state saved (incl. sessionStorage: ${entries} entries)`);
|
|
@@ -76,7 +76,7 @@ export class AuthManager {
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
else {
|
|
79
|
-
log.success(
|
|
79
|
+
log.success('✅ Browser state saved');
|
|
80
80
|
}
|
|
81
81
|
return true;
|
|
82
82
|
}
|
|
@@ -116,8 +116,8 @@ export class AuthManager {
|
|
|
116
116
|
return null;
|
|
117
117
|
}
|
|
118
118
|
if (await this.isStateExpired()) {
|
|
119
|
-
log.warning(
|
|
120
|
-
log.info(
|
|
119
|
+
log.warning('⚠️ Saved state is expired (>24h old)');
|
|
120
|
+
log.info('💡 Run setup_auth tool to re-authenticate');
|
|
121
121
|
return null;
|
|
122
122
|
}
|
|
123
123
|
return statePath;
|
|
@@ -127,7 +127,7 @@ export class AuthManager {
|
|
|
127
127
|
*/
|
|
128
128
|
async loadSessionStorage() {
|
|
129
129
|
try {
|
|
130
|
-
const data = await fs.readFile(this.sessionFilePath, { encoding:
|
|
130
|
+
const data = await fs.readFile(this.sessionFilePath, { encoding: 'utf-8' });
|
|
131
131
|
const sessionData = JSON.parse(data);
|
|
132
132
|
log.success(`✅ Loaded sessionStorage (${Object.keys(sessionData).length} entries)`);
|
|
133
133
|
return sessionData;
|
|
@@ -147,13 +147,13 @@ export class AuthManager {
|
|
|
147
147
|
try {
|
|
148
148
|
const cookies = await context.cookies();
|
|
149
149
|
if (cookies.length === 0) {
|
|
150
|
-
log.warning(
|
|
150
|
+
log.warning('⚠️ No cookies found in state');
|
|
151
151
|
return false;
|
|
152
152
|
}
|
|
153
153
|
// Check for Google auth cookies
|
|
154
|
-
const googleCookies = cookies.filter((c) => c.domain.includes(
|
|
154
|
+
const googleCookies = cookies.filter((c) => c.domain.includes('google.com'));
|
|
155
155
|
if (googleCookies.length === 0) {
|
|
156
|
-
log.warning(
|
|
156
|
+
log.warning('⚠️ No Google cookies found');
|
|
157
157
|
return false;
|
|
158
158
|
}
|
|
159
159
|
// Check if important cookies are expired
|
|
@@ -165,7 +165,7 @@ export class AuthManager {
|
|
|
165
165
|
return false;
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
|
-
log.success(
|
|
168
|
+
log.success('✅ State validation passed');
|
|
169
169
|
return true;
|
|
170
170
|
}
|
|
171
171
|
catch (error) {
|
|
@@ -180,13 +180,13 @@ export class AuthManager {
|
|
|
180
180
|
try {
|
|
181
181
|
const cookies = await context.cookies();
|
|
182
182
|
if (cookies.length === 0) {
|
|
183
|
-
log.warning(
|
|
183
|
+
log.warning('⚠️ No cookies found');
|
|
184
184
|
return false;
|
|
185
185
|
}
|
|
186
186
|
// Find critical cookies
|
|
187
187
|
const criticalCookies = cookies.filter((c) => CRITICAL_COOKIE_NAMES.includes(c.name));
|
|
188
188
|
if (criticalCookies.length === 0) {
|
|
189
|
-
log.warning(
|
|
189
|
+
log.warning('⚠️ No critical auth cookies found');
|
|
190
190
|
return false;
|
|
191
191
|
}
|
|
192
192
|
// Check expiration for each critical cookie
|
|
@@ -204,7 +204,7 @@ export class AuthManager {
|
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
if (expiredCookies.length > 0) {
|
|
207
|
-
log.warning(`⚠️ Expired cookies: ${expiredCookies.join(
|
|
207
|
+
log.warning(`⚠️ Expired cookies: ${expiredCookies.join(', ')}`);
|
|
208
208
|
return false;
|
|
209
209
|
}
|
|
210
210
|
log.success(`✅ All ${criticalCookies.length} critical cookies are valid`);
|
|
@@ -245,18 +245,18 @@ export class AuthManager {
|
|
|
245
245
|
*/
|
|
246
246
|
async performLogin(page, sendProgress) {
|
|
247
247
|
try {
|
|
248
|
-
log.info(
|
|
249
|
-
log.warning(
|
|
250
|
-
log.warning(
|
|
251
|
-
log.info(
|
|
248
|
+
log.info('🌐 Opening Google login page...');
|
|
249
|
+
log.warning('📝 Please login to your Google account');
|
|
250
|
+
log.warning('⏳ Browser will close automatically once you reach NotebookLM');
|
|
251
|
+
log.info('');
|
|
252
252
|
// Progress: Navigating
|
|
253
|
-
await sendProgress?.(
|
|
253
|
+
await sendProgress?.('Navigating to Google login...', 3, 10);
|
|
254
254
|
// Navigate to Google login (redirects to NotebookLM after auth)
|
|
255
255
|
await page.goto(NOTEBOOKLM_AUTH_URL, { timeout: 60000 });
|
|
256
256
|
// Progress: Waiting for login
|
|
257
|
-
await sendProgress?.(
|
|
257
|
+
await sendProgress?.('Waiting for manual login (up to 10 minutes)...', 4, 10);
|
|
258
258
|
// Wait for user to complete login
|
|
259
|
-
log.warning(
|
|
259
|
+
log.warning('⏳ Waiting for login (up to 10 minutes)...');
|
|
260
260
|
const checkIntervalMs = 1000; // Check every 1 second
|
|
261
261
|
const maxAttempts = 600; // 10 minutes total
|
|
262
262
|
let lastProgressUpdate = 0;
|
|
@@ -271,32 +271,32 @@ export class AuthManager {
|
|
|
271
271
|
await sendProgress?.(`Waiting for login... (${elapsedSeconds}s elapsed)`, progressStep, 10);
|
|
272
272
|
}
|
|
273
273
|
// ✅ SIMPLE: Check if we're on NotebookLM (any path!)
|
|
274
|
-
if (currentUrl.startsWith(
|
|
275
|
-
await sendProgress?.(
|
|
276
|
-
log.success(
|
|
274
|
+
if (currentUrl.startsWith('https://notebooklm.google.com/')) {
|
|
275
|
+
await sendProgress?.('Login successful! NotebookLM detected!', 9, 10);
|
|
276
|
+
log.success('✅ Login successful! NotebookLM URL detected.');
|
|
277
277
|
log.success(`✅ Current URL: ${currentUrl}`);
|
|
278
278
|
// ✅ CRITICAL: Wait for page to fully load before saving cookies
|
|
279
279
|
// NotebookLM needs time to:
|
|
280
280
|
// 1. Complete the OAuth redirect
|
|
281
281
|
// 2. Generate session cookies (__Secure-OSID, etc.)
|
|
282
282
|
// 3. Load the application and establish session
|
|
283
|
-
log.info(
|
|
284
|
-
await sendProgress?.(
|
|
283
|
+
log.info('⏳ Waiting for NotebookLM to fully load (10 seconds)...');
|
|
284
|
+
await sendProgress?.('Waiting for page to fully load...', 9, 10);
|
|
285
285
|
// Wait for network to be idle (no more requests for 500ms)
|
|
286
286
|
try {
|
|
287
|
-
await page.waitForLoadState(
|
|
288
|
-
log.success(
|
|
287
|
+
await page.waitForLoadState('networkidle', { timeout: 15000 });
|
|
288
|
+
log.success('✅ Page network is idle');
|
|
289
289
|
}
|
|
290
290
|
catch {
|
|
291
|
-
log.warning(
|
|
291
|
+
log.warning('⚠️ Network idle timeout - continuing anyway');
|
|
292
292
|
}
|
|
293
293
|
// Additional buffer to ensure all cookies are set
|
|
294
294
|
await page.waitForTimeout(5000);
|
|
295
|
-
log.success(
|
|
295
|
+
log.success('✅ Page fully loaded, cookies should be set');
|
|
296
296
|
return true;
|
|
297
297
|
}
|
|
298
298
|
// Still on accounts.google.com - log periodically
|
|
299
|
-
if (currentUrl.includes(
|
|
299
|
+
if (currentUrl.includes('accounts.google.com') && attempt % 30 === 0 && attempt > 0) {
|
|
300
300
|
log.warning(`⏳ Still waiting... (${elapsedSeconds}s elapsed)`);
|
|
301
301
|
}
|
|
302
302
|
await page.waitForTimeout(checkIntervalMs);
|
|
@@ -308,12 +308,12 @@ export class AuthManager {
|
|
|
308
308
|
}
|
|
309
309
|
// Timeout reached - final check
|
|
310
310
|
const currentUrl = page.url();
|
|
311
|
-
if (currentUrl.startsWith(
|
|
312
|
-
await sendProgress?.(
|
|
313
|
-
log.success(
|
|
311
|
+
if (currentUrl.startsWith('https://notebooklm.google.com/')) {
|
|
312
|
+
await sendProgress?.('Login successful (detected on timeout check)!', 9, 10);
|
|
313
|
+
log.success('✅ Login successful (detected on timeout check)');
|
|
314
314
|
return true;
|
|
315
315
|
}
|
|
316
|
-
log.error(
|
|
316
|
+
log.error('❌ Login verification failed - timeout reached');
|
|
317
317
|
log.warning(`Current URL: ${currentUrl}`);
|
|
318
318
|
return false;
|
|
319
319
|
}
|
|
@@ -333,55 +333,55 @@ export class AuthManager {
|
|
|
333
333
|
log.warning(`🔁 Attempting automatic login for ${maskedEmail}...`);
|
|
334
334
|
// Log browser visibility
|
|
335
335
|
if (!CONFIG.headless) {
|
|
336
|
-
log.info(
|
|
336
|
+
log.info(' 👁️ Browser is VISIBLE for debugging');
|
|
337
337
|
}
|
|
338
338
|
else {
|
|
339
|
-
log.info(
|
|
339
|
+
log.info(' 🙈 Browser is HEADLESS (invisible)');
|
|
340
340
|
}
|
|
341
341
|
log.info(` 🌐 Navigating to Google login...`);
|
|
342
342
|
try {
|
|
343
343
|
await page.goto(NOTEBOOKLM_AUTH_URL, {
|
|
344
|
-
waitUntil:
|
|
344
|
+
waitUntil: 'domcontentloaded',
|
|
345
345
|
timeout: CONFIG.browserTimeout,
|
|
346
346
|
});
|
|
347
347
|
log.success(` ✅ Page loaded: ${page.url().slice(0, 80)}...`);
|
|
348
348
|
}
|
|
349
|
-
catch
|
|
349
|
+
catch {
|
|
350
350
|
log.warning(` ⚠️ Page load timeout (continuing anyway)`);
|
|
351
351
|
}
|
|
352
352
|
const deadline = Date.now() + CONFIG.autoLoginTimeoutMs;
|
|
353
353
|
log.info(` ⏰ Auto-login timeout: ${CONFIG.autoLoginTimeoutMs / 1000}s`);
|
|
354
354
|
// Already on NotebookLM?
|
|
355
|
-
log.info(
|
|
355
|
+
log.info(' 🔍 Checking if already authenticated...');
|
|
356
356
|
if (await this.waitForNotebook(page, CONFIG.autoLoginTimeoutMs)) {
|
|
357
|
-
log.success(
|
|
357
|
+
log.success('✅ Already authenticated');
|
|
358
358
|
await this.saveBrowserState(context, page);
|
|
359
359
|
return true;
|
|
360
360
|
}
|
|
361
|
-
log.warning(
|
|
361
|
+
log.warning(' ❌ Not authenticated yet, proceeding with login...');
|
|
362
362
|
// Handle possible account chooser
|
|
363
|
-
log.info(
|
|
363
|
+
log.info(' 🔍 Checking for account chooser...');
|
|
364
364
|
if (await this.handleAccountChooser(page, email)) {
|
|
365
|
-
log.success(
|
|
365
|
+
log.success(' ✅ Account selected from chooser');
|
|
366
366
|
if (await this.waitForNotebook(page, CONFIG.autoLoginTimeoutMs)) {
|
|
367
|
-
log.success(
|
|
367
|
+
log.success('✅ Automatic login successful');
|
|
368
368
|
await this.saveBrowserState(context, page);
|
|
369
369
|
return true;
|
|
370
370
|
}
|
|
371
371
|
}
|
|
372
372
|
// Email step
|
|
373
|
-
log.info(
|
|
373
|
+
log.info(' 📧 Entering email address...');
|
|
374
374
|
if (!(await this.fillIdentifier(page, email))) {
|
|
375
375
|
if (await this.waitForNotebook(page, CONFIG.autoLoginTimeoutMs)) {
|
|
376
|
-
log.success(
|
|
376
|
+
log.success('✅ Automatic login successful');
|
|
377
377
|
await this.saveBrowserState(context, page);
|
|
378
378
|
return true;
|
|
379
379
|
}
|
|
380
|
-
log.warning(
|
|
380
|
+
log.warning('⚠️ Email input not detected');
|
|
381
381
|
}
|
|
382
382
|
// Password step (wait until visible)
|
|
383
383
|
let waitAttempts = 0;
|
|
384
|
-
log.warning(
|
|
384
|
+
log.warning(' ⏳ Waiting for password page to load...');
|
|
385
385
|
while (Date.now() < deadline && !(await this.fillPassword(page, password))) {
|
|
386
386
|
waitAttempts++;
|
|
387
387
|
// Log every 10 seconds (20 attempts * 0.5s)
|
|
@@ -391,21 +391,21 @@ export class AuthManager {
|
|
|
391
391
|
log.warning(` ⏳ Still waiting for password field... (${secondsWaited}s elapsed, ${secondsRemaining.toFixed(0)}s remaining)`);
|
|
392
392
|
log.info(` 📍 Current URL: ${page.url().slice(0, 100)}`);
|
|
393
393
|
}
|
|
394
|
-
if (page.url().includes(
|
|
395
|
-
log.warning(
|
|
394
|
+
if (page.url().includes('challenge')) {
|
|
395
|
+
log.warning('⚠️ Additional verification required (Google challenge page).');
|
|
396
396
|
return false;
|
|
397
397
|
}
|
|
398
398
|
await page.waitForTimeout(500);
|
|
399
399
|
}
|
|
400
400
|
// Wait for Google redirect after login
|
|
401
|
-
log.info(
|
|
401
|
+
log.info(' 🔄 Waiting for Google redirect to NotebookLM...');
|
|
402
402
|
if (await this.waitForRedirectAfterLogin(page, deadline)) {
|
|
403
|
-
log.success(
|
|
403
|
+
log.success('✅ Automatic login successful');
|
|
404
404
|
await this.saveBrowserState(context, page);
|
|
405
405
|
return true;
|
|
406
406
|
}
|
|
407
407
|
// Login failed - diagnose
|
|
408
|
-
log.error(
|
|
408
|
+
log.error('❌ Automatic login timed out');
|
|
409
409
|
// Take screenshot for debugging
|
|
410
410
|
try {
|
|
411
411
|
const screenshotPath = path.join(CONFIG.dataDir, `login_failed_${Date.now()}.png`);
|
|
@@ -417,27 +417,27 @@ export class AuthManager {
|
|
|
417
417
|
}
|
|
418
418
|
// Diagnose specific failure reason
|
|
419
419
|
const currentUrl = page.url();
|
|
420
|
-
log.warning(
|
|
421
|
-
if (currentUrl.includes(
|
|
422
|
-
if (currentUrl.includes(
|
|
423
|
-
log.error(
|
|
424
|
-
log.info(
|
|
420
|
+
log.warning(' 🔍 Diagnosing failure...');
|
|
421
|
+
if (currentUrl.includes('accounts.google.com')) {
|
|
422
|
+
if (currentUrl.includes('/signin/identifier')) {
|
|
423
|
+
log.error(' ❌ Still on email page - email input might have failed');
|
|
424
|
+
log.info(' 💡 Check if email is correct in .env');
|
|
425
425
|
}
|
|
426
|
-
else if (currentUrl.includes(
|
|
427
|
-
log.error(
|
|
428
|
-
log.info(
|
|
426
|
+
else if (currentUrl.includes('/challenge')) {
|
|
427
|
+
log.error(' ❌ Google requires additional verification (2FA, CAPTCHA, suspicious login)');
|
|
428
|
+
log.info(' 💡 Try logging in manually first: use setup_auth tool');
|
|
429
429
|
}
|
|
430
|
-
else if (currentUrl.includes(
|
|
431
|
-
log.error(
|
|
432
|
-
log.info(
|
|
430
|
+
else if (currentUrl.includes('/pwd') || currentUrl.includes('/password')) {
|
|
431
|
+
log.error(' ❌ Still on password page - password input might have failed');
|
|
432
|
+
log.info(' 💡 Check if password is correct in .env');
|
|
433
433
|
}
|
|
434
434
|
else {
|
|
435
435
|
log.error(` ❌ Stuck on Google accounts page: ${currentUrl.slice(0, 80)}...`);
|
|
436
436
|
}
|
|
437
437
|
}
|
|
438
|
-
else if (currentUrl.includes(
|
|
438
|
+
else if (currentUrl.includes('notebooklm.google.com')) {
|
|
439
439
|
log.warning(" ⚠️ Reached NotebookLM but couldn't detect successful login");
|
|
440
|
-
log.info(
|
|
440
|
+
log.info(' 💡 This might be a timing issue - try again');
|
|
441
441
|
}
|
|
442
442
|
else {
|
|
443
443
|
log.error(` ❌ Unexpected page: ${currentUrl.slice(0, 80)}...`);
|
|
@@ -454,13 +454,13 @@ export class AuthManager {
|
|
|
454
454
|
* Matches the simplified approach used in performLogin().
|
|
455
455
|
*/
|
|
456
456
|
async waitForRedirectAfterLogin(page, deadline) {
|
|
457
|
-
log.info(
|
|
457
|
+
log.info(' ⏳ Waiting for redirect to NotebookLM...');
|
|
458
458
|
while (Date.now() < deadline) {
|
|
459
459
|
try {
|
|
460
460
|
const currentUrl = page.url();
|
|
461
461
|
// Simple check: Are we on NotebookLM?
|
|
462
|
-
if (currentUrl.startsWith(
|
|
463
|
-
log.success(
|
|
462
|
+
if (currentUrl.startsWith('https://notebooklm.google.com/')) {
|
|
463
|
+
log.success(' ✅ NotebookLM URL detected!');
|
|
464
464
|
// Short wait to ensure page is loaded
|
|
465
465
|
await page.waitForTimeout(2000);
|
|
466
466
|
return true;
|
|
@@ -471,7 +471,7 @@ export class AuthManager {
|
|
|
471
471
|
}
|
|
472
472
|
await page.waitForTimeout(500);
|
|
473
473
|
}
|
|
474
|
-
log.error(
|
|
474
|
+
log.error(' ❌ Redirect timeout - NotebookLM URL not reached');
|
|
475
475
|
return false;
|
|
476
476
|
}
|
|
477
477
|
/**
|
|
@@ -486,8 +486,8 @@ export class AuthManager {
|
|
|
486
486
|
try {
|
|
487
487
|
const currentUrl = page.url();
|
|
488
488
|
// Simple check: Are we on NotebookLM?
|
|
489
|
-
if (currentUrl.startsWith(
|
|
490
|
-
log.success(
|
|
489
|
+
if (currentUrl.startsWith('https://notebooklm.google.com/')) {
|
|
490
|
+
log.success(' ✅ NotebookLM URL detected');
|
|
491
491
|
return true;
|
|
492
492
|
}
|
|
493
493
|
}
|
|
@@ -503,10 +503,10 @@ export class AuthManager {
|
|
|
503
503
|
*/
|
|
504
504
|
async handleAccountChooser(page, email) {
|
|
505
505
|
try {
|
|
506
|
-
const chooser = await page.$$(
|
|
506
|
+
const chooser = await page.$$('div[data-identifier], li[data-identifier]');
|
|
507
507
|
if (chooser.length > 0) {
|
|
508
508
|
for (const item of chooser) {
|
|
509
|
-
const identifier = (await item.getAttribute(
|
|
509
|
+
const identifier = (await item.getAttribute('data-identifier'))?.toLowerCase() || '';
|
|
510
510
|
if (identifier === email.toLowerCase()) {
|
|
511
511
|
await item.click();
|
|
512
512
|
await randomDelay(150, 320);
|
|
@@ -516,9 +516,9 @@ export class AuthManager {
|
|
|
516
516
|
}
|
|
517
517
|
// Click "Use another account"
|
|
518
518
|
await this.clickText(page, [
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
519
|
+
'Use another account',
|
|
520
|
+
'Weiteres Konto hinzufügen',
|
|
521
|
+
'Anderes Konto verwenden',
|
|
522
522
|
]);
|
|
523
523
|
await randomDelay(150, 320);
|
|
524
524
|
return false;
|
|
@@ -533,9 +533,9 @@ export class AuthManager {
|
|
|
533
533
|
* Fill email identifier field with human-like typing
|
|
534
534
|
*/
|
|
535
535
|
async fillIdentifier(page, email) {
|
|
536
|
-
log.info(
|
|
536
|
+
log.info(' 📧 Looking for email field...');
|
|
537
537
|
const emailSelectors = [
|
|
538
|
-
|
|
538
|
+
'input#identifierId',
|
|
539
539
|
"input[name='identifier']",
|
|
540
540
|
"input[type='email']",
|
|
541
541
|
];
|
|
@@ -544,7 +544,7 @@ export class AuthManager {
|
|
|
544
544
|
for (const selector of emailSelectors) {
|
|
545
545
|
try {
|
|
546
546
|
const candidate = await page.waitForSelector(selector, {
|
|
547
|
-
state:
|
|
547
|
+
state: 'attached',
|
|
548
548
|
timeout: 3000,
|
|
549
549
|
});
|
|
550
550
|
if (!candidate)
|
|
@@ -567,7 +567,7 @@ export class AuthManager {
|
|
|
567
567
|
}
|
|
568
568
|
}
|
|
569
569
|
if (!emailField || !emailSelector) {
|
|
570
|
-
log.warning(
|
|
570
|
+
log.warning(' ℹ️ No visible email field found (likely pre-filled)');
|
|
571
571
|
log.info(` 📍 Current URL: ${page.url().slice(0, 100)}`);
|
|
572
572
|
return false;
|
|
573
573
|
}
|
|
@@ -594,22 +594,23 @@ export class AuthManager {
|
|
|
594
594
|
await emailField.focus();
|
|
595
595
|
}
|
|
596
596
|
catch {
|
|
597
|
-
log.error(
|
|
597
|
+
log.error(' ❌ Failed to focus email field');
|
|
598
598
|
return false;
|
|
599
599
|
}
|
|
600
600
|
}
|
|
601
601
|
// ✅ FASTER: Programmer typing speed (90-120 WPM from config)
|
|
602
602
|
log.info(` ⌨️ Typing email: ${this.maskEmail(email)}`);
|
|
603
603
|
try {
|
|
604
|
-
const wpm = CONFIG.typingWpmMin +
|
|
604
|
+
const wpm = CONFIG.typingWpmMin +
|
|
605
|
+
Math.floor(Math.random() * (CONFIG.typingWpmMax - CONFIG.typingWpmMin + 1));
|
|
605
606
|
await humanType(page, emailSelector, email, { wpm, withTypos: false });
|
|
606
|
-
log.success(
|
|
607
|
+
log.success(' ✅ Email typed successfully');
|
|
607
608
|
}
|
|
608
609
|
catch (error) {
|
|
609
610
|
log.error(` ❌ Typing failed: ${error}`);
|
|
610
611
|
try {
|
|
611
612
|
await page.fill(emailSelector, email);
|
|
612
|
-
log.success(
|
|
613
|
+
log.success(' ✅ Filled email using fallback');
|
|
613
614
|
}
|
|
614
615
|
catch {
|
|
615
616
|
return false;
|
|
@@ -618,11 +619,11 @@ export class AuthManager {
|
|
|
618
619
|
// Human "thinking" pause before clicking Next
|
|
619
620
|
await randomDelay(400, 1200);
|
|
620
621
|
// Click Next button
|
|
621
|
-
log.info(
|
|
622
|
+
log.info(' 🔘 Looking for Next button...');
|
|
622
623
|
const nextSelectors = [
|
|
623
624
|
"button:has-text('Next')",
|
|
624
625
|
"button:has-text('Weiter')",
|
|
625
|
-
|
|
626
|
+
'#identifierNext',
|
|
626
627
|
];
|
|
627
628
|
let nextClicked = false;
|
|
628
629
|
for (const selector of nextSelectors) {
|
|
@@ -640,19 +641,19 @@ export class AuthManager {
|
|
|
640
641
|
}
|
|
641
642
|
}
|
|
642
643
|
if (!nextClicked) {
|
|
643
|
-
log.warning(
|
|
644
|
-
await emailField.press(
|
|
644
|
+
log.warning(' ⚠️ Button not found, pressing Enter');
|
|
645
|
+
await emailField.press('Enter');
|
|
645
646
|
}
|
|
646
647
|
// Variable delay
|
|
647
648
|
await randomDelay(800, 1500);
|
|
648
|
-
log.success(
|
|
649
|
+
log.success(' ✅ Email step complete');
|
|
649
650
|
return true;
|
|
650
651
|
}
|
|
651
652
|
/**
|
|
652
653
|
* Fill password field with human-like typing
|
|
653
654
|
*/
|
|
654
655
|
async fillPassword(page, password) {
|
|
655
|
-
log.info(
|
|
656
|
+
log.info(' 🔐 Looking for password field...');
|
|
656
657
|
const passwordSelectors = ["input[name='Passwd']", "input[type='password']"];
|
|
657
658
|
let passwordSelector = null;
|
|
658
659
|
let passwordField = null;
|
|
@@ -691,13 +692,14 @@ export class AuthManager {
|
|
|
691
692
|
await realisticClick(page, passwordSelector, false);
|
|
692
693
|
}
|
|
693
694
|
// ✅ FASTER: Programmer typing speed (90-120 WPM from config)
|
|
694
|
-
log.info(
|
|
695
|
+
log.info(' ⌨️ Typing password...');
|
|
695
696
|
try {
|
|
696
|
-
const wpm = CONFIG.typingWpmMin +
|
|
697
|
+
const wpm = CONFIG.typingWpmMin +
|
|
698
|
+
Math.floor(Math.random() * (CONFIG.typingWpmMax - CONFIG.typingWpmMin + 1));
|
|
697
699
|
if (passwordSelector) {
|
|
698
700
|
await humanType(page, passwordSelector, password, { wpm, withTypos: false });
|
|
699
701
|
}
|
|
700
|
-
log.success(
|
|
702
|
+
log.success(' ✅ Password typed successfully');
|
|
701
703
|
}
|
|
702
704
|
catch (error) {
|
|
703
705
|
log.error(` ❌ Typing failed: ${error}`);
|
|
@@ -706,11 +708,11 @@ export class AuthManager {
|
|
|
706
708
|
// Human "review" pause before submitting password
|
|
707
709
|
await randomDelay(300, 1000);
|
|
708
710
|
// Click Next button
|
|
709
|
-
log.info(
|
|
711
|
+
log.info(' 🔘 Looking for Next button...');
|
|
710
712
|
const pwdNextSelectors = [
|
|
711
713
|
"button:has-text('Next')",
|
|
712
714
|
"button:has-text('Weiter')",
|
|
713
|
-
|
|
715
|
+
'#passwordNext',
|
|
714
716
|
];
|
|
715
717
|
let pwdNextClicked = false;
|
|
716
718
|
for (const selector of pwdNextSelectors) {
|
|
@@ -728,12 +730,12 @@ export class AuthManager {
|
|
|
728
730
|
}
|
|
729
731
|
}
|
|
730
732
|
if (!pwdNextClicked) {
|
|
731
|
-
log.warning(
|
|
732
|
-
await passwordField.press(
|
|
733
|
+
log.warning(' ⚠️ Button not found, pressing Enter');
|
|
734
|
+
await passwordField.press('Enter');
|
|
733
735
|
}
|
|
734
736
|
// Variable delay
|
|
735
737
|
await randomDelay(800, 1500);
|
|
736
|
-
log.success(
|
|
738
|
+
log.success(' ✅ Password step complete');
|
|
737
739
|
return true;
|
|
738
740
|
}
|
|
739
741
|
/**
|
|
@@ -760,14 +762,14 @@ export class AuthManager {
|
|
|
760
762
|
* Mask email for logging
|
|
761
763
|
*/
|
|
762
764
|
maskEmail(email) {
|
|
763
|
-
if (!email.includes(
|
|
764
|
-
return
|
|
765
|
+
if (!email.includes('@')) {
|
|
766
|
+
return '***';
|
|
765
767
|
}
|
|
766
|
-
const [name, domain] = email.split(
|
|
768
|
+
const [name, domain] = email.split('@');
|
|
767
769
|
if (name.length <= 2) {
|
|
768
|
-
return `${
|
|
770
|
+
return `${'*'.repeat(name.length)}@${domain}`;
|
|
769
771
|
}
|
|
770
|
-
return `${name[0]}${
|
|
772
|
+
return `${name[0]}${'*'.repeat(name.length - 2)}${name[name.length - 1]}@${domain}`;
|
|
771
773
|
}
|
|
772
774
|
// ============================================================================
|
|
773
775
|
// Additional Helper Methods
|
|
@@ -778,7 +780,7 @@ export class AuthManager {
|
|
|
778
780
|
async loadAuthState(context, statePath) {
|
|
779
781
|
try {
|
|
780
782
|
// Read state.json
|
|
781
|
-
const stateData = await fs.readFile(statePath, { encoding:
|
|
783
|
+
const stateData = await fs.readFile(statePath, { encoding: 'utf-8' });
|
|
782
784
|
const state = JSON.parse(stateData);
|
|
783
785
|
// Add cookies to context
|
|
784
786
|
if (state.cookies) {
|
|
@@ -812,7 +814,7 @@ export class AuthManager {
|
|
|
812
814
|
* If not provided, defaults to true (visible) for setup
|
|
813
815
|
*/
|
|
814
816
|
async performSetup(sendProgress, overrideHeadless) {
|
|
815
|
-
const { chromium } = await import(
|
|
817
|
+
const { chromium } = await import('patchright');
|
|
816
818
|
// Determine headless mode: override or default to true (visible for setup)
|
|
817
819
|
// overrideHeadless contains show_browser value (true = show, false = hide)
|
|
818
820
|
const shouldShowBrowser = overrideHeadless !== undefined ? overrideHeadless : true;
|
|
@@ -821,29 +823,29 @@ export class AuthManager {
|
|
|
821
823
|
const statePath = await this.getValidStatePath();
|
|
822
824
|
const isAuthenticated = statePath !== null;
|
|
823
825
|
if (isAuthenticated) {
|
|
824
|
-
log.info(
|
|
826
|
+
log.info('✅ Already authenticated, skipping setup');
|
|
825
827
|
log.info(" Use 're_auth' tool to switch accounts or re-authenticate");
|
|
826
|
-
await sendProgress?.(
|
|
828
|
+
await sendProgress?.('Already authenticated!', 10, 10);
|
|
827
829
|
return true;
|
|
828
830
|
}
|
|
829
|
-
log.info(
|
|
830
|
-
await sendProgress?.(
|
|
831
|
-
log.info(
|
|
831
|
+
log.info('🔄 Preparing for first-time authentication...');
|
|
832
|
+
await sendProgress?.('Preparing browser...', 1, 10);
|
|
833
|
+
log.info('🚀 Launching persistent browser for interactive setup...');
|
|
832
834
|
log.info(` 📍 Profile: ${CONFIG.chromeProfileDir}`);
|
|
833
|
-
await sendProgress?.(
|
|
835
|
+
await sendProgress?.('Launching persistent browser...', 2, 10);
|
|
834
836
|
// ✅ CRITICAL FIX: Use launchPersistentContext (same as runtime!)
|
|
835
837
|
// This ensures session cookies persist correctly
|
|
836
838
|
const context = await chromium.launchPersistentContext(CONFIG.chromeProfileDir, {
|
|
837
839
|
headless: !shouldShowBrowser, // Use override or default to visible for setup
|
|
838
|
-
channel:
|
|
840
|
+
channel: 'chrome',
|
|
839
841
|
viewport: CONFIG.viewport,
|
|
840
|
-
locale:
|
|
841
|
-
timezoneId:
|
|
842
|
+
locale: 'en-US',
|
|
843
|
+
timezoneId: 'Europe/Berlin',
|
|
842
844
|
args: [
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
845
|
+
'--disable-blink-features=AutomationControlled',
|
|
846
|
+
'--disable-dev-shm-usage',
|
|
847
|
+
'--no-first-run',
|
|
848
|
+
'--no-default-browser-check',
|
|
847
849
|
],
|
|
848
850
|
});
|
|
849
851
|
// Get or create a page
|
|
@@ -854,17 +856,17 @@ export class AuthManager {
|
|
|
854
856
|
if (loginSuccess) {
|
|
855
857
|
// ✅ Save browser state to state.json (for validation & backup)
|
|
856
858
|
// Chrome ALSO saves everything to the persistent profile automatically!
|
|
857
|
-
await sendProgress?.(
|
|
859
|
+
await sendProgress?.('Saving authentication state...', 9, 10);
|
|
858
860
|
await this.saveBrowserState(context, page);
|
|
859
861
|
// ✅ CRITICAL FIX: Wait for Chrome to flush profile to disk
|
|
860
862
|
// Windows needs time to write persistent data (cookies, cache, session storage, etc.)
|
|
861
863
|
// Without this delay, chrome_profile/ folder remains empty!
|
|
862
|
-
log.info(
|
|
864
|
+
log.info('⏳ Waiting for Chrome to finalize profile writes (5 seconds)...');
|
|
863
865
|
await page.waitForTimeout(5000); // 5 seconds buffer for Windows filesystem
|
|
864
|
-
log.success(
|
|
866
|
+
log.success('✅ Setup complete - authentication saved to:');
|
|
865
867
|
log.success(` 📄 State file: ${this.stateFilePath}`);
|
|
866
868
|
log.success(` 📁 Chrome profile: ${CONFIG.chromeProfileDir}`);
|
|
867
|
-
log.info(
|
|
869
|
+
log.info('💡 Session cookies will now persist across restarts!');
|
|
868
870
|
}
|
|
869
871
|
// Close persistent context
|
|
870
872
|
await context.close();
|
|
@@ -889,13 +891,13 @@ export class AuthManager {
|
|
|
889
891
|
* Use this BEFORE authenticating a new account!
|
|
890
892
|
*/
|
|
891
893
|
async clearAllAuthData() {
|
|
892
|
-
log.warning(
|
|
894
|
+
log.warning('🗑️ Clearing ALL authentication data for account switch...');
|
|
893
895
|
let deletedCount = 0;
|
|
894
896
|
// 1. Delete all state files in browser_state_dir
|
|
895
897
|
try {
|
|
896
898
|
const files = await fs.readdir(CONFIG.browserStateDir);
|
|
897
899
|
for (const file of files) {
|
|
898
|
-
if (file.endsWith(
|
|
900
|
+
if (file.endsWith('.json')) {
|
|
899
901
|
await fs.unlink(path.join(CONFIG.browserStateDir, file));
|
|
900
902
|
log.info(` ✅ Deleted: ${file}`);
|
|
901
903
|
deletedCount++;
|
|
@@ -919,7 +921,7 @@ export class AuthManager {
|
|
|
919
921
|
log.warning(` ⚠️ Could not delete Chrome profile: ${error}`);
|
|
920
922
|
}
|
|
921
923
|
if (deletedCount === 0) {
|
|
922
|
-
log.info(
|
|
924
|
+
log.info(' ℹ️ No old auth data found (already clean)');
|
|
923
925
|
}
|
|
924
926
|
else {
|
|
925
927
|
log.success(`✅ All auth data cleared (${deletedCount} items) - ready for new account!`);
|
|
@@ -942,7 +944,7 @@ export class AuthManager {
|
|
|
942
944
|
catch {
|
|
943
945
|
// File doesn't exist
|
|
944
946
|
}
|
|
945
|
-
log.success(
|
|
947
|
+
log.success('✅ Authentication state cleared');
|
|
946
948
|
return true;
|
|
947
949
|
}
|
|
948
950
|
catch (error) {
|
|
@@ -955,7 +957,7 @@ export class AuthManager {
|
|
|
955
957
|
*/
|
|
956
958
|
async hardResetState() {
|
|
957
959
|
try {
|
|
958
|
-
log.warning(
|
|
960
|
+
log.warning('🧹 Performing HARD RESET of all authentication state...');
|
|
959
961
|
let deletedCount = 0;
|
|
960
962
|
// Delete state file
|
|
961
963
|
try {
|
|
@@ -988,7 +990,7 @@ export class AuthManager {
|
|
|
988
990
|
// Directory doesn't exist or empty
|
|
989
991
|
}
|
|
990
992
|
if (deletedCount === 0) {
|
|
991
|
-
log.info(
|
|
993
|
+
log.info(' ℹ️ No state to delete (already clean)');
|
|
992
994
|
}
|
|
993
995
|
else {
|
|
994
996
|
log.success(`✅ Hard reset complete: ${deletedCount} items deleted`);
|