notebooklm-sdk 0.2.0 → 0.3.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/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  Generate AI podcasts, chat with documents, run web research, and manage notebooks programmatically — from Node.js, Bun, or Deno.
10
10
 
11
- > ⚠️ **Unofficial.** This SDK reverse-engineers the NotebookLM internal API. It may break when Google updates their service. Not affiliated with Google.
11
+ > **Unofficial.** This SDK reverse-engineers the NotebookLM internal API. It may break when Google updates their service. Not affiliated with Google.
12
12
 
13
13
  TypeScript port of [notebooklm-py](https://github.com/teng-lin/notebooklm-py).
14
14
 
package/dist/index.cjs CHANGED
@@ -21,6 +21,10 @@ __export(enums_exports, {
21
21
  ArtifactTypeCode: () => exports.ArtifactTypeCode,
22
22
  AudioFormat: () => exports.AudioFormat,
23
23
  AudioLength: () => exports.AudioLength,
24
+ ChatGoal: () => exports.ChatGoal,
25
+ ChatMode: () => exports.ChatMode,
26
+ ChatResponseLength: () => exports.ChatResponseLength,
27
+ DriveMimeType: () => exports.DriveMimeType,
24
28
  ExportType: () => exports.ExportType,
25
29
  InfographicDetail: () => exports.InfographicDetail,
26
30
  InfographicOrientation: () => exports.InfographicOrientation,
@@ -38,6 +42,7 @@ __export(enums_exports, {
38
42
  VideoStyle: () => exports.VideoStyle,
39
43
  artifactStatusFromCode: () => artifactStatusFromCode,
40
44
  artifactTypeFromCode: () => artifactTypeFromCode,
45
+ chatModeToParams: () => chatModeToParams,
41
46
  sourceStatusFromCode: () => sourceStatusFromCode,
42
47
  sourceTypeFromCode: () => sourceTypeFromCode
43
48
  });
@@ -59,7 +64,10 @@ function artifactStatusFromCode(code) {
59
64
  function sourceStatusFromCode(code) {
60
65
  return SOURCE_STATUS_MAP[code] ?? "unknown";
61
66
  }
62
- exports.RPCMethod = void 0; exports.ArtifactTypeCode = void 0; var ArtifactStatusCode, SourceStatusCode; exports.AudioFormat = void 0; exports.AudioLength = void 0; exports.VideoFormat = void 0; exports.VideoStyle = void 0; exports.QuizQuantity = void 0; exports.QuizDifficulty = void 0; exports.InfographicOrientation = void 0; exports.InfographicDetail = void 0; exports.InfographicStyle = void 0; exports.SlideDeckFormat = void 0; exports.SlideDeckLength = void 0; exports.ExportType = void 0; var SOURCE_TYPE_MAP, ARTIFACT_TYPE_MAP, ARTIFACT_STATUS_MAP, SOURCE_STATUS_MAP; exports.ShareAccess = void 0; exports.ShareViewLevel = void 0; exports.SharePermission = void 0;
67
+ function chatModeToParams(mode) {
68
+ return CHAT_MODE_PARAMS[mode];
69
+ }
70
+ exports.RPCMethod = void 0; exports.ArtifactTypeCode = void 0; var ArtifactStatusCode, SourceStatusCode; exports.AudioFormat = void 0; exports.AudioLength = void 0; exports.VideoFormat = void 0; exports.VideoStyle = void 0; exports.QuizQuantity = void 0; exports.QuizDifficulty = void 0; exports.InfographicOrientation = void 0; exports.InfographicDetail = void 0; exports.InfographicStyle = void 0; exports.SlideDeckFormat = void 0; exports.SlideDeckLength = void 0; exports.ExportType = void 0; exports.DriveMimeType = void 0; var SOURCE_TYPE_MAP, ARTIFACT_TYPE_MAP, ARTIFACT_STATUS_MAP, SOURCE_STATUS_MAP; exports.ChatMode = void 0; var CHAT_MODE_PARAMS; exports.ChatGoal = void 0; exports.ChatResponseLength = void 0; exports.ShareAccess = void 0; exports.ShareViewLevel = void 0; exports.SharePermission = void 0;
63
71
  var init_enums = __esm({
64
72
  "src/types/enums.ts"() {
65
73
  exports.RPCMethod = {
@@ -208,6 +216,12 @@ var init_enums = __esm({
208
216
  DOCS: 1,
209
217
  SHEETS: 2
210
218
  };
219
+ exports.DriveMimeType = {
220
+ GOOGLE_DOC: "application/vnd.google-apps.document",
221
+ GOOGLE_SLIDES: "application/vnd.google-apps.presentation",
222
+ GOOGLE_SHEETS: "application/vnd.google-apps.spreadsheet",
223
+ PDF: "application/pdf"
224
+ };
211
225
  SOURCE_TYPE_MAP = {
212
226
  1: "google_docs",
213
227
  2: "google_slides",
@@ -243,6 +257,38 @@ var init_enums = __esm({
243
257
  3: "error",
244
258
  5: "preparing"
245
259
  };
260
+ exports.ChatMode = {
261
+ /** General purpose — balanced length and style. */
262
+ DEFAULT: "default",
263
+ /** Educational focus with longer, learning-oriented responses. */
264
+ LEARNING_GUIDE: "learning_guide",
265
+ /** Short, concise answers. */
266
+ CONCISE: "concise",
267
+ /** Verbose, detailed answers. */
268
+ DETAILED: "detailed"
269
+ };
270
+ CHAT_MODE_PARAMS = {
271
+ default: [1, 1],
272
+ learning_guide: [3, 4],
273
+ concise: [1, 5],
274
+ detailed: [1, 4]
275
+ };
276
+ exports.ChatGoal = {
277
+ /** General purpose research and brainstorming. */
278
+ DEFAULT: 1,
279
+ /** Custom instructions (up to 10,000 characters). */
280
+ CUSTOM: 2,
281
+ /** Educational focus with learning-oriented responses. */
282
+ LEARNING_GUIDE: 3
283
+ };
284
+ exports.ChatResponseLength = {
285
+ /** Standard response length. */
286
+ DEFAULT: 1,
287
+ /** Verbose, detailed responses. */
288
+ LONGER: 4,
289
+ /** Concise, brief responses. */
290
+ SHORTER: 5
291
+ };
246
292
  exports.ShareAccess = {
247
293
  /** Only explicitly shared users can access */
248
294
  RESTRICTED: 0,
@@ -765,6 +811,30 @@ var ArtifactsAPI = class {
765
811
  const artifacts = await this.list(notebookId);
766
812
  return artifacts.find((a) => a.id === artifactId) ?? null;
767
813
  }
814
+ async listAudio(notebookId) {
815
+ return (await this.list(notebookId)).filter((a) => a.kind === "audio");
816
+ }
817
+ async listVideo(notebookId) {
818
+ return (await this.list(notebookId)).filter((a) => a.kind === "video");
819
+ }
820
+ async listReports(notebookId) {
821
+ return (await this.list(notebookId)).filter((a) => a.kind === "report");
822
+ }
823
+ async listQuizzes(notebookId) {
824
+ return (await this.list(notebookId)).filter((a) => a.kind === "quiz");
825
+ }
826
+ async listFlashcards(notebookId) {
827
+ return (await this.list(notebookId)).filter((a) => a.kind === "flashcards");
828
+ }
829
+ async listInfographics(notebookId) {
830
+ return (await this.list(notebookId)).filter((a) => a.kind === "infographic");
831
+ }
832
+ async listSlideDecks(notebookId) {
833
+ return (await this.list(notebookId)).filter((a) => a.kind === "slide_deck");
834
+ }
835
+ async listDataTables(notebookId) {
836
+ return (await this.list(notebookId)).filter((a) => a.kind === "data_table");
837
+ }
768
838
  async delete(notebookId, artifactId) {
769
839
  const params = [[2], notebookId, artifactId];
770
840
  await this.rpc.call(exports.RPCMethod.DELETE_ARTIFACT, params, {
@@ -1133,6 +1203,39 @@ ${opts.extraInstructions}` : cfg.prompt;
1133
1203
  if (!url) throw new exports.ArtifactNotReadyError("infographic", { artifactId });
1134
1204
  return this._fetchMediaWithCookies(url);
1135
1205
  }
1206
+ /** Get AI-suggested report formats based on notebook content. */
1207
+ async suggestReports(notebookId) {
1208
+ const params = [[2], notebookId];
1209
+ const result = await this.rpc.call(exports.RPCMethod.GET_SUGGESTED_REPORTS, params, {
1210
+ sourcePath: `/notebook/${notebookId}`,
1211
+ allowNull: true,
1212
+ timeoutMs: 12e4
1213
+ });
1214
+ if (!Array.isArray(result) || !result.length) return [];
1215
+ const items = Array.isArray(result[0]) ? result[0] : result;
1216
+ const suggestions = [];
1217
+ for (const item of items) {
1218
+ if (Array.isArray(item) && item.length >= 5) {
1219
+ suggestions.push({
1220
+ title: typeof item[0] === "string" ? item[0] : "",
1221
+ description: typeof item[1] === "string" ? item[1] : "",
1222
+ prompt: typeof item[4] === "string" ? item[4] : "",
1223
+ audienceLevel: typeof item[5] === "number" ? item[5] : 2
1224
+ });
1225
+ }
1226
+ }
1227
+ return suggestions;
1228
+ }
1229
+ /** Revise an individual slide in a completed slide deck using a prompt. */
1230
+ async reviseSlide(notebookId, artifactId, slideIndex, prompt) {
1231
+ if (slideIndex < 0) throw new Error("slideIndex must be >= 0");
1232
+ const params = [[2], artifactId, [[[slideIndex, prompt]]]];
1233
+ const result = await this.rpc.call(exports.RPCMethod.REVISE_SLIDE, params, {
1234
+ sourcePath: `/notebook/${notebookId}`,
1235
+ allowNull: true
1236
+ });
1237
+ return this._parseGenerationResult(result);
1238
+ }
1136
1239
  /** Get parsed headers and rows from a completed data table artifact. */
1137
1240
  async getDataTableContent(notebookId, artifactId) {
1138
1241
  const artifacts = await this._listRaw(notebookId);
@@ -1356,6 +1459,36 @@ var ChatAPI = class {
1356
1459
  }
1357
1460
  return null;
1358
1461
  }
1462
+ /**
1463
+ * Low-level chat configuration. Set goal, response length, and optional
1464
+ * custom instructions directly. Persists on the server per notebook.
1465
+ * Use `setMode()` for preset combinations instead.
1466
+ */
1467
+ async configure(notebookId, goal, length, customPrompt) {
1468
+ if (goal === exports.ChatGoal.CUSTOM && !customPrompt) {
1469
+ throw new Error("customPrompt is required when goal is ChatGoal.CUSTOM");
1470
+ }
1471
+ const goalArray = goal === exports.ChatGoal.CUSTOM ? [goal, customPrompt] : [goal];
1472
+ const chatSettings = [goalArray, [length]];
1473
+ const params = [notebookId, [[null, null, null, null, null, null, null, chatSettings]]];
1474
+ await this.rpc.call(exports.RPCMethod.RENAME_NOTEBOOK, params, {
1475
+ sourcePath: `/notebook/${notebookId}`,
1476
+ allowNull: true
1477
+ });
1478
+ }
1479
+ /**
1480
+ * Set the chat mode for a notebook. Persists on the server — affects all
1481
+ * subsequent `ask()` calls until changed.
1482
+ */
1483
+ async setMode(notebookId, mode) {
1484
+ const [goal, length] = chatModeToParams(mode);
1485
+ const chatSettings = [[goal], [length]];
1486
+ const params = [notebookId, [[null, null, null, null, null, null, null, chatSettings]]];
1487
+ await this.rpc.call(exports.RPCMethod.RENAME_NOTEBOOK, params, {
1488
+ sourcePath: `/notebook/${notebookId}`,
1489
+ allowNull: true
1490
+ });
1491
+ }
1359
1492
  clearCache(conversationId) {
1360
1493
  if (conversationId) {
1361
1494
  this.conversationCache.delete(conversationId);
@@ -1526,6 +1659,9 @@ var NotebooksAPI = class {
1526
1659
  }
1527
1660
  return "";
1528
1661
  }
1662
+ async removeFromRecent(notebookId) {
1663
+ await this.rpc.call(exports.RPCMethod.REMOVE_RECENTLY_VIEWED, [notebookId], { allowNull: true });
1664
+ }
1529
1665
  async getDescription(notebookId) {
1530
1666
  const params = [notebookId, [2]];
1531
1667
  const result = await this.rpc.call(exports.RPCMethod.SUMMARIZE, params, {
@@ -2051,6 +2187,44 @@ var SourcesAPI = class {
2051
2187
  _typeCode: null
2052
2188
  };
2053
2189
  }
2190
+ async addDrive(notebookId, fileId, title, mimeType = exports.DriveMimeType.GOOGLE_DOC, opts = {}) {
2191
+ const sourceData = [
2192
+ [fileId, mimeType, 1, title],
2193
+ null,
2194
+ null,
2195
+ null,
2196
+ null,
2197
+ null,
2198
+ null,
2199
+ null,
2200
+ null,
2201
+ null,
2202
+ 1
2203
+ ];
2204
+ const params = [
2205
+ [sourceData],
2206
+ notebookId,
2207
+ [2],
2208
+ [1, null, null, null, null, null, null, null, null, null, [1]]
2209
+ ];
2210
+ const result = await this.rpc.call(exports.RPCMethod.ADD_SOURCE, params, {
2211
+ sourcePath: `/notebook/${notebookId}`,
2212
+ allowNull: true
2213
+ });
2214
+ const sourceId = extractSourceId(result);
2215
+ if (opts.waitUntilReady) {
2216
+ return this.waitUntilReady(notebookId, sourceId, opts.waitTimeout);
2217
+ }
2218
+ return {
2219
+ id: sourceId,
2220
+ title,
2221
+ url: null,
2222
+ kind: "unknown",
2223
+ createdAt: null,
2224
+ status: "processing",
2225
+ _typeCode: null
2226
+ };
2227
+ }
2054
2228
  async addFile(notebookId, filePath, mimeType, opts = {}) {
2055
2229
  const fileData = fs.readFileSync(filePath);
2056
2230
  const fileName = filePath.split("/").pop() ?? "file";
@@ -2133,6 +2307,74 @@ var SourcesAPI = class {
2133
2307
  const uploadResult = await uploadResp.text();
2134
2308
  return uploadResult.trim();
2135
2309
  }
2310
+ /** Get the AI-generated Source Guide (summary + keywords) for a source. */
2311
+ async getGuide(notebookId, sourceId) {
2312
+ const params = [[[[sourceId]]]];
2313
+ const result = await this.rpc.call(exports.RPCMethod.GET_SOURCE_GUIDE, params, {
2314
+ sourcePath: `/notebook/${notebookId}`,
2315
+ allowNull: true,
2316
+ timeoutMs: 12e4
2317
+ });
2318
+ let summary = "";
2319
+ let keywords = [];
2320
+ if (Array.isArray(result) && result.length > 0) {
2321
+ const outer = result[0];
2322
+ if (Array.isArray(outer) && outer.length > 0) {
2323
+ const inner = outer[0];
2324
+ if (Array.isArray(inner)) {
2325
+ if (inner.length > 1 && Array.isArray(inner[1]) && typeof inner[1][0] === "string") {
2326
+ summary = inner[1][0];
2327
+ }
2328
+ if (inner.length > 2 && Array.isArray(inner[2]) && Array.isArray(inner[2][0])) {
2329
+ keywords = inner[2][0].filter((k) => typeof k === "string");
2330
+ }
2331
+ }
2332
+ }
2333
+ }
2334
+ return { summary, keywords };
2335
+ }
2336
+ /** Get the full indexed text content of a source. */
2337
+ async getFulltext(notebookId, sourceId) {
2338
+ const params = [[sourceId], [2], [2]];
2339
+ const result = await this.rpc.call(exports.RPCMethod.GET_SOURCE, params, {
2340
+ sourcePath: `/notebook/${notebookId}`,
2341
+ allowNull: true
2342
+ });
2343
+ if (!Array.isArray(result) || !result.length) {
2344
+ throw new Error(`Source ${sourceId} not found in notebook ${notebookId}`);
2345
+ }
2346
+ let title = "";
2347
+ let url = null;
2348
+ if (Array.isArray(result[0]) && result[0].length > 1) {
2349
+ title = typeof result[0][1] === "string" ? result[0][1] : "";
2350
+ const meta = result[0][2];
2351
+ if (Array.isArray(meta) && meta.length > 7 && Array.isArray(meta[7]) && typeof meta[7][0] === "string") {
2352
+ url = meta[7][0];
2353
+ }
2354
+ }
2355
+ let content = "";
2356
+ if (Array.isArray(result[3]) && result[3].length > 0) {
2357
+ const texts = extractAllText(result[3][0]);
2358
+ content = texts.join("\n");
2359
+ }
2360
+ return { sourceId, title, content, url, charCount: content.length };
2361
+ }
2362
+ /** Check if a source has newer content available. Returns true if fresh, false if stale. */
2363
+ async checkFreshness(notebookId, sourceId) {
2364
+ const params = [null, [sourceId], [2]];
2365
+ const result = await this.rpc.call(exports.RPCMethod.CHECK_SOURCE_FRESHNESS, params, {
2366
+ sourcePath: `/notebook/${notebookId}`,
2367
+ allowNull: true
2368
+ });
2369
+ if (result === true) return true;
2370
+ if (result === false) return false;
2371
+ if (Array.isArray(result)) {
2372
+ if (result.length === 0) return true;
2373
+ const first = result[0];
2374
+ if (Array.isArray(first) && first.length > 1 && first[1] === true) return true;
2375
+ }
2376
+ return false;
2377
+ }
2136
2378
  async delete(notebookId, sourceId) {
2137
2379
  const params = [notebookId, [sourceId], [2]];
2138
2380
  await this.rpc.call(exports.RPCMethod.DELETE_SOURCE, params, {
@@ -2187,6 +2429,15 @@ function extractSourceId(result) {
2187
2429
  console.log("extractSourceId debug info: could not parse:", JSON.stringify(result, null, 2));
2188
2430
  throw new Error("Could not extract source ID from API response");
2189
2431
  }
2432
+ function extractAllText(data, maxDepth = 100) {
2433
+ if (maxDepth <= 0) return [];
2434
+ const texts = [];
2435
+ for (const item of data) {
2436
+ if (typeof item === "string" && item.length > 0) texts.push(item);
2437
+ else if (Array.isArray(item)) texts.push(...extractAllText(item, maxDepth - 1));
2438
+ }
2439
+ return texts;
2440
+ }
2190
2441
  function sleep2(ms) {
2191
2442
  return new Promise((resolve) => setTimeout(resolve, ms));
2192
2443
  }