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.
- package/LICENSE +21 -0
- package/README.md +420 -0
- package/dist/api/batch-execute-client.d.ts +232 -0
- package/dist/api/batch-execute-client.d.ts.map +1 -0
- package/dist/api/batch-execute-client.js +672 -0
- package/dist/api/batch-execute-client.js.map +1 -0
- package/dist/api/content-types.d.ts +44 -0
- package/dist/api/content-types.d.ts.map +1 -0
- package/dist/api/content-types.js +89 -0
- package/dist/api/content-types.js.map +1 -0
- package/dist/api/csrf-manager.d.ts +94 -0
- package/dist/api/csrf-manager.d.ts.map +1 -0
- package/dist/api/csrf-manager.js +178 -0
- package/dist/api/csrf-manager.js.map +1 -0
- package/dist/api/index.d.ts +27 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +56 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/operation-poller.d.ts +67 -0
- package/dist/api/operation-poller.d.ts.map +1 -0
- package/dist/api/operation-poller.js +132 -0
- package/dist/api/operation-poller.js.map +1 -0
- package/dist/api/request-builder.d.ts +196 -0
- package/dist/api/request-builder.d.ts.map +1 -0
- package/dist/api/request-builder.js +371 -0
- package/dist/api/request-builder.js.map +1 -0
- package/dist/api/response-parser.d.ts +124 -0
- package/dist/api/response-parser.d.ts.map +1 -0
- package/dist/api/response-parser.js +595 -0
- package/dist/api/response-parser.js.map +1 -0
- package/dist/api/rpc-ids.d.ts +92 -0
- package/dist/api/rpc-ids.d.ts.map +1 -0
- package/dist/api/rpc-ids.js +138 -0
- package/dist/api/rpc-ids.js.map +1 -0
- package/dist/api/streaming-chat-client.d.ts +50 -0
- package/dist/api/streaming-chat-client.d.ts.map +1 -0
- package/dist/api/streaming-chat-client.js +198 -0
- package/dist/api/streaming-chat-client.js.map +1 -0
- package/dist/api/types.d.ts +318 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +22 -0
- package/dist/api/types.js.map +1 -0
- package/dist/auth/auth-manager.d.ts +163 -0
- package/dist/auth/auth-manager.d.ts.map +1 -0
- package/dist/auth/auth-manager.js +1055 -0
- package/dist/auth/auth-manager.js.map +1 -0
- package/dist/auth/cookie-store.d.ts +121 -0
- package/dist/auth/cookie-store.d.ts.map +1 -0
- package/dist/auth/cookie-store.js +283 -0
- package/dist/auth/cookie-store.js.map +1 -0
- package/dist/config.d.ts +89 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +217 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +26 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +41 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +439 -0
- package/dist/index.js.map +1 -0
- package/dist/library/notebook-library.d.ts +79 -0
- package/dist/library/notebook-library.d.ts.map +1 -0
- package/dist/library/notebook-library.js +296 -0
- package/dist/library/notebook-library.js.map +1 -0
- package/dist/library/types.d.ts +67 -0
- package/dist/library/types.d.ts.map +1 -0
- package/dist/library/types.js +8 -0
- package/dist/library/types.js.map +1 -0
- package/dist/operations/content-operations.d.ts +78 -0
- package/dist/operations/content-operations.d.ts.map +1 -0
- package/dist/operations/content-operations.js +162 -0
- package/dist/operations/content-operations.js.map +1 -0
- package/dist/operations/hybrid-executor.d.ts +47 -0
- package/dist/operations/hybrid-executor.d.ts.map +1 -0
- package/dist/operations/hybrid-executor.js +114 -0
- package/dist/operations/hybrid-executor.js.map +1 -0
- package/dist/operations/notebook-crud-operations.d.ts +52 -0
- package/dist/operations/notebook-crud-operations.d.ts.map +1 -0
- package/dist/operations/notebook-crud-operations.js +248 -0
- package/dist/operations/notebook-crud-operations.js.map +1 -0
- package/dist/operations/research-operations.d.ts +42 -0
- package/dist/operations/research-operations.d.ts.map +1 -0
- package/dist/operations/research-operations.js +189 -0
- package/dist/operations/research-operations.js.map +1 -0
- package/dist/operations/source-operations.d.ts +59 -0
- package/dist/operations/source-operations.d.ts.map +1 -0
- package/dist/operations/source-operations.js +280 -0
- package/dist/operations/source-operations.js.map +1 -0
- package/dist/operations/studio-operations.d.ts +98 -0
- package/dist/operations/studio-operations.d.ts.map +1 -0
- package/dist/operations/studio-operations.js +309 -0
- package/dist/operations/studio-operations.js.map +1 -0
- package/dist/resources/resource-handlers.d.ts +22 -0
- package/dist/resources/resource-handlers.d.ts.map +1 -0
- package/dist/resources/resource-handlers.js +216 -0
- package/dist/resources/resource-handlers.js.map +1 -0
- package/dist/session/browser-session.d.ts +113 -0
- package/dist/session/browser-session.d.ts.map +1 -0
- package/dist/session/browser-session.js +670 -0
- package/dist/session/browser-session.js.map +1 -0
- package/dist/session/session-manager.d.ts +88 -0
- package/dist/session/session-manager.d.ts.map +1 -0
- package/dist/session/session-manager.js +314 -0
- package/dist/session/session-manager.js.map +1 -0
- package/dist/session/shared-context-manager.d.ts +107 -0
- package/dist/session/shared-context-manager.d.ts.map +1 -0
- package/dist/session/shared-context-manager.js +447 -0
- package/dist/session/shared-context-manager.js.map +1 -0
- package/dist/tools/definitions/ask-question.d.ts +8 -0
- package/dist/tools/definitions/ask-question.d.ts.map +1 -0
- package/dist/tools/definitions/ask-question.js +213 -0
- package/dist/tools/definitions/ask-question.js.map +1 -0
- package/dist/tools/definitions/content-generation.d.ts +52 -0
- package/dist/tools/definitions/content-generation.d.ts.map +1 -0
- package/dist/tools/definitions/content-generation.js +236 -0
- package/dist/tools/definitions/content-generation.js.map +1 -0
- package/dist/tools/definitions/notebook-crud.d.ts +9 -0
- package/dist/tools/definitions/notebook-crud.d.ts.map +1 -0
- package/dist/tools/definitions/notebook-crud.js +156 -0
- package/dist/tools/definitions/notebook-crud.js.map +1 -0
- package/dist/tools/definitions/notebook-management.d.ts +3 -0
- package/dist/tools/definitions/notebook-management.d.ts.map +1 -0
- package/dist/tools/definitions/notebook-management.js +243 -0
- package/dist/tools/definitions/notebook-management.js.map +1 -0
- package/dist/tools/definitions/research.d.ts +23 -0
- package/dist/tools/definitions/research.d.ts.map +1 -0
- package/dist/tools/definitions/research.js +108 -0
- package/dist/tools/definitions/research.js.map +1 -0
- package/dist/tools/definitions/session-management.d.ts +3 -0
- package/dist/tools/definitions/session-management.d.ts.map +1 -0
- package/dist/tools/definitions/session-management.js +41 -0
- package/dist/tools/definitions/session-management.js.map +1 -0
- package/dist/tools/definitions/source-management.d.ts +39 -0
- package/dist/tools/definitions/source-management.d.ts.map +1 -0
- package/dist/tools/definitions/source-management.js +224 -0
- package/dist/tools/definitions/source-management.js.map +1 -0
- package/dist/tools/definitions/studio.d.ts +36 -0
- package/dist/tools/definitions/studio.d.ts.map +1 -0
- package/dist/tools/definitions/studio.js +153 -0
- package/dist/tools/definitions/studio.js.map +1 -0
- package/dist/tools/definitions/system.d.ts +3 -0
- package/dist/tools/definitions/system.d.ts.map +1 -0
- package/dist/tools/definitions/system.js +143 -0
- package/dist/tools/definitions/system.js.map +1 -0
- package/dist/tools/definitions.d.ts +12 -0
- package/dist/tools/definitions.d.ts.map +1 -0
- package/dist/tools/definitions.js +36 -0
- package/dist/tools/definitions.js.map +1 -0
- package/dist/tools/handlers/content-handlers.d.ts +287 -0
- package/dist/tools/handlers/content-handlers.d.ts.map +1 -0
- package/dist/tools/handlers/content-handlers.js +244 -0
- package/dist/tools/handlers/content-handlers.js.map +1 -0
- package/dist/tools/handlers/notebook-crud-handlers.d.ts +69 -0
- package/dist/tools/handlers/notebook-crud-handlers.d.ts.map +1 -0
- package/dist/tools/handlers/notebook-crud-handlers.js +117 -0
- package/dist/tools/handlers/notebook-crud-handlers.js.map +1 -0
- package/dist/tools/handlers/research-handlers.d.ts +37 -0
- package/dist/tools/handlers/research-handlers.d.ts.map +1 -0
- package/dist/tools/handlers/research-handlers.js +87 -0
- package/dist/tools/handlers/research-handlers.js.map +1 -0
- package/dist/tools/handlers/source-handlers.d.ts +52 -0
- package/dist/tools/handlers/source-handlers.d.ts.map +1 -0
- package/dist/tools/handlers/source-handlers.js +177 -0
- package/dist/tools/handlers/source-handlers.js.map +1 -0
- package/dist/tools/handlers/studio-handlers.d.ts +125 -0
- package/dist/tools/handlers/studio-handlers.d.ts.map +1 -0
- package/dist/tools/handlers/studio-handlers.js +183 -0
- package/dist/tools/handlers/studio-handlers.js.map +1 -0
- package/dist/tools/handlers.d.ts +629 -0
- package/dist/tools/handlers.d.ts.map +1 -0
- package/dist/tools/handlers.js +833 -0
- package/dist/tools/handlers.js.map +1 -0
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +8 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types.d.ts +82 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/cleanup-manager.d.ts +133 -0
- package/dist/utils/cleanup-manager.d.ts.map +1 -0
- package/dist/utils/cleanup-manager.js +673 -0
- package/dist/utils/cleanup-manager.js.map +1 -0
- package/dist/utils/cli-handler.d.ts +16 -0
- package/dist/utils/cli-handler.d.ts.map +1 -0
- package/dist/utils/cli-handler.js +102 -0
- package/dist/utils/cli-handler.js.map +1 -0
- package/dist/utils/logger.d.ts +61 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +92 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/page-utils.d.ts +54 -0
- package/dist/utils/page-utils.d.ts.map +1 -0
- package/dist/utils/page-utils.js +405 -0
- package/dist/utils/page-utils.js.map +1 -0
- package/dist/utils/settings-manager.d.ts +37 -0
- package/dist/utils/settings-manager.d.ts.map +1 -0
- package/dist/utils/settings-manager.js +120 -0
- package/dist/utils/settings-manager.js.map +1 -0
- package/dist/utils/stealth-utils.d.ts +135 -0
- package/dist/utils/stealth-utils.d.ts.map +1 -0
- package/dist/utils/stealth-utils.js +398 -0
- package/dist/utils/stealth-utils.js.map +1 -0
- 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
|