notebooklm-mcp-ultimate 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +420 -0
  3. package/dist/api/batch-execute-client.d.ts +232 -0
  4. package/dist/api/batch-execute-client.d.ts.map +1 -0
  5. package/dist/api/batch-execute-client.js +672 -0
  6. package/dist/api/batch-execute-client.js.map +1 -0
  7. package/dist/api/content-types.d.ts +44 -0
  8. package/dist/api/content-types.d.ts.map +1 -0
  9. package/dist/api/content-types.js +89 -0
  10. package/dist/api/content-types.js.map +1 -0
  11. package/dist/api/csrf-manager.d.ts +94 -0
  12. package/dist/api/csrf-manager.d.ts.map +1 -0
  13. package/dist/api/csrf-manager.js +178 -0
  14. package/dist/api/csrf-manager.js.map +1 -0
  15. package/dist/api/index.d.ts +27 -0
  16. package/dist/api/index.d.ts.map +1 -0
  17. package/dist/api/index.js +56 -0
  18. package/dist/api/index.js.map +1 -0
  19. package/dist/api/operation-poller.d.ts +67 -0
  20. package/dist/api/operation-poller.d.ts.map +1 -0
  21. package/dist/api/operation-poller.js +132 -0
  22. package/dist/api/operation-poller.js.map +1 -0
  23. package/dist/api/request-builder.d.ts +196 -0
  24. package/dist/api/request-builder.d.ts.map +1 -0
  25. package/dist/api/request-builder.js +371 -0
  26. package/dist/api/request-builder.js.map +1 -0
  27. package/dist/api/response-parser.d.ts +124 -0
  28. package/dist/api/response-parser.d.ts.map +1 -0
  29. package/dist/api/response-parser.js +595 -0
  30. package/dist/api/response-parser.js.map +1 -0
  31. package/dist/api/rpc-ids.d.ts +92 -0
  32. package/dist/api/rpc-ids.d.ts.map +1 -0
  33. package/dist/api/rpc-ids.js +138 -0
  34. package/dist/api/rpc-ids.js.map +1 -0
  35. package/dist/api/streaming-chat-client.d.ts +50 -0
  36. package/dist/api/streaming-chat-client.d.ts.map +1 -0
  37. package/dist/api/streaming-chat-client.js +198 -0
  38. package/dist/api/streaming-chat-client.js.map +1 -0
  39. package/dist/api/types.d.ts +318 -0
  40. package/dist/api/types.d.ts.map +1 -0
  41. package/dist/api/types.js +22 -0
  42. package/dist/api/types.js.map +1 -0
  43. package/dist/auth/auth-manager.d.ts +163 -0
  44. package/dist/auth/auth-manager.d.ts.map +1 -0
  45. package/dist/auth/auth-manager.js +1055 -0
  46. package/dist/auth/auth-manager.js.map +1 -0
  47. package/dist/auth/cookie-store.d.ts +121 -0
  48. package/dist/auth/cookie-store.d.ts.map +1 -0
  49. package/dist/auth/cookie-store.js +283 -0
  50. package/dist/auth/cookie-store.js.map +1 -0
  51. package/dist/config.d.ts +89 -0
  52. package/dist/config.d.ts.map +1 -0
  53. package/dist/config.js +217 -0
  54. package/dist/config.js.map +1 -0
  55. package/dist/errors.d.ts +26 -0
  56. package/dist/errors.d.ts.map +1 -0
  57. package/dist/errors.js +41 -0
  58. package/dist/errors.js.map +1 -0
  59. package/dist/index.d.ts +32 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +439 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/library/notebook-library.d.ts +79 -0
  64. package/dist/library/notebook-library.d.ts.map +1 -0
  65. package/dist/library/notebook-library.js +296 -0
  66. package/dist/library/notebook-library.js.map +1 -0
  67. package/dist/library/types.d.ts +67 -0
  68. package/dist/library/types.d.ts.map +1 -0
  69. package/dist/library/types.js +8 -0
  70. package/dist/library/types.js.map +1 -0
  71. package/dist/operations/content-operations.d.ts +78 -0
  72. package/dist/operations/content-operations.d.ts.map +1 -0
  73. package/dist/operations/content-operations.js +162 -0
  74. package/dist/operations/content-operations.js.map +1 -0
  75. package/dist/operations/hybrid-executor.d.ts +47 -0
  76. package/dist/operations/hybrid-executor.d.ts.map +1 -0
  77. package/dist/operations/hybrid-executor.js +114 -0
  78. package/dist/operations/hybrid-executor.js.map +1 -0
  79. package/dist/operations/notebook-crud-operations.d.ts +52 -0
  80. package/dist/operations/notebook-crud-operations.d.ts.map +1 -0
  81. package/dist/operations/notebook-crud-operations.js +248 -0
  82. package/dist/operations/notebook-crud-operations.js.map +1 -0
  83. package/dist/operations/research-operations.d.ts +42 -0
  84. package/dist/operations/research-operations.d.ts.map +1 -0
  85. package/dist/operations/research-operations.js +189 -0
  86. package/dist/operations/research-operations.js.map +1 -0
  87. package/dist/operations/source-operations.d.ts +59 -0
  88. package/dist/operations/source-operations.d.ts.map +1 -0
  89. package/dist/operations/source-operations.js +280 -0
  90. package/dist/operations/source-operations.js.map +1 -0
  91. package/dist/operations/studio-operations.d.ts +98 -0
  92. package/dist/operations/studio-operations.d.ts.map +1 -0
  93. package/dist/operations/studio-operations.js +309 -0
  94. package/dist/operations/studio-operations.js.map +1 -0
  95. package/dist/resources/resource-handlers.d.ts +22 -0
  96. package/dist/resources/resource-handlers.d.ts.map +1 -0
  97. package/dist/resources/resource-handlers.js +216 -0
  98. package/dist/resources/resource-handlers.js.map +1 -0
  99. package/dist/session/browser-session.d.ts +113 -0
  100. package/dist/session/browser-session.d.ts.map +1 -0
  101. package/dist/session/browser-session.js +670 -0
  102. package/dist/session/browser-session.js.map +1 -0
  103. package/dist/session/session-manager.d.ts +88 -0
  104. package/dist/session/session-manager.d.ts.map +1 -0
  105. package/dist/session/session-manager.js +314 -0
  106. package/dist/session/session-manager.js.map +1 -0
  107. package/dist/session/shared-context-manager.d.ts +107 -0
  108. package/dist/session/shared-context-manager.d.ts.map +1 -0
  109. package/dist/session/shared-context-manager.js +447 -0
  110. package/dist/session/shared-context-manager.js.map +1 -0
  111. package/dist/tools/definitions/ask-question.d.ts +8 -0
  112. package/dist/tools/definitions/ask-question.d.ts.map +1 -0
  113. package/dist/tools/definitions/ask-question.js +213 -0
  114. package/dist/tools/definitions/ask-question.js.map +1 -0
  115. package/dist/tools/definitions/content-generation.d.ts +52 -0
  116. package/dist/tools/definitions/content-generation.d.ts.map +1 -0
  117. package/dist/tools/definitions/content-generation.js +236 -0
  118. package/dist/tools/definitions/content-generation.js.map +1 -0
  119. package/dist/tools/definitions/notebook-crud.d.ts +9 -0
  120. package/dist/tools/definitions/notebook-crud.d.ts.map +1 -0
  121. package/dist/tools/definitions/notebook-crud.js +156 -0
  122. package/dist/tools/definitions/notebook-crud.js.map +1 -0
  123. package/dist/tools/definitions/notebook-management.d.ts +3 -0
  124. package/dist/tools/definitions/notebook-management.d.ts.map +1 -0
  125. package/dist/tools/definitions/notebook-management.js +243 -0
  126. package/dist/tools/definitions/notebook-management.js.map +1 -0
  127. package/dist/tools/definitions/research.d.ts +23 -0
  128. package/dist/tools/definitions/research.d.ts.map +1 -0
  129. package/dist/tools/definitions/research.js +108 -0
  130. package/dist/tools/definitions/research.js.map +1 -0
  131. package/dist/tools/definitions/session-management.d.ts +3 -0
  132. package/dist/tools/definitions/session-management.d.ts.map +1 -0
  133. package/dist/tools/definitions/session-management.js +41 -0
  134. package/dist/tools/definitions/session-management.js.map +1 -0
  135. package/dist/tools/definitions/source-management.d.ts +39 -0
  136. package/dist/tools/definitions/source-management.d.ts.map +1 -0
  137. package/dist/tools/definitions/source-management.js +224 -0
  138. package/dist/tools/definitions/source-management.js.map +1 -0
  139. package/dist/tools/definitions/studio.d.ts +36 -0
  140. package/dist/tools/definitions/studio.d.ts.map +1 -0
  141. package/dist/tools/definitions/studio.js +153 -0
  142. package/dist/tools/definitions/studio.js.map +1 -0
  143. package/dist/tools/definitions/system.d.ts +3 -0
  144. package/dist/tools/definitions/system.d.ts.map +1 -0
  145. package/dist/tools/definitions/system.js +143 -0
  146. package/dist/tools/definitions/system.js.map +1 -0
  147. package/dist/tools/definitions.d.ts +12 -0
  148. package/dist/tools/definitions.d.ts.map +1 -0
  149. package/dist/tools/definitions.js +36 -0
  150. package/dist/tools/definitions.js.map +1 -0
  151. package/dist/tools/handlers/content-handlers.d.ts +287 -0
  152. package/dist/tools/handlers/content-handlers.d.ts.map +1 -0
  153. package/dist/tools/handlers/content-handlers.js +244 -0
  154. package/dist/tools/handlers/content-handlers.js.map +1 -0
  155. package/dist/tools/handlers/notebook-crud-handlers.d.ts +69 -0
  156. package/dist/tools/handlers/notebook-crud-handlers.d.ts.map +1 -0
  157. package/dist/tools/handlers/notebook-crud-handlers.js +117 -0
  158. package/dist/tools/handlers/notebook-crud-handlers.js.map +1 -0
  159. package/dist/tools/handlers/research-handlers.d.ts +37 -0
  160. package/dist/tools/handlers/research-handlers.d.ts.map +1 -0
  161. package/dist/tools/handlers/research-handlers.js +87 -0
  162. package/dist/tools/handlers/research-handlers.js.map +1 -0
  163. package/dist/tools/handlers/source-handlers.d.ts +52 -0
  164. package/dist/tools/handlers/source-handlers.d.ts.map +1 -0
  165. package/dist/tools/handlers/source-handlers.js +177 -0
  166. package/dist/tools/handlers/source-handlers.js.map +1 -0
  167. package/dist/tools/handlers/studio-handlers.d.ts +125 -0
  168. package/dist/tools/handlers/studio-handlers.d.ts.map +1 -0
  169. package/dist/tools/handlers/studio-handlers.js +183 -0
  170. package/dist/tools/handlers/studio-handlers.js.map +1 -0
  171. package/dist/tools/handlers.d.ts +629 -0
  172. package/dist/tools/handlers.d.ts.map +1 -0
  173. package/dist/tools/handlers.js +833 -0
  174. package/dist/tools/handlers.js.map +1 -0
  175. package/dist/tools/index.d.ts +8 -0
  176. package/dist/tools/index.d.ts.map +1 -0
  177. package/dist/tools/index.js +8 -0
  178. package/dist/tools/index.js.map +1 -0
  179. package/dist/types.d.ts +82 -0
  180. package/dist/types.d.ts.map +1 -0
  181. package/dist/types.js +5 -0
  182. package/dist/types.js.map +1 -0
  183. package/dist/utils/cleanup-manager.d.ts +133 -0
  184. package/dist/utils/cleanup-manager.d.ts.map +1 -0
  185. package/dist/utils/cleanup-manager.js +673 -0
  186. package/dist/utils/cleanup-manager.js.map +1 -0
  187. package/dist/utils/cli-handler.d.ts +16 -0
  188. package/dist/utils/cli-handler.d.ts.map +1 -0
  189. package/dist/utils/cli-handler.js +102 -0
  190. package/dist/utils/cli-handler.js.map +1 -0
  191. package/dist/utils/logger.d.ts +61 -0
  192. package/dist/utils/logger.d.ts.map +1 -0
  193. package/dist/utils/logger.js +92 -0
  194. package/dist/utils/logger.js.map +1 -0
  195. package/dist/utils/page-utils.d.ts +54 -0
  196. package/dist/utils/page-utils.d.ts.map +1 -0
  197. package/dist/utils/page-utils.js +405 -0
  198. package/dist/utils/page-utils.js.map +1 -0
  199. package/dist/utils/settings-manager.d.ts +37 -0
  200. package/dist/utils/settings-manager.d.ts.map +1 -0
  201. package/dist/utils/settings-manager.js +120 -0
  202. package/dist/utils/settings-manager.js.map +1 -0
  203. package/dist/utils/stealth-utils.d.ts +135 -0
  204. package/dist/utils/stealth-utils.d.ts.map +1 -0
  205. package/dist/utils/stealth-utils.js +398 -0
  206. package/dist/utils/stealth-utils.js.map +1 -0
  207. package/package.json +63 -0
