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,672 @@
1
+ /**
2
+ * BatchExecute Client for NotebookLM Internal API
3
+ *
4
+ * Main client for making API calls to the NotebookLM batchexecute endpoint.
5
+ * This is the primary interface for API operations.
6
+ *
7
+ * Features:
8
+ * - Cookie-based authentication
9
+ * - CSRF token handling
10
+ * - Automatic retries with exponential backoff
11
+ * - Request/response logging
12
+ * - Error classification
13
+ */
14
+ import { csrfManager } from './csrf-manager.js';
15
+ import { buildRequestBody, buildRequestHeaders, buildEndpointURL, logRequests, } from './request-builder.js';
16
+ import { parseBatchExecuteResponse, isAuthError, isRateLimited, logResponse, } from './response-parser.js';
17
+ import { log } from '../utils/logger.js';
18
+ /**
19
+ * BatchExecute API Client
20
+ *
21
+ * Provides high-level methods for interacting with the NotebookLM API.
22
+ */
23
+ export class BatchExecuteClient {
24
+ config;
25
+ cookies = [];
26
+ lastRequestTime = 0;
27
+ /** Minimum delay between requests to avoid rate limiting (ms) */
28
+ MIN_REQUEST_INTERVAL = 500;
29
+ /** In-flight CSRF fetch promise — deduplicates concurrent requests */
30
+ csrfFetchPromise = null;
31
+ constructor(config = {}) {
32
+ this.config = {
33
+ timeout: config.timeout ?? 30000,
34
+ maxRetries: config.maxRetries ?? 3,
35
+ retryBaseDelay: config.retryBaseDelay ?? 1000,
36
+ debug: config.debug ?? false,
37
+ };
38
+ }
39
+ /**
40
+ * Set cookies for authentication
41
+ *
42
+ * @param cookies - Browser cookies from AuthManager
43
+ */
44
+ setCookies(cookies) {
45
+ this.cookies = cookies;
46
+ if (this.config.debug) {
47
+ log.dim(`🍪 Set ${cookies.length} cookies for API client`);
48
+ }
49
+ }
50
+ /**
51
+ * Get current cookies
52
+ */
53
+ getCookies() {
54
+ return this.cookies;
55
+ }
56
+ /**
57
+ * Check if client has valid authentication
58
+ */
59
+ hasValidAuth() {
60
+ return csrfManager.hasRequiredCookies(this.cookies);
61
+ }
62
+ /**
63
+ * Execute a single RPC request
64
+ *
65
+ * @param request - The RPC request to execute
66
+ * @returns Batch response containing the result
67
+ */
68
+ async execute(request) {
69
+ return this.executeBatch([request]);
70
+ }
71
+ /**
72
+ * Execute multiple RPC requests in a single batch
73
+ *
74
+ * @param requests - Array of RPC requests
75
+ * @returns Batch response containing all results
76
+ */
77
+ async executeBatch(requests) {
78
+ // Guard: warn about duplicate RPC IDs in batch (responses may collide)
79
+ const rpcIds = requests.map(r => r.rpcId);
80
+ const uniqueIds = new Set(rpcIds);
81
+ if (uniqueIds.size < rpcIds.length) {
82
+ const dupes = rpcIds.filter((id, i) => rpcIds.indexOf(id) !== i);
83
+ log.warning(`⚠️ Batch contains duplicate RPC IDs: ${[...new Set(dupes)].join(', ')}. Responses may be overwritten.`);
84
+ }
85
+ // Validate authentication
86
+ if (!this.hasValidAuth()) {
87
+ return {
88
+ success: false,
89
+ responses: new Map(),
90
+ error: 'No valid authentication cookies. Please run setup_auth first.',
91
+ };
92
+ }
93
+ // Rate limiting - ensure minimum interval between requests
94
+ const now = Date.now();
95
+ const timeSinceLastRequest = now - this.lastRequestTime;
96
+ if (timeSinceLastRequest < this.MIN_REQUEST_INTERVAL) {
97
+ await this.sleep(this.MIN_REQUEST_INTERVAL - timeSinceLastRequest);
98
+ }
99
+ // Execute with retries
100
+ let lastError = null;
101
+ for (let attempt = 0; attempt < this.config.maxRetries; attempt++) {
102
+ try {
103
+ if (attempt > 0) {
104
+ const delay = this.config.retryBaseDelay * Math.pow(2, attempt - 1);
105
+ log.warning(`⏳ Retry attempt ${attempt + 1}/${this.config.maxRetries} after ${delay}ms`);
106
+ await this.sleep(delay);
107
+ }
108
+ const response = await this.executeInternal(requests);
109
+ this.lastRequestTime = Date.now();
110
+ // Check for retriable errors
111
+ if (!response.success) {
112
+ if (isRateLimited(response)) {
113
+ log.warning('⚠️ Rate limited, will retry...');
114
+ lastError = new Error('Rate limited');
115
+ continue;
116
+ }
117
+ if (isAuthError(response)) {
118
+ // Auth errors are not retriable
119
+ return response;
120
+ }
121
+ }
122
+ return response;
123
+ }
124
+ catch (error) {
125
+ lastError = error instanceof Error ? error : new Error(String(error));
126
+ log.warning(`⚠️ Request failed: ${lastError.message}`);
127
+ // Network errors are retriable
128
+ if (lastError.message.includes('ETIMEDOUT') ||
129
+ lastError.message.includes('ECONNRESET') ||
130
+ lastError.message.includes('fetch failed')) {
131
+ continue;
132
+ }
133
+ // Other errors are not retriable
134
+ break;
135
+ }
136
+ }
137
+ return {
138
+ success: false,
139
+ responses: new Map(),
140
+ error: lastError?.message || 'Unknown error after all retries',
141
+ };
142
+ }
143
+ /**
144
+ * Internal execution method (no retries)
145
+ */
146
+ async executeInternal(requests) {
147
+ if (this.config.debug) {
148
+ logRequests(requests);
149
+ }
150
+ // Build request components
151
+ const rpcIds = requests.map((r) => r.rpcId);
152
+ const url = buildEndpointURL(rpcIds);
153
+ const cookieHeader = csrfManager.buildCookieHeader(this.cookies);
154
+ const authHeader = csrfManager.generateSAPISIDHash(this.cookies);
155
+ const headers = buildRequestHeaders(cookieHeader, authHeader);
156
+ // Get or fetch CSRF token
157
+ let atValue = csrfManager.getCachedToken();
158
+ if (!atValue) {
159
+ atValue = await this.fetchCSRFToken();
160
+ }
161
+ const body = buildRequestBody(requests, atValue || undefined);
162
+ // Execute request
163
+ const controller = new AbortController();
164
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
165
+ try {
166
+ const response = await fetch(url, {
167
+ method: 'POST',
168
+ headers,
169
+ body,
170
+ signal: controller.signal,
171
+ });
172
+ clearTimeout(timeoutId);
173
+ log.info(`[executeInternal] HTTP ${response.status} ${response.statusText}`);
174
+ if (!response.ok) {
175
+ const text = await response.text().catch(() => '');
176
+ log.warning(`[executeInternal] HTTP error, raw: ${text.substring(0, 200)}`);
177
+ return {
178
+ success: false,
179
+ responses: new Map(),
180
+ rawResponse: text,
181
+ error: `HTTP ${response.status}: ${response.statusText}`,
182
+ };
183
+ }
184
+ const responseText = await response.text();
185
+ const parsed = parseBatchExecuteResponse(responseText);
186
+ log.info(`[executeInternal] Parsed: success=${parsed.success} responses=${parsed.responses.size}`);
187
+ if (this.config.debug) {
188
+ logResponse(parsed);
189
+ }
190
+ return parsed;
191
+ }
192
+ finally {
193
+ clearTimeout(timeoutId);
194
+ }
195
+ }
196
+ /**
197
+ * Fetch CSRF token from the NotebookLM page.
198
+ *
199
+ * Deduplicates concurrent callers — if a fetch is already in flight, all
200
+ * concurrent callers await the same promise instead of triggering extra requests.
201
+ */
202
+ async fetchCSRFToken() {
203
+ if (this.csrfFetchPromise) {
204
+ return this.csrfFetchPromise;
205
+ }
206
+ this.csrfFetchPromise = this._fetchCSRFTokenInternal();
207
+ try {
208
+ return await this.csrfFetchPromise;
209
+ }
210
+ finally {
211
+ this.csrfFetchPromise = null;
212
+ }
213
+ }
214
+ /**
215
+ * Internal CSRF fetch implementation (no concurrency guard)
216
+ */
217
+ async _fetchCSRFTokenInternal() {
218
+ try {
219
+ const cookieHeader = csrfManager.buildCookieHeader(this.cookies);
220
+ const response = await fetch('https://notebooklm.google.com/', {
221
+ method: 'GET',
222
+ headers: {
223
+ Cookie: cookieHeader,
224
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
225
+ },
226
+ });
227
+ if (!response.ok) {
228
+ log.warning(`⚠️ Failed to fetch CSRF token: HTTP ${response.status}`);
229
+ return null;
230
+ }
231
+ const html = await response.text();
232
+ const token = csrfManager.extractTokenFromHTML(html);
233
+ if (token) {
234
+ csrfManager.setCachedToken(token);
235
+ if (this.config.debug) {
236
+ log.dim(`🔑 Extracted CSRF token (${token.length} chars)`);
237
+ }
238
+ }
239
+ return token;
240
+ }
241
+ catch (error) {
242
+ log.warning(`⚠️ Failed to fetch CSRF token: ${error}`);
243
+ return null;
244
+ }
245
+ }
246
+ /**
247
+ * Refresh CSRF token (clear cache and fetch new)
248
+ */
249
+ async refreshCSRFToken() {
250
+ csrfManager.clearCache();
251
+ return this.fetchCSRFToken();
252
+ }
253
+ /**
254
+ * Clear cached CSRF token
255
+ */
256
+ clearCSRFToken() {
257
+ csrfManager.clearCache();
258
+ }
259
+ /**
260
+ * Sleep for a specified duration
261
+ */
262
+ sleep(ms) {
263
+ return new Promise((resolve) => setTimeout(resolve, ms));
264
+ }
265
+ }
266
+ // ============================================================================
267
+ // High-Level API Methods
268
+ // ============================================================================
269
+ import { buildListNotebooksRequest, buildGetNotebookRequest, buildCreateNotebookRequest, buildDeleteNotebookRequest, buildRenameNotebookRequest, buildListSourcesRequest, buildAddURLSourceRequest, buildAddTextSourceRequest, buildAddYouTubeSourceRequest, buildAddDriveSourceRequest, buildDeleteSourceRequest, buildGetAudioStatusRequest, buildCreateAudioRequest, buildDeleteAudioRequest, buildGetAudioURLRequest, buildGenerateFAQRequest, buildGenerateBriefingRequest, buildGenerateTimelineRequest, buildGenerateStudyGuideRequest, buildGetSuggestionsRequest, } from './request-builder.js';
270
+ import { parseNotebookList, parseCreateNotebookResponse, parseSourceList, parseAudioStatus, parseAudioURL, parseGeneratedContent, parseSuggestedQuestions, extractResponse, getFirstSuccessfulResponse, } from './response-parser.js';
271
+ import { RPC_IDS } from './rpc-ids.js';
272
+ import { generateAndWait } from './operation-poller.js';
273
+ /**
274
+ * Extended BatchExecute Client with high-level API methods
275
+ */
276
+ export class NotebookLMAPIClient extends BatchExecuteClient {
277
+ // ==========================================================================
278
+ // Notebook Operations
279
+ // ==========================================================================
280
+ /**
281
+ * List all notebooks for the authenticated user
282
+ */
283
+ async listNotebooks() {
284
+ const response = await this.execute(buildListNotebooksRequest());
285
+ if (!response.success) {
286
+ log.warning(`⚠️ Failed to list notebooks: ${response.error}`);
287
+ return [];
288
+ }
289
+ const rpcResponse = extractResponse(response, RPC_IDS.LIST_NOTEBOOKS);
290
+ if (!rpcResponse?.success || !rpcResponse.data) {
291
+ // Try getting first response if specific one not found
292
+ const firstResponse = getFirstSuccessfulResponse(response);
293
+ if (firstResponse?.data) {
294
+ return parseNotebookList(firstResponse.data);
295
+ }
296
+ return [];
297
+ }
298
+ return parseNotebookList(rpcResponse.data);
299
+ }
300
+ /**
301
+ * Get a specific notebook by ID
302
+ */
303
+ async getNotebook(notebookId) {
304
+ const response = await this.execute(buildGetNotebookRequest(notebookId));
305
+ if (!response.success) {
306
+ log.warning(`⚠️ Failed to get notebook: ${response.error}`);
307
+ return null;
308
+ }
309
+ const rpcResponse = getFirstSuccessfulResponse(response);
310
+ if (!rpcResponse?.success || !rpcResponse.data) {
311
+ return null;
312
+ }
313
+ const data = rpcResponse.data;
314
+ if (!Array.isArray(data)) {
315
+ return null;
316
+ }
317
+ return {
318
+ id: notebookId,
319
+ title: String(data[1] || 'Untitled notebook'),
320
+ createdAt: typeof data[4] === 'number' ? data[4] : undefined,
321
+ modifiedAt: typeof data[5] === 'number' ? data[5] : undefined,
322
+ };
323
+ }
324
+ /**
325
+ * Create a new notebook
326
+ */
327
+ async createNotebook(title) {
328
+ const response = await this.execute(buildCreateNotebookRequest(title));
329
+ if (!response.success) {
330
+ log.warning(`⚠️ Failed to create notebook: ${response.error}`);
331
+ return null;
332
+ }
333
+ const rpcResponse = getFirstSuccessfulResponse(response);
334
+ if (!rpcResponse?.success || !rpcResponse.data) {
335
+ return null;
336
+ }
337
+ return parseCreateNotebookResponse(rpcResponse.data);
338
+ }
339
+ /**
340
+ * Delete a notebook
341
+ */
342
+ async deleteNotebook(notebookId) {
343
+ const response = await this.execute(buildDeleteNotebookRequest(notebookId));
344
+ if (!response.success) {
345
+ log.warning(`⚠️ Failed to delete notebook: ${response.error}`);
346
+ return false;
347
+ }
348
+ return true;
349
+ }
350
+ /**
351
+ * Rename a notebook
352
+ */
353
+ async renameNotebook(notebookId, newTitle) {
354
+ const response = await this.execute(buildRenameNotebookRequest(notebookId, newTitle));
355
+ if (!response.success) {
356
+ log.warning(`⚠️ Failed to rename notebook: ${response.error}`);
357
+ return false;
358
+ }
359
+ return true;
360
+ }
361
+ // ==========================================================================
362
+ // Source Operations
363
+ // ==========================================================================
364
+ /**
365
+ * List all sources in a notebook
366
+ */
367
+ async listSources(notebookId) {
368
+ const response = await this.execute(buildListSourcesRequest(notebookId));
369
+ if (!response.success) {
370
+ log.warning(`⚠️ Failed to list sources: ${response.error}`);
371
+ return [];
372
+ }
373
+ const rpcResponse = getFirstSuccessfulResponse(response);
374
+ if (!rpcResponse?.success || !rpcResponse.data) {
375
+ return [];
376
+ }
377
+ return parseSourceList(rpcResponse.data);
378
+ }
379
+ /**
380
+ * Add a URL source to a notebook
381
+ */
382
+ async addURLSource(notebookId, url) {
383
+ const response = await this.execute(buildAddURLSourceRequest(notebookId, url));
384
+ if (!response.success) {
385
+ log.warning(`⚠️ Failed to add URL source: ${response.error}`);
386
+ return false;
387
+ }
388
+ return true;
389
+ }
390
+ /**
391
+ * Add a text source to a notebook
392
+ */
393
+ async addTextSource(notebookId, title, content) {
394
+ const response = await this.execute(buildAddTextSourceRequest(notebookId, title, content));
395
+ if (!response.success) {
396
+ log.warning(`⚠️ Failed to add text source: ${response.error}`);
397
+ return false;
398
+ }
399
+ return true;
400
+ }
401
+ /**
402
+ * Add a YouTube source to a notebook
403
+ */
404
+ async addYouTubeSource(notebookId, youtubeUrl) {
405
+ const response = await this.execute(buildAddYouTubeSourceRequest(notebookId, youtubeUrl));
406
+ if (!response.success) {
407
+ log.warning(`⚠️ Failed to add YouTube source: ${response.error}`);
408
+ return false;
409
+ }
410
+ return true;
411
+ }
412
+ /**
413
+ * Add a Google Drive source to a notebook
414
+ */
415
+ async addDriveSource(notebookId, fileId) {
416
+ const response = await this.execute(buildAddDriveSourceRequest(notebookId, fileId));
417
+ if (!response.success) {
418
+ log.warning(`⚠️ Failed to add Drive source: ${response.error}`);
419
+ return false;
420
+ }
421
+ return true;
422
+ }
423
+ /**
424
+ * Delete a source from a notebook
425
+ */
426
+ async deleteSource(notebookId, sourceId) {
427
+ const response = await this.execute(buildDeleteSourceRequest(notebookId, sourceId));
428
+ if (!response.success) {
429
+ log.warning(`⚠️ Failed to delete source: ${response.error}`);
430
+ return false;
431
+ }
432
+ return true;
433
+ }
434
+ // ==========================================================================
435
+ // Audio Operations
436
+ // ==========================================================================
437
+ /**
438
+ * Get audio overview status for a notebook
439
+ */
440
+ async getAudioStatus(notebookId) {
441
+ const response = await this.execute(buildGetAudioStatusRequest(notebookId));
442
+ if (!response.success) {
443
+ return { status: 'not_created', error: response.error };
444
+ }
445
+ const rpcResponse = getFirstSuccessfulResponse(response);
446
+ if (!rpcResponse?.success || !rpcResponse.data) {
447
+ return { status: 'not_created' };
448
+ }
449
+ return parseAudioStatus(rpcResponse.data);
450
+ }
451
+ /**
452
+ * Create/generate audio overview for a notebook
453
+ */
454
+ async createAudio(notebookId, customInstructions) {
455
+ const response = await this.execute(buildCreateAudioRequest(notebookId, customInstructions));
456
+ if (!response.success) {
457
+ log.warning(`⚠️ Failed to create audio: ${response.error}`);
458
+ return false;
459
+ }
460
+ return true;
461
+ }
462
+ /**
463
+ * Delete audio overview for a notebook
464
+ */
465
+ async deleteAudio(notebookId) {
466
+ const response = await this.execute(buildDeleteAudioRequest(notebookId));
467
+ if (!response.success) {
468
+ log.warning(`⚠️ Failed to delete audio: ${response.error}`);
469
+ return false;
470
+ }
471
+ return true;
472
+ }
473
+ /**
474
+ * Get audio download URL for a notebook
475
+ */
476
+ async getAudioURL(notebookId) {
477
+ const response = await this.execute(buildGetAudioURLRequest(notebookId));
478
+ if (!response.success) {
479
+ log.warning(`⚠️ Failed to get audio URL: ${response.error}`);
480
+ return null;
481
+ }
482
+ const rpcResponse = getFirstSuccessfulResponse(response);
483
+ if (!rpcResponse?.success || !rpcResponse.data) {
484
+ return null;
485
+ }
486
+ return parseAudioURL(rpcResponse.data);
487
+ }
488
+ // ==========================================================================
489
+ // Content Generation Operations
490
+ // ==========================================================================
491
+ /**
492
+ * Generate FAQ from notebook sources
493
+ */
494
+ async generateFAQ(notebookId, sourceIds) {
495
+ const response = await this.execute(buildGenerateFAQRequest(notebookId, sourceIds));
496
+ if (!response.success) {
497
+ log.warning(`⚠️ Failed to generate FAQ: ${response.error}`);
498
+ return null;
499
+ }
500
+ const rpcResponse = getFirstSuccessfulResponse(response);
501
+ if (!rpcResponse?.success || !rpcResponse.data) {
502
+ return null;
503
+ }
504
+ return parseGeneratedContent(rpcResponse.data);
505
+ }
506
+ /**
507
+ * Generate briefing document from notebook sources
508
+ */
509
+ async generateBriefing(notebookId, sourceIds) {
510
+ const response = await this.execute(buildGenerateBriefingRequest(notebookId, sourceIds));
511
+ if (!response.success) {
512
+ log.warning(`⚠️ Failed to generate briefing: ${response.error}`);
513
+ return null;
514
+ }
515
+ const rpcResponse = getFirstSuccessfulResponse(response);
516
+ if (!rpcResponse?.success || !rpcResponse.data) {
517
+ return null;
518
+ }
519
+ return parseGeneratedContent(rpcResponse.data);
520
+ }
521
+ /**
522
+ * Generate timeline from notebook sources
523
+ */
524
+ async generateTimeline(notebookId, sourceIds) {
525
+ const response = await this.execute(buildGenerateTimelineRequest(notebookId, sourceIds));
526
+ if (!response.success) {
527
+ log.warning(`⚠️ Failed to generate timeline: ${response.error}`);
528
+ return null;
529
+ }
530
+ const rpcResponse = getFirstSuccessfulResponse(response);
531
+ if (!rpcResponse?.success || !rpcResponse.data) {
532
+ return null;
533
+ }
534
+ return parseGeneratedContent(rpcResponse.data);
535
+ }
536
+ /**
537
+ * Generate study guide from notebook sources
538
+ */
539
+ async generateStudyGuide(notebookId, sourceIds) {
540
+ const response = await this.execute(buildGenerateStudyGuideRequest(notebookId, sourceIds));
541
+ if (!response.success) {
542
+ log.warning(`⚠️ Failed to generate study guide: ${response.error}`);
543
+ return null;
544
+ }
545
+ const rpcResponse = getFirstSuccessfulResponse(response);
546
+ if (!rpcResponse?.success || !rpcResponse.data) {
547
+ return null;
548
+ }
549
+ return parseGeneratedContent(rpcResponse.data);
550
+ }
551
+ // ==========================================================================
552
+ // Q&A Operations
553
+ // ==========================================================================
554
+ /**
555
+ * Get suggested questions for a notebook
556
+ */
557
+ async getSuggestedQuestions(notebookId) {
558
+ const response = await this.execute(buildGetSuggestionsRequest(notebookId));
559
+ if (!response.success) {
560
+ log.warning(`⚠️ Failed to get suggestions: ${response.error}`);
561
+ return [];
562
+ }
563
+ const rpcResponse = getFirstSuccessfulResponse(response);
564
+ if (!rpcResponse?.success || !rpcResponse.data) {
565
+ return [];
566
+ }
567
+ return parseSuggestedQuestions(rpcResponse.data);
568
+ }
569
+ // ==========================================================================
570
+ // Batch Convenience Operations
571
+ // ==========================================================================
572
+ /**
573
+ * Create a notebook and immediately add a text source in a single batch call.
574
+ *
575
+ * @param title - Notebook title
576
+ * @param sourceTitle - Title for the text source
577
+ * @param sourceContent - Content of the text source
578
+ * @returns Created notebook ID or null on failure
579
+ */
580
+ async createNotebookWithSource(title, sourceTitle, sourceContent) {
581
+ // Step 1: create the notebook
582
+ const notebookId = await this.createNotebook(title);
583
+ if (!notebookId) {
584
+ log.warning('⚠️ createNotebookWithSource: failed to create notebook');
585
+ return { notebookId: null, sourceAdded: false };
586
+ }
587
+ // Step 2: add source — reuse existing single-call method
588
+ const added = await this.addTextSource(notebookId, sourceTitle, sourceContent);
589
+ if (!added) {
590
+ log.warning(`⚠️ createNotebookWithSource: notebook created (${notebookId}) but source add failed`);
591
+ }
592
+ return { notebookId, sourceAdded: added };
593
+ }
594
+ /**
595
+ * List notebooks and sources for a specific notebook in a single batch call.
596
+ *
597
+ * @param notebookId - Notebook whose sources to fetch
598
+ * @returns Object containing notebooks list and sources list
599
+ */
600
+ async batchListNotebooksAndSources(notebookId) {
601
+ const requests = [
602
+ buildListNotebooksRequest(),
603
+ buildListSourcesRequest(notebookId),
604
+ ];
605
+ const response = await this.executeBatch(requests);
606
+ const notebooks = [];
607
+ const sources = [];
608
+ if (!response.success) {
609
+ log.warning(`⚠️ batchListNotebooksAndSources failed: ${response.error}`);
610
+ return { notebooks, sources };
611
+ }
612
+ // Parse notebooks from LIST_NOTEBOOKS response
613
+ const nbResponse = response.responses.get(RPC_IDS.LIST_NOTEBOOKS);
614
+ if (nbResponse?.success && nbResponse.data) {
615
+ notebooks.push(...parseNotebookList(nbResponse.data));
616
+ }
617
+ else {
618
+ const first = getFirstSuccessfulResponse(response);
619
+ if (first?.data)
620
+ notebooks.push(...parseNotebookList(first.data));
621
+ }
622
+ // Parse sources from LIST_SOURCES response
623
+ const srcResponse = response.responses.get(RPC_IDS.LIST_SOURCES);
624
+ if (srcResponse?.success && srcResponse.data) {
625
+ sources.push(...parseSourceList(srcResponse.data));
626
+ }
627
+ return { notebooks, sources };
628
+ }
629
+ // ==========================================================================
630
+ // Async Generation with Polling
631
+ // ==========================================================================
632
+ /**
633
+ * Generate content and wait for it to be ready.
634
+ *
635
+ * Triggers generation via R7cb6c then polls gArtLc until the artifact
636
+ * status reaches COMPLETED (status 3) or the timeout is exceeded.
637
+ *
638
+ * @param notebookId - Target notebook ID
639
+ * @param contentType - Content type code from ContentTypeCode
640
+ * @param sourceIds - Optional source ID filter
641
+ * @param maxWaitMs - Maximum polling duration in ms (default: 120_000)
642
+ * @returns GenerateAndWaitResult with content when successful
643
+ */
644
+ async generateAndWait(notebookId, contentType, sourceIds, maxWaitMs = 120_000) {
645
+ return generateAndWait(this, notebookId, contentType, sourceIds, maxWaitMs);
646
+ }
647
+ }
648
+ // Export singleton factory function
649
+ let clientInstance = null;
650
+ /**
651
+ * Get or create the API client instance.
652
+ *
653
+ * Note: This is a singleton. If an instance already exists and a new config
654
+ * is passed, the config is ignored. Call resetAPIClient() first to apply new config.
655
+ */
656
+ export function getAPIClient(config) {
657
+ if (!clientInstance) {
658
+ clientInstance = new NotebookLMAPIClient(config);
659
+ }
660
+ else if (config) {
661
+ // Warn when caller passes config to an already-initialized singleton (config is silently ignored)
662
+ log.dim('⚠️ API client already initialized; ignoring new config. Use resetAPIClient() first.');
663
+ }
664
+ return clientInstance;
665
+ }
666
+ /**
667
+ * Reset the API client (for testing or re-auth)
668
+ */
669
+ export function resetAPIClient() {
670
+ clientInstance = null;
671
+ }
672
+ //# sourceMappingURL=batch-execute-client.js.map