@@ -0,0 +1,1055 @@
1
+ /**
2
+ * Authentication Manager for NotebookLM
3
+ *
4
+ * Handles:
5
+ * - Interactive login (headful browser for setup)
6
+ * - Auto-login with credentials (email/password from ENV)
7
+ * - Browser state persistence (cookies + localStorage + sessionStorage)
8
+ * - Cookie expiry validation
9
+ * - State expiry checks (24h file age)
10
+ * - Hard reset for clean start
11
+ *
12
+ * Based on the Python implementation from auth.py
13
+ */
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
+ /**
21
+ * Critical cookie names for Google authentication
22
+ */
23
+ const CRITICAL_COOKIE_NAMES = [
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
+ ];
34
+ export class AuthManager {
35
+ stateFilePath;
36
+ sessionFilePath;
37
+ constructor() {
38
+ this.stateFilePath = path.join(CONFIG.browserStateDir, "state.json");
39
+ this.sessionFilePath = path.join(CONFIG.browserStateDir, "session.json");
40
+ }
41
+ // ============================================================================
42
+ // Browser State Management
43
+ // ============================================================================
44
+ /**
45
+ * Save entire browser state (cookies + localStorage)
46
+ */
47
+ async saveBrowserState(context, page) {
48
+ try {
49
+ // Save storage state (cookies + localStorage + IndexedDB)
50
+ await context.storageState({ path: this.stateFilePath });
51
+ // Also save sessionStorage if page is provided
52
+ if (page) {
53
+ try {
54
+ const sessionStorageData = await page.evaluate(() => {
55
+ // Properly extract sessionStorage as a plain object
56
+ const storage = {};
57
+ // @ts-expect-error - sessionStorage exists in browser context
58
+ for (let i = 0; i < sessionStorage.length; i++) {
59
+ // @ts-expect-error - sessionStorage exists in browser context
60
+ const key = sessionStorage.key(i);
61
+ if (key) {
62
+ // @ts-expect-error - sessionStorage exists in browser context
63
+ storage[key] = sessionStorage.getItem(key) || '';
64
+ }
65
+ }
66
+ return JSON.stringify(storage);
67
+ });
68
+ await fs.writeFile(this.sessionFilePath, sessionStorageData, {
69
+ encoding: "utf-8",
70
+ });
71
+ const entries = Object.keys(JSON.parse(sessionStorageData)).length;
72
+ log.success(`โœ… Browser state saved (incl. sessionStorage: ${entries} entries)`);
73
+ }
74
+ catch (error) {
75
+ log.warning(`โš ๏ธ State saved, but sessionStorage failed: ${error}`);
76
+ }
77
+ }
78
+ else {
79
+ log.success("โœ… Browser state saved");
80
+ }
81
+ return true;
82
+ }
83
+ catch (error) {
84
+ log.error(`โŒ Failed to save browser state: ${error}`);
85
+ return false;
86
+ }
87
+ }
88
+ /**
89
+ * Check if saved browser state exists
90
+ */
91
+ async hasSavedState() {
92
+ try {
93
+ await fs.access(this.stateFilePath);
94
+ return true;
95
+ }
96
+ catch {
97
+ return false;
98
+ }
99
+ }
100
+ /**
101
+ * Get path to saved browser state
102
+ */
103
+ getStatePath() {
104
+ // Synchronous check using imported existsSync
105
+ if (existsSync(this.stateFilePath)) {
106
+ return this.stateFilePath;
107
+ }
108
+ return null;
109
+ }
110
+ /**
111
+ * Get valid state path (checks expiry)
112
+ */
113
+ async getValidStatePath() {
114
+ const statePath = this.getStatePath();
115
+ if (!statePath) {
116
+ return null;
117
+ }
118
+ try {
119
+ if (await this.isStateExpired()) {
120
+ log.warning("โš ๏ธ Saved state is expired (>24h old)");
121
+ log.info("๐Ÿ’ก Run setup_auth tool to re-authenticate");
122
+ return null;
123
+ }
124
+ return statePath;
125
+ }
126
+ catch {
127
+ // File was deleted between existsSync check and read (TOCTOU race condition)
128
+ log.warning("โš ๏ธ State file disappeared during validation");
129
+ return null;
130
+ }
131
+ }
132
+ /**
133
+ * Load sessionStorage from file
134
+ */
135
+ async loadSessionStorage() {
136
+ try {
137
+ const data = await fs.readFile(this.sessionFilePath, { encoding: "utf-8" });
138
+ const sessionData = JSON.parse(data);
139
+ log.success(`โœ… Loaded sessionStorage (${Object.keys(sessionData).length} entries)`);
140
+ return sessionData;
141
+ }
142
+ catch (error) {
143
+ log.warning(`โš ๏ธ Failed to load sessionStorage: ${error}`);
144
+ return null;
145
+ }
146
+ }
147
+ // ============================================================================
148
+ // Cookie Validation
149
+ // ============================================================================
150
+ /**
151
+ * Validate if saved state is still valid
152
+ */
153
+ async validateState(context) {
154
+ try {
155
+ const cookies = await context.cookies();
156
+ if (cookies.length === 0) {
157
+ log.warning("โš ๏ธ No cookies found in state");
158
+ return false;
159
+ }
160
+ // Check for Google auth cookies
161
+ const googleCookies = cookies.filter((c) => c.domain.includes("google.com"));
162
+ if (googleCookies.length === 0) {
163
+ log.warning("โš ๏ธ No Google cookies found");
164
+ return false;
165
+ }
166
+ // Check if important cookies are expired
167
+ const currentTime = Date.now() / 1000;
168
+ for (const cookie of googleCookies) {
169
+ const expires = cookie.expires ?? -1;
170
+ if (expires !== -1 && expires < currentTime) {
171
+ log.warning(`โš ๏ธ Cookie '${cookie.name}' has expired`);
172
+ return false;
173
+ }
174
+ }
175
+ log.success("โœ… State validation passed");
176
+ return true;
177
+ }
178
+ catch (error) {
179
+ log.warning(`โš ๏ธ State validation failed: ${error}`);
180
+ return false;
181
+ }
182
+ }
183
+ /**
184
+ * Validate if critical authentication cookies are still valid
185
+ */
186
+ async validateCookiesExpiry(context) {
187
+ try {
188
+ const cookies = await context.cookies();
189
+ if (cookies.length === 0) {
190
+ log.warning("โš ๏ธ No cookies found");
191
+ return false;
192
+ }
193
+ // Find critical cookies
194
+ const criticalCookies = cookies.filter((c) => CRITICAL_COOKIE_NAMES.includes(c.name));
195
+ if (criticalCookies.length === 0) {
196
+ log.warning("โš ๏ธ No critical auth cookies found");
197
+ return false;
198
+ }
199
+ // Check expiration for each critical cookie
200
+ const currentTime = Date.now() / 1000;
201
+ const expiredCookies = [];
202
+ for (const cookie of criticalCookies) {
203
+ const expires = cookie.expires ?? -1;
204
+ // -1 means session cookie (valid until browser closes)
205
+ if (expires === -1) {
206
+ continue;
207
+ }
208
+ // Check if cookie is expired
209
+ if (expires < currentTime) {
210
+ expiredCookies.push(cookie.name);
211
+ }
212
+ }
213
+ if (expiredCookies.length > 0) {
214
+ log.warning(`โš ๏ธ Expired cookies: ${expiredCookies.join(", ")}`);
215
+ return false;
216
+ }
217
+ log.success(`โœ… All ${criticalCookies.length} critical cookies are valid`);
218
+ return true;
219
+ }
220
+ catch (error) {
221
+ log.warning(`โš ๏ธ Cookie validation failed: ${error}`);
222
+ return false;
223
+ }
224
+ }
225
+ /**
226
+ * Check if the saved state file is too old (>24 hours)
227
+ */
228
+ async isStateExpired() {
229
+ try {
230
+ const stats = await fs.stat(this.stateFilePath);
231
+ const fileAgeSeconds = (Date.now() - stats.mtimeMs) / 1000;
232
+ const maxAgeSeconds = 24 * 60 * 60; // 24 hours
233
+ if (fileAgeSeconds > maxAgeSeconds) {
234
+ const hoursOld = fileAgeSeconds / 3600;
235
+ log.warning(`โš ๏ธ Saved state is ${hoursOld.toFixed(1)}h old (max: 24h)`);
236
+ return true;
237
+ }
238
+ return false;
239
+ }
240
+ catch {
241
+ return true; // File doesn't exist = expired
242
+ }
243
+ }
244
+ // ============================================================================
245
+ // Interactive Login
246
+ // ============================================================================
247
+ /**
248
+ * Perform interactive login
249
+ * User will see a browser window and login manually
250
+ *
251
+ * SIMPLE & RELIABLE: Just wait for URL to change to notebooklm.google.com
252
+ */
253
+ async performLogin(page, sendProgress) {
254
+ try {
255
+ log.info("๐ŸŒ Opening Google login page...");
256
+ log.warning("๐Ÿ“ Please login to your Google account");
257
+ log.warning("โณ Browser will close automatically once you reach NotebookLM");
258
+ log.info("");
259
+ // Progress: Navigating
260
+ await sendProgress?.("Navigating to Google login...", 3, 10);
261
+ // Navigate to Google login (redirects to NotebookLM after auth)
262
+ await page.goto(NOTEBOOKLM_AUTH_URL, { timeout: 60000 });
263
+ // Progress: Waiting for login
264
+ await sendProgress?.("Waiting for manual login (up to 10 minutes)...", 4, 10);
265
+ // Wait for user to complete login
266
+ log.warning("โณ Waiting for login (up to 10 minutes)...");
267
+ const checkIntervalMs = 1000; // Check every 1 second
268
+ const maxAttempts = 600; // 10 minutes total
269
+ let lastProgressUpdate = 0;
270
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
271
+ try {
272
+ const currentUrl = page.url();
273
+ const elapsedSeconds = Math.floor(attempt * (checkIntervalMs / 1000));
274
+ // Send progress every 10 seconds
275
+ if (elapsedSeconds - lastProgressUpdate >= 10) {
276
+ lastProgressUpdate = elapsedSeconds;
277
+ const progressStep = Math.min(8, 4 + Math.floor(elapsedSeconds / 60));
278
+ await sendProgress?.(`Waiting for login... (${elapsedSeconds}s elapsed)`, progressStep, 10);
279
+ }
280
+ // โœ… SIMPLE: Check if we're on NotebookLM (any path!)
281
+ if (currentUrl.startsWith("https://notebooklm.google.com/")) {
282
+ await sendProgress?.("Login successful! NotebookLM detected!", 9, 10);
283
+ log.success("โœ… Login successful! NotebookLM URL detected.");
284
+ log.success(`โœ… Current URL: ${currentUrl}`);
285
+ // Short wait to ensure page is loaded
286
+ await page.waitForTimeout(2000);
287
+ return true;
288
+ }
289
+ // Still on accounts.google.com - log periodically
290
+ if (currentUrl.includes("accounts.google.com") && attempt % 30 === 0 && attempt > 0) {
291
+ log.warning(`โณ Still waiting... (${elapsedSeconds}s elapsed)`);
292
+ }
293
+ await page.waitForTimeout(checkIntervalMs);
294
+ }
295
+ catch {
296
+ await page.waitForTimeout(checkIntervalMs);
297
+ continue;
298
+ }
299
+ }
300
+ // Timeout reached - final check
301
+ const currentUrl = page.url();
302
+ if (currentUrl.startsWith("https://notebooklm.google.com/")) {
303
+ await sendProgress?.("Login successful (detected on timeout check)!", 9, 10);
304
+ log.success("โœ… Login successful (detected on timeout check)");
305
+ return true;
306
+ }
307
+ log.error("โŒ Login verification failed - timeout reached");
308
+ log.warning(`Current URL: ${currentUrl}`);
309
+ return false;
310
+ }
311
+ catch (error) {
312
+ log.error(`โŒ Login failed: ${error}`);
313
+ return false;
314
+ }
315
+ }
316
+ // ============================================================================
317
+ // Auto-Login with Credentials
318
+ // ============================================================================
319
+ /**
320
+ * Attempt to authenticate using configured credentials
321
+ */
322
+ async loginWithCredentials(context, page, email, password) {
323
+ const maskedEmail = this.maskEmail(email);
324
+ log.warning(`๐Ÿ” Attempting automatic login for ${maskedEmail}...`);
325
+ // Log browser visibility
326
+ if (!CONFIG.headless) {
327
+ log.info(" ๐Ÿ‘๏ธ Browser is VISIBLE for debugging");
328
+ }
329
+ else {
330
+ log.info(" ๐Ÿ™ˆ Browser is HEADLESS (invisible)");
331
+ }
332
+ log.info(` ๐ŸŒ Navigating to Google login...`);
333
+ try {
334
+ await page.goto(NOTEBOOKLM_AUTH_URL, {
335
+ waitUntil: "domcontentloaded",
336
+ timeout: CONFIG.browserTimeout,
337
+ });
338
+ log.success(` โœ… Page loaded: ${page.url().slice(0, 80)}...`);
339
+ }
340
+ catch (_error) {
341
+ log.warning(` โš ๏ธ Page load timeout (continuing anyway)`);
342
+ }
343
+ const deadline = Date.now() + CONFIG.autoLoginTimeoutMs;
344
+ log.info(` โฐ Auto-login timeout: ${CONFIG.autoLoginTimeoutMs / 1000}s`);
345
+ // Already on NotebookLM?
346
+ log.info(" ๐Ÿ” Checking if already authenticated...");
347
+ if (await this.waitForNotebook(page, CONFIG.autoLoginTimeoutMs)) {
348
+ log.success("โœ… Already authenticated");
349
+ await this.saveBrowserState(context, page);
350
+ return true;
351
+ }
352
+ log.warning(" โŒ Not authenticated yet, proceeding with login...");
353
+ // Handle possible account chooser
354
+ log.info(" ๐Ÿ” Checking for account chooser...");
355
+ if (await this.handleAccountChooser(page, email)) {
356
+ log.success(" โœ… Account selected from chooser");
357
+ if (await this.waitForNotebook(page, CONFIG.autoLoginTimeoutMs)) {
358
+ log.success("โœ… Automatic login successful");
359
+ await this.saveBrowserState(context, page);
360
+ return true;
361
+ }
362
+ }
363
+ // Email step
364
+ log.info(" ๐Ÿ“ง Entering email address...");
365
+ if (!(await this.fillIdentifier(page, email))) {
366
+ if (await this.waitForNotebook(page, CONFIG.autoLoginTimeoutMs)) {
367
+ log.success("โœ… Automatic login successful");
368
+ await this.saveBrowserState(context, page);
369
+ return true;
370
+ }
371
+ log.warning("โš ๏ธ Email input not detected");
372
+ }
373
+ // Password step (wait until visible)
374
+ let waitAttempts = 0;
375
+ log.warning(" โณ Waiting for password page to load...");
376
+ while (Date.now() < deadline && !(await this.fillPassword(page, password))) {
377
+ waitAttempts++;
378
+ // Log every 10 seconds (20 attempts * 0.5s)
379
+ if (waitAttempts % 20 === 0) {
380
+ const secondsWaited = waitAttempts * 0.5;
381
+ const secondsRemaining = (deadline - Date.now()) / 1000;
382
+ log.warning(` โณ Still waiting for password field... (${secondsWaited}s elapsed, ${secondsRemaining.toFixed(0)}s remaining)`);
383
+ log.info(` ๐Ÿ“ Current URL: ${page.url().slice(0, 100)}`);
384
+ }
385
+ if (page.url().includes("challenge")) {
386
+ log.warning("โš ๏ธ Additional verification required (Google challenge page).");
387
+ return false;
388
+ }
389
+ await page.waitForTimeout(500);
390
+ }
391
+ // Wait for Google redirect after login
392
+ log.info(" ๐Ÿ”„ Waiting for Google redirect to NotebookLM...");
393
+ if (await this.waitForRedirectAfterLogin(page, deadline)) {
394
+ log.success("โœ… Automatic login successful");
395
+ await this.saveBrowserState(context, page);
396
+ return true;
397
+ }
398
+ // Login failed - diagnose
399
+ log.error("โŒ Automatic login timed out");
400
+ // Take screenshot for debugging
401
+ try {
402
+ const screenshotPath = path.join(CONFIG.dataDir, `login_failed_${Date.now()}.png`);
403
+ await page.screenshot({ path: screenshotPath });
404
+ log.info(` ๐Ÿ“ธ Screenshot saved: ${screenshotPath}`);
405
+ }
406
+ catch (error) {
407
+ log.warning(` โš ๏ธ Could not save screenshot: ${error}`);
408
+ }
409
+ // Diagnose specific failure reason
410
+ const currentUrl = page.url();
411
+ log.warning(" ๐Ÿ” Diagnosing failure...");
412
+ if (currentUrl.includes("accounts.google.com")) {
413
+ if (currentUrl.includes("/signin/identifier")) {
414
+ log.error(" โŒ Still on email page - email input might have failed");
415
+ log.info(" ๐Ÿ’ก Check if email is correct in .env");
416
+ }
417
+ else if (currentUrl.includes("/challenge")) {
418
+ log.error(" โŒ Google requires additional verification (2FA, CAPTCHA, suspicious login)");
419
+ log.info(" ๐Ÿ’ก Try logging in manually first: use setup_auth tool");
420
+ }
421
+ else if (currentUrl.includes("/pwd") || currentUrl.includes("/password")) {
422
+ log.error(" โŒ Still on password page - password input might have failed");
423
+ log.info(" ๐Ÿ’ก Check if password is correct in .env");
424
+ }
425
+ else {
426
+ log.error(` โŒ Stuck on Google accounts page: ${currentUrl.slice(0, 80)}...`);
427
+ }
428
+ }
429
+ else if (currentUrl.includes("notebooklm.google.com")) {
430
+ log.warning(" โš ๏ธ Reached NotebookLM but couldn't detect successful login");
431
+ log.info(" ๐Ÿ’ก This might be a timing issue - try again");
432
+ }
433
+ else {
434
+ log.error(` โŒ Unexpected page: ${currentUrl.slice(0, 80)}...`);
435
+ }
436
+ return false;
437
+ }
438
+ // ============================================================================
439
+ // Helper Methods
440
+ // ============================================================================
441
+ /**
442
+ * Wait for Google to redirect to NotebookLM after successful login (SIMPLE & RELIABLE)
443
+ *
444
+ * Just checks if URL changes to notebooklm.google.com - no complex UI element searching!
445
+ * Matches the simplified approach used in performLogin().
446
+ */
447
+ async waitForRedirectAfterLogin(page, deadline) {
448
+ log.info(" โณ Waiting for redirect to NotebookLM...");
449
+ while (Date.now() < deadline) {
450
+ try {
451
+ const currentUrl = page.url();
452
+ // Simple check: Are we on NotebookLM?
453
+ if (currentUrl.startsWith("https://notebooklm.google.com/")) {
454
+ log.success(" โœ… NotebookLM URL detected!");
455
+ // Short wait to ensure page is loaded
456
+ await page.waitForTimeout(2000);
457
+ return true;
458
+ }
459
+ }
460
+ catch {
461
+ // Ignore errors
462
+ }
463
+ await page.waitForTimeout(500);
464
+ }
465
+ log.error(" โŒ Redirect timeout - NotebookLM URL not reached");
466
+ return false;
467
+ }
468
+ /**
469
+ * Wait for NotebookLM to load (SIMPLE & RELIABLE)
470
+ *
471
+ * Just checks if URL starts with notebooklm.google.com - no complex UI element searching!
472
+ * Matches the simplified approach used in performLogin().
473
+ */
474
+ async waitForNotebook(page, timeoutMs) {
475
+ const endTime = Date.now() + timeoutMs;
476
+ while (Date.now() < endTime) {
477
+ try {
478
+ const currentUrl = page.url();
479
+ // Simple check: Are we on NotebookLM?
480
+ if (currentUrl.startsWith("https://notebooklm.google.com/")) {
481
+ log.success(" โœ… NotebookLM URL detected");
482
+ return true;
483
+ }
484
+ }
485
+ catch {
486
+ // Ignore errors
487
+ }
488
+ await page.waitForTimeout(1000);
489
+ }
490
+ return false;
491
+ }
492
+ /**
493
+ * Handle possible account chooser
494
+ */
495
+ async handleAccountChooser(page, email) {
496
+ try {
497
+ const chooser = await page.$$("div[data-identifier], li[data-identifier]");
498
+ if (chooser.length > 0) {
499
+ for (const item of chooser) {
500
+ const identifier = (await item.getAttribute("data-identifier"))?.toLowerCase() || "";
501
+ if (identifier === email.toLowerCase()) {
502
+ await item.click();
503
+ await randomDelay(150, 320);
504
+ await page.waitForTimeout(500);
505
+ return true;
506
+ }
507
+ }
508
+ // Click "Use another account"
509
+ await this.clickText(page, [
510
+ "Use another account",
511
+ "Weiteres Konto hinzufรผgen",
512
+ "Anderes Konto verwenden",
513
+ ]);
514
+ await randomDelay(150, 320);
515
+ return false;
516
+ }
517
+ return false;
518
+ }
519
+ catch {
520
+ return false;
521
+ }
522
+ }
523
+ /**
524
+ * Fill email identifier field with human-like typing
525
+ */
526
+ async fillIdentifier(page, email) {
527
+ log.info(" ๐Ÿ“ง Looking for email field...");
528
+ const emailSelectors = [
529
+ "input#identifierId",
530
+ "input[name='identifier']",
531
+ "input[type='email']",
532
+ ];
533
+ let emailSelector = null;
534
+ let emailField = null;
535
+ for (const selector of emailSelectors) {
536
+ try {
537
+ const candidate = await page.waitForSelector(selector, {
538
+ state: "attached",
539
+ timeout: 3000,
540
+ });
541
+ if (!candidate)
542
+ continue;
543
+ try {
544
+ if (!(await candidate.isVisible())) {
545
+ continue; // Hidden field
546
+ }
547
+ }
548
+ catch {
549
+ continue;
550
+ }
551
+ emailField = candidate;
552
+ emailSelector = selector;
553
+ log.success(` โœ… Email field visible: ${selector}`);
554
+ break;
555
+ }
556
+ catch {
557
+ continue;
558
+ }
559
+ }
560
+ if (!emailField || !emailSelector) {
561
+ log.warning(" โ„น๏ธ No visible email field found (likely pre-filled)");
562
+ log.info(` ๐Ÿ“ Current URL: ${page.url().slice(0, 100)}`);
563
+ return false;
564
+ }
565
+ // Human-like mouse movement to field
566
+ try {
567
+ const box = await emailField.boundingBox();
568
+ if (box) {
569
+ const targetX = box.x + box.width / 2;
570
+ const targetY = box.y + box.height / 2;
571
+ await randomMouseMovement(page, targetX, targetY);
572
+ await randomDelay(200, 500);
573
+ }
574
+ }
575
+ catch {
576
+ // Ignore errors
577
+ }
578
+ // Click to focus
579
+ try {
580
+ await realisticClick(page, emailSelector, false);
581
+ }
582
+ catch (error) {
583
+ log.warning(` โš ๏ธ Could not click email field (${error}); trying direct focus`);
584
+ try {
585
+ await emailField.focus();
586
+ }
587
+ catch {
588
+ log.error(" โŒ Failed to focus email field");
589
+ return false;
590
+ }
591
+ }
592
+ // โœ… FASTER: Programmer typing speed (90-120 WPM from config)
593
+ log.info(` โŒจ๏ธ Typing email: ${this.maskEmail(email)}`);
594
+ try {
595
+ const wpm = CONFIG.typingWpmMin + Math.floor(Math.random() * (CONFIG.typingWpmMax - CONFIG.typingWpmMin + 1));
596
+ await humanType(page, emailSelector, email, { wpm, withTypos: false });
597
+ log.success(" โœ… Email typed successfully");
598
+ }
599
+ catch (error) {
600
+ log.error(` โŒ Typing failed: ${error}`);
601
+ try {
602
+ await page.fill(emailSelector, email);
603
+ log.success(" โœ… Filled email using fallback");
604
+ }
605
+ catch {
606
+ return false;
607
+ }
608
+ }
609
+ // Human "thinking" pause before clicking Next
610
+ await randomDelay(400, 1200);
611
+ // Click Next button
612
+ log.info(" ๐Ÿ”˜ Looking for Next button...");
613
+ const nextSelectors = [
614
+ "button:has-text('Next')",
615
+ "button:has-text('Weiter')",
616
+ "#identifierNext",
617
+ ];
618
+ let nextClicked = false;
619
+ for (const selector of nextSelectors) {
620
+ try {
621
+ const button = await page.locator(selector);
622
+ if ((await button.count()) > 0) {
623
+ await realisticClick(page, selector, true);
624
+ log.success(` โœ… Next button clicked: ${selector}`);
625
+ nextClicked = true;
626
+ break;
627
+ }
628
+ }
629
+ catch {
630
+ continue;
631
+ }
632
+ }
633
+ if (!nextClicked) {
634
+ log.warning(" โš ๏ธ Button not found, pressing Enter");
635
+ await emailField.press("Enter");
636
+ }
637
+ // Variable delay
638
+ await randomDelay(800, 1500);
639
+ log.success(" โœ… Email step complete");
640
+ return true;
641
+ }
642
+ /**
643
+ * Fill password field with human-like typing
644
+ */
645
+ async fillPassword(page, password) {
646
+ log.info(" ๐Ÿ” Looking for password field...");
647
+ const passwordSelectors = ["input[name='Passwd']", "input[type='password']"];
648
+ let passwordSelector = null;
649
+ let passwordField = null;
650
+ for (const selector of passwordSelectors) {
651
+ try {
652
+ passwordField = await page.$(selector);
653
+ if (passwordField) {
654
+ passwordSelector = selector;
655
+ log.success(` โœ… Password field found: ${selector}`);
656
+ break;
657
+ }
658
+ }
659
+ catch {
660
+ continue;
661
+ }
662
+ }
663
+ if (!passwordField) {
664
+ // Not found yet, but don't fail - this is called in a loop
665
+ return false;
666
+ }
667
+ // Human-like mouse movement to field
668
+ try {
669
+ const box = await passwordField.boundingBox();
670
+ if (box) {
671
+ const targetX = box.x + box.width / 2;
672
+ const targetY = box.y + box.height / 2;
673
+ await randomMouseMovement(page, targetX, targetY);
674
+ await randomDelay(300, 700);
675
+ }
676
+ }
677
+ catch {
678
+ // Ignore errors
679
+ }
680
+ // Click to focus
681
+ if (passwordSelector) {
682
+ await realisticClick(page, passwordSelector, false);
683
+ }
684
+ // โœ… FASTER: Programmer typing speed (90-120 WPM from config)
685
+ log.info(" โŒจ๏ธ Typing password...");
686
+ try {
687
+ const wpm = CONFIG.typingWpmMin + Math.floor(Math.random() * (CONFIG.typingWpmMax - CONFIG.typingWpmMin + 1));
688
+ if (passwordSelector) {
689
+ await humanType(page, passwordSelector, password, { wpm, withTypos: false });
690
+ }
691
+ log.success(" โœ… Password typed successfully");
692
+ }
693
+ catch (error) {
694
+ log.error(` โŒ Typing failed: ${error}`);
695
+ return false;
696
+ }
697
+ // Human "review" pause before submitting password
698
+ await randomDelay(300, 1000);
699
+ // Click Next button
700
+ log.info(" ๐Ÿ”˜ Looking for Next button...");
701
+ const pwdNextSelectors = [
702
+ "button:has-text('Next')",
703
+ "button:has-text('Weiter')",
704
+ "#passwordNext",
705
+ ];
706
+ let pwdNextClicked = false;
707
+ for (const selector of pwdNextSelectors) {
708
+ try {
709
+ const button = await page.locator(selector);
710
+ if ((await button.count()) > 0) {
711
+ await realisticClick(page, selector, true);
712
+ log.success(` โœ… Next button clicked: ${selector}`);
713
+ pwdNextClicked = true;
714
+ break;
715
+ }
716
+ }
717
+ catch {
718
+ continue;
719
+ }
720
+ }
721
+ if (!pwdNextClicked) {
722
+ log.warning(" โš ๏ธ Button not found, pressing Enter");
723
+ await passwordField.press("Enter");
724
+ }
725
+ // Variable delay
726
+ await randomDelay(800, 1500);
727
+ log.success(" โœ… Password step complete");
728
+ return true;
729
+ }
730
+ /**
731
+ * Click text element
732
+ */
733
+ async clickText(page, texts) {
734
+ for (const text of texts) {
735
+ const selector = `text="${text}"`;
736
+ try {
737
+ const locator = page.locator(selector);
738
+ if ((await locator.count()) > 0) {
739
+ await realisticClick(page, selector, true);
740
+ await randomDelay(120, 260);
741
+ return true;
742
+ }
743
+ }
744
+ catch {
745
+ continue;
746
+ }
747
+ }
748
+ return false;
749
+ }
750
+ /**
751
+ * Mask email for logging
752
+ */
753
+ maskEmail(email) {
754
+ if (!email.includes("@")) {
755
+ return "***";
756
+ }
757
+ const [name, domain] = email.split("@");
758
+ if (name.length <= 2) {
759
+ return `${"*".repeat(name.length)}@${domain}`;
760
+ }
761
+ return `${name[0]}${"*".repeat(name.length - 2)}${name[name.length - 1]}@${domain}`;
762
+ }
763
+ // ============================================================================
764
+ // Additional Helper Methods
765
+ // ============================================================================
766
+ /**
767
+ * Load authentication state from a specific file path
768
+ */
769
+ async loadAuthState(context, statePath) {
770
+ try {
771
+ // Read state.json
772
+ const stateData = await fs.readFile(statePath, { encoding: "utf-8" });
773
+ const state = JSON.parse(stateData);
774
+ // Add cookies to context
775
+ if (state.cookies) {
776
+ await context.addCookies(state.cookies);
777
+ log.success(`โœ… Loaded ${state.cookies.length} cookies from ${statePath}`);
778
+ return true;
779
+ }
780
+ log.warning(`โš ๏ธ No cookies found in state file`);
781
+ return false;
782
+ }
783
+ catch (error) {
784
+ log.error(`โŒ Failed to load auth state: ${error}`);
785
+ return false;
786
+ }
787
+ }
788
+ /**
789
+ * Perform interactive setup (for setup_auth tool)
790
+ * Opens a PERSISTENT browser for manual login
791
+ *
792
+ * CRITICAL: Uses the SAME persistent context as runtime!
793
+ * This ensures cookies are automatically saved to the Chrome profile.
794
+ *
795
+ * Benefits over temporary browser:
796
+ * - Session cookies persist correctly (Playwright bug workaround)
797
+ * - Same fingerprint as runtime
798
+ * - No need for addCookies() workarounds
799
+ * - Automatic cookie persistence via Chrome profile
800
+ *
801
+ * @param sendProgress Optional progress callback
802
+ * @param overrideHeadless Optional override for headless mode (true = visible, false = headless)
803
+ * If not provided, defaults to true (visible) for setup
804
+ */
805
+ async performSetup(sendProgress, overrideHeadless) {
806
+ const { chromium } = await import("patchright");
807
+ // Determine headless mode: override or default to true (visible for setup)
808
+ // overrideHeadless contains show_browser value (true = show, false = hide)
809
+ const shouldShowBrowser = overrideHeadless !== undefined ? overrideHeadless : true;
810
+ try {
811
+ // CRITICAL: Clear ALL old auth data FIRST (for account switching)
812
+ log.info("๐Ÿ”„ Preparing for new account authentication...");
813
+ await sendProgress?.("Clearing old authentication data...", 1, 10);
814
+ await this.clearAllAuthData();
815
+ log.info("๐Ÿš€ Launching persistent browser for interactive setup...");
816
+ log.info(` ๐Ÿ“ Profile: ${CONFIG.chromeProfileDir}`);
817
+ await sendProgress?.("Launching persistent browser...", 2, 10);
818
+ // โœ… CRITICAL FIX: Use launchPersistentContext (same as runtime!)
819
+ // This ensures session cookies persist correctly
820
+ const context = await chromium.launchPersistentContext(CONFIG.chromeProfileDir, {
821
+ headless: !shouldShowBrowser, // Use override or default to visible for setup
822
+ channel: "chromium",
823
+ viewport: CONFIG.viewport,
824
+ locale: "en-US",
825
+ timezoneId: "Europe/Berlin",
826
+ args: [
827
+ "--disable-blink-features=AutomationControlled",
828
+ "--disable-dev-shm-usage",
829
+ "--no-first-run",
830
+ "--no-default-browser-check",
831
+ ],
832
+ });
833
+ // Get or create a page
834
+ const pages = context.pages();
835
+ const page = pages.length > 0 ? pages[0] : await context.newPage();
836
+ // Perform login with progress updates
837
+ const loginSuccess = await this.performLogin(page, sendProgress);
838
+ if (loginSuccess) {
839
+ // โœ… Save browser state to state.json (for validation & backup)
840
+ // Chrome ALSO saves everything to the persistent profile automatically!
841
+ await sendProgress?.("Saving authentication state...", 9, 10);
842
+ await this.saveBrowserState(context, page);
843
+ log.success("โœ… Setup complete - authentication saved to:");
844
+ log.success(` ๐Ÿ“„ State file: ${this.stateFilePath}`);
845
+ log.success(` ๐Ÿ“ Chrome profile: ${CONFIG.chromeProfileDir}`);
846
+ log.info("๐Ÿ’ก Session cookies will now persist across restarts!");
847
+ }
848
+ // Close persistent context
849
+ await context.close();
850
+ return loginSuccess;
851
+ }
852
+ catch (error) {
853
+ const errorDetail = error instanceof Error
854
+ ? `${error.message}\n${error.stack}`
855
+ : String(error);
856
+ log.error(`โŒ Setup failed: ${errorDetail}`);
857
+ await sendProgress?.(`Setup failed: ${error instanceof Error ? error.message : String(error)}`, 10, 10);
858
+ return false;
859
+ }
860
+ }
861
+ // ============================================================================
862
+ // Cookie Export for API Client
863
+ // ============================================================================
864
+ /**
865
+ * Export cookies from the saved state for use with the API client
866
+ *
867
+ * @returns Array of cookies in API-compatible format, or empty array if no state
868
+ */
869
+ async exportCookiesForAPI() {
870
+ try {
871
+ if (!existsSync(this.stateFilePath)) {
872
+ log.warning('โš ๏ธ No state file found for cookie export');
873
+ return [];
874
+ }
875
+ const stateData = await fs.readFile(this.stateFilePath, 'utf-8');
876
+ const state = JSON.parse(stateData);
877
+ if (!state.cookies || !Array.isArray(state.cookies)) {
878
+ log.warning('โš ๏ธ No cookies in state file');
879
+ return [];
880
+ }
881
+ // Convert to API-compatible format
882
+ return state.cookies.map((c) => ({
883
+ name: c.name,
884
+ value: c.value,
885
+ domain: c.domain,
886
+ path: c.path,
887
+ expires: c.expires,
888
+ httpOnly: c.httpOnly,
889
+ secure: c.secure,
890
+ sameSite: c.sameSite,
891
+ }));
892
+ }
893
+ catch (error) {
894
+ log.error(`โŒ Failed to export cookies: ${error}`);
895
+ return [];
896
+ }
897
+ }
898
+ /**
899
+ * Check if we have valid cookies for API access
900
+ *
901
+ * @returns True if essential cookies are present and not expired
902
+ */
903
+ async hasValidCookiesForAPI() {
904
+ const cookies = await this.exportCookiesForAPI();
905
+ if (cookies.length === 0) {
906
+ return false;
907
+ }
908
+ // Check for required cookies
909
+ const required = ['SID', 'HSID', 'SSID'];
910
+ const hasRequired = required.every((name) => cookies.some((c) => c.name === name));
911
+ if (!hasRequired) {
912
+ return false;
913
+ }
914
+ // Check for at least one PSID cookie
915
+ const hasPSID = cookies.some((c) => c.name === '__Secure-1PSID' ||
916
+ c.name === '__Secure-3PSID' ||
917
+ c.name === 'OSID' ||
918
+ c.name === '__Secure-OSID');
919
+ if (!hasPSID) {
920
+ return false;
921
+ }
922
+ // Check for SAPISID (needed for API authorization)
923
+ const hasSAPISID = cookies.some((c) => c.name === 'SAPISID' || c.name === '__Secure-3PAPISID');
924
+ return hasSAPISID;
925
+ }
926
+ // ============================================================================
927
+ // Cleanup
928
+ // ============================================================================
929
+ /**
930
+ * Clear ALL authentication data for account switching.
931
+ *
932
+ * WARNING: Caller MUST close all active sessions BEFORE calling this method.
933
+ * Deleting the Chrome profile while sessions are active will crash the browser.
934
+ *
935
+ * CRITICAL: This deletes EVERYTHING to ensure only ONE account is active:
936
+ * - All state.json files (cookies, localStorage)
937
+ * - sessionStorage files
938
+ * - Chrome profile directory (browser fingerprint, cache, etc.)
939
+ *
940
+ * Use this BEFORE authenticating a new account!
941
+ */
942
+ async clearAllAuthData() {
943
+ log.warning("๐Ÿ—‘๏ธ Clearing ALL authentication data for account switch...");
944
+ let deletedCount = 0;
945
+ // 1. Delete all state files in browser_state_dir
946
+ try {
947
+ const files = await fs.readdir(CONFIG.browserStateDir);
948
+ for (const file of files) {
949
+ if (file.endsWith(".json")) {
950
+ await fs.unlink(path.join(CONFIG.browserStateDir, file));
951
+ log.info(` โœ… Deleted: ${file}`);
952
+ deletedCount++;
953
+ }
954
+ }
955
+ }
956
+ catch (error) {
957
+ log.warning(` โš ๏ธ Could not delete state files: ${error}`);
958
+ }
959
+ // 2. Delete Chrome profile (THE KEY for account switching!)
960
+ // This removes ALL browser data: cookies, cache, fingerprint, etc.
961
+ try {
962
+ const chromeProfileDir = CONFIG.chromeProfileDir;
963
+ if (existsSync(chromeProfileDir)) {
964
+ await fs.rm(chromeProfileDir, { recursive: true, force: true });
965
+ log.success(` โœ… Deleted Chrome profile: ${chromeProfileDir}`);
966
+ deletedCount++;
967
+ }
968
+ }
969
+ catch (error) {
970
+ log.warning(` โš ๏ธ Could not delete Chrome profile: ${error}`);
971
+ }
972
+ if (deletedCount === 0) {
973
+ log.info(" โ„น๏ธ No old auth data found (already clean)");
974
+ }
975
+ else {
976
+ log.success(`โœ… All auth data cleared (${deletedCount} items) - ready for new account!`);
977
+ }
978
+ }
979
+ /**
980
+ * Clear all saved authentication state
981
+ */
982
+ async clearState() {
983
+ try {
984
+ try {
985
+ await fs.unlink(this.stateFilePath);
986
+ }
987
+ catch {
988
+ // File doesn't exist
989
+ }
990
+ try {
991
+ await fs.unlink(this.sessionFilePath);
992
+ }
993
+ catch {
994
+ // File doesn't exist
995
+ }
996
+ log.success("โœ… Authentication state cleared");
997
+ return true;
998
+ }
999
+ catch (error) {
1000
+ log.error(`โŒ Failed to clear state: ${error}`);
1001
+ return false;
1002
+ }
1003
+ }
1004
+ /**
1005
+ * HARD RESET: Completely delete ALL authentication state
1006
+ */
1007
+ async hardResetState() {
1008
+ try {
1009
+ log.warning("๐Ÿงน Performing HARD RESET of all authentication state...");
1010
+ let deletedCount = 0;
1011
+ // Delete state file
1012
+ try {
1013
+ await fs.unlink(this.stateFilePath);
1014
+ log.info(` ๐Ÿ—‘๏ธ Deleted: ${this.stateFilePath}`);
1015
+ deletedCount++;
1016
+ }
1017
+ catch {
1018
+ // File doesn't exist
1019
+ }
1020
+ // Delete session file
1021
+ try {
1022
+ await fs.unlink(this.sessionFilePath);
1023
+ log.info(` ๐Ÿ—‘๏ธ Deleted: ${this.sessionFilePath}`);
1024
+ deletedCount++;
1025
+ }
1026
+ catch {
1027
+ // File doesn't exist
1028
+ }
1029
+ // Delete entire browser_state_dir
1030
+ try {
1031
+ const files = await fs.readdir(CONFIG.browserStateDir);
1032
+ for (const file of files) {
1033
+ await fs.unlink(path.join(CONFIG.browserStateDir, file));
1034
+ deletedCount++;
1035
+ }
1036
+ log.info(` ๐Ÿ—‘๏ธ Deleted: ${CONFIG.browserStateDir}/ (${files.length} files)`);
1037
+ }
1038
+ catch {
1039
+ // Directory doesn't exist or empty
1040
+ }
1041
+ if (deletedCount === 0) {
1042
+ log.info(" โ„น๏ธ No state to delete (already clean)");
1043
+ }
1044
+ else {
1045
+ log.success(`โœ… Hard reset complete: ${deletedCount} items deleted`);
1046
+ }
1047
+ return true;
1048
+ }
1049
+ catch (error) {
1050
+ log.error(`โŒ Hard reset failed: ${error}`);
1051
+ return false;
1052
+ }
1053
+ }
1054
+ }
1055
+ //# sourceMappingURL=auth-manager.js.map