notebooklm-sdk 0.2.0 → 0.3.0

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,7 @@ __export(enums_exports, {
21
21
  ArtifactTypeCode: () => exports.ArtifactTypeCode,
22
22
  AudioFormat: () => exports.AudioFormat,
23
23
  AudioLength: () => exports.AudioLength,
24
+ ChatMode: () => exports.ChatMode,
24
25
  ExportType: () => exports.ExportType,
25
26
  InfographicDetail: () => exports.InfographicDetail,
26
27
  InfographicOrientation: () => exports.InfographicOrientation,
@@ -38,6 +39,7 @@ __export(enums_exports, {
38
39
  VideoStyle: () => exports.VideoStyle,
39
40
  artifactStatusFromCode: () => artifactStatusFromCode,
40
41
  artifactTypeFromCode: () => artifactTypeFromCode,
42
+ chatModeToParams: () => chatModeToParams,
41
43
  sourceStatusFromCode: () => sourceStatusFromCode,
42
44
  sourceTypeFromCode: () => sourceTypeFromCode
43
45
  });
@@ -59,7 +61,10 @@ function artifactStatusFromCode(code) {
59
61
  function sourceStatusFromCode(code) {
60
62
  return SOURCE_STATUS_MAP[code] ?? "unknown";
61
63
  }
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;
64
+ function chatModeToParams(mode) {
65
+ return CHAT_MODE_PARAMS[mode];
66
+ }
67
+ 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.ChatMode = void 0; var CHAT_MODE_PARAMS; exports.ShareAccess = void 0; exports.ShareViewLevel = void 0; exports.SharePermission = void 0;
63
68
  var init_enums = __esm({
64
69
  "src/types/enums.ts"() {
65
70
  exports.RPCMethod = {
@@ -243,6 +248,22 @@ var init_enums = __esm({
243
248
  3: "error",
244
249
  5: "preparing"
245
250
  };
251
+ exports.ChatMode = {
252
+ /** General purpose — balanced length and style. */
253
+ DEFAULT: "default",
254
+ /** Educational focus with longer, learning-oriented responses. */
255
+ LEARNING_GUIDE: "learning_guide",
256
+ /** Short, concise answers. */
257
+ CONCISE: "concise",
258
+ /** Verbose, detailed answers. */
259
+ DETAILED: "detailed"
260
+ };
261
+ CHAT_MODE_PARAMS = {
262
+ default: [1, 1],
263
+ learning_guide: [3, 4],
264
+ concise: [1, 5],
265
+ detailed: [1, 4]
266
+ };
246
267
  exports.ShareAccess = {
247
268
  /** Only explicitly shared users can access */
248
269
  RESTRICTED: 0,
@@ -1133,6 +1154,39 @@ ${opts.extraInstructions}` : cfg.prompt;
1133
1154
  if (!url) throw new exports.ArtifactNotReadyError("infographic", { artifactId });
1134
1155
  return this._fetchMediaWithCookies(url);
1135
1156
  }
1157
+ /** Get AI-suggested report formats based on notebook content. */
1158
+ async suggestReports(notebookId) {
1159
+ const params = [[2], notebookId];
1160
+ const result = await this.rpc.call(exports.RPCMethod.GET_SUGGESTED_REPORTS, params, {
1161
+ sourcePath: `/notebook/${notebookId}`,
1162
+ allowNull: true,
1163
+ timeoutMs: 12e4
1164
+ });
1165
+ if (!Array.isArray(result) || !result.length) return [];
1166
+ const items = Array.isArray(result[0]) ? result[0] : result;
1167
+ const suggestions = [];
1168
+ for (const item of items) {
1169
+ if (Array.isArray(item) && item.length >= 5) {
1170
+ suggestions.push({
1171
+ title: typeof item[0] === "string" ? item[0] : "",
1172
+ description: typeof item[1] === "string" ? item[1] : "",
1173
+ prompt: typeof item[4] === "string" ? item[4] : "",
1174
+ audienceLevel: typeof item[5] === "number" ? item[5] : 2
1175
+ });
1176
+ }
1177
+ }
1178
+ return suggestions;
1179
+ }
1180
+ /** Revise an individual slide in a completed slide deck using a prompt. */
1181
+ async reviseSlide(notebookId, artifactId, slideIndex, prompt) {
1182
+ if (slideIndex < 0) throw new Error("slideIndex must be >= 0");
1183
+ const params = [[2], artifactId, [[[slideIndex, prompt]]]];
1184
+ const result = await this.rpc.call(exports.RPCMethod.REVISE_SLIDE, params, {
1185
+ sourcePath: `/notebook/${notebookId}`,
1186
+ allowNull: true
1187
+ });
1188
+ return this._parseGenerationResult(result);
1189
+ }
1136
1190
  /** Get parsed headers and rows from a completed data table artifact. */
1137
1191
  async getDataTableContent(notebookId, artifactId) {
1138
1192
  const artifacts = await this._listRaw(notebookId);
@@ -1356,6 +1410,19 @@ var ChatAPI = class {
1356
1410
  }
1357
1411
  return null;
1358
1412
  }
1413
+ /**
1414
+ * Set the chat mode for a notebook. Persists on the server — affects all
1415
+ * subsequent `ask()` calls until changed.
1416
+ */
1417
+ async setMode(notebookId, mode) {
1418
+ const [goal, length] = chatModeToParams(mode);
1419
+ const chatSettings = [[goal], [length]];
1420
+ const params = [notebookId, [[null, null, null, null, null, null, null, chatSettings]]];
1421
+ await this.rpc.call(exports.RPCMethod.RENAME_NOTEBOOK, params, {
1422
+ sourcePath: `/notebook/${notebookId}`,
1423
+ allowNull: true
1424
+ });
1425
+ }
1359
1426
  clearCache(conversationId) {
1360
1427
  if (conversationId) {
1361
1428
  this.conversationCache.delete(conversationId);
@@ -1526,6 +1593,9 @@ var NotebooksAPI = class {
1526
1593
  }
1527
1594
  return "";
1528
1595
  }
1596
+ async removeFromRecent(notebookId) {
1597
+ await this.rpc.call(exports.RPCMethod.REMOVE_RECENTLY_VIEWED, [notebookId], { allowNull: true });
1598
+ }
1529
1599
  async getDescription(notebookId) {
1530
1600
  const params = [notebookId, [2]];
1531
1601
  const result = await this.rpc.call(exports.RPCMethod.SUMMARIZE, params, {
@@ -2133,6 +2203,74 @@ var SourcesAPI = class {
2133
2203
  const uploadResult = await uploadResp.text();
2134
2204
  return uploadResult.trim();
2135
2205
  }
2206
+ /** Get the AI-generated Source Guide (summary + keywords) for a source. */
2207
+ async getGuide(notebookId, sourceId) {
2208
+ const params = [[[[sourceId]]]];
2209
+ const result = await this.rpc.call(exports.RPCMethod.GET_SOURCE_GUIDE, params, {
2210
+ sourcePath: `/notebook/${notebookId}`,
2211
+ allowNull: true,
2212
+ timeoutMs: 12e4
2213
+ });
2214
+ let summary = "";
2215
+ let keywords = [];
2216
+ if (Array.isArray(result) && result.length > 0) {
2217
+ const outer = result[0];
2218
+ if (Array.isArray(outer) && outer.length > 0) {
2219
+ const inner = outer[0];
2220
+ if (Array.isArray(inner)) {
2221
+ if (inner.length > 1 && Array.isArray(inner[1]) && typeof inner[1][0] === "string") {
2222
+ summary = inner[1][0];
2223
+ }
2224
+ if (inner.length > 2 && Array.isArray(inner[2]) && Array.isArray(inner[2][0])) {
2225
+ keywords = inner[2][0].filter((k) => typeof k === "string");
2226
+ }
2227
+ }
2228
+ }
2229
+ }
2230
+ return { summary, keywords };
2231
+ }
2232
+ /** Get the full indexed text content of a source. */
2233
+ async getFulltext(notebookId, sourceId) {
2234
+ const params = [[sourceId], [2], [2]];
2235
+ const result = await this.rpc.call(exports.RPCMethod.GET_SOURCE, params, {
2236
+ sourcePath: `/notebook/${notebookId}`,
2237
+ allowNull: true
2238
+ });
2239
+ if (!Array.isArray(result) || !result.length) {
2240
+ throw new Error(`Source ${sourceId} not found in notebook ${notebookId}`);
2241
+ }
2242
+ let title = "";
2243
+ let url = null;
2244
+ if (Array.isArray(result[0]) && result[0].length > 1) {
2245
+ title = typeof result[0][1] === "string" ? result[0][1] : "";
2246
+ const meta = result[0][2];
2247
+ if (Array.isArray(meta) && meta.length > 7 && Array.isArray(meta[7]) && typeof meta[7][0] === "string") {
2248
+ url = meta[7][0];
2249
+ }
2250
+ }
2251
+ let content = "";
2252
+ if (Array.isArray(result[3]) && result[3].length > 0) {
2253
+ const texts = extractAllText(result[3][0]);
2254
+ content = texts.join("\n");
2255
+ }
2256
+ return { sourceId, title, content, url, charCount: content.length };
2257
+ }
2258
+ /** Check if a source has newer content available. Returns true if fresh, false if stale. */
2259
+ async checkFreshness(notebookId, sourceId) {
2260
+ const params = [null, [sourceId], [2]];
2261
+ const result = await this.rpc.call(exports.RPCMethod.CHECK_SOURCE_FRESHNESS, params, {
2262
+ sourcePath: `/notebook/${notebookId}`,
2263
+ allowNull: true
2264
+ });
2265
+ if (result === true) return true;
2266
+ if (result === false) return false;
2267
+ if (Array.isArray(result)) {
2268
+ if (result.length === 0) return true;
2269
+ const first = result[0];
2270
+ if (Array.isArray(first) && first.length > 1 && first[1] === true) return true;
2271
+ }
2272
+ return false;
2273
+ }
2136
2274
  async delete(notebookId, sourceId) {
2137
2275
  const params = [notebookId, [sourceId], [2]];
2138
2276
  await this.rpc.call(exports.RPCMethod.DELETE_SOURCE, params, {
@@ -2187,6 +2325,15 @@ function extractSourceId(result) {
2187
2325
  console.log("extractSourceId debug info: could not parse:", JSON.stringify(result, null, 2));
2188
2326
  throw new Error("Could not extract source ID from API response");
2189
2327
  }
2328
+ function extractAllText(data, maxDepth = 100) {
2329
+ if (maxDepth <= 0) return [];
2330
+ const texts = [];
2331
+ for (const item of data) {
2332
+ if (typeof item === "string" && item.length > 0) texts.push(item);
2333
+ else if (Array.isArray(item)) texts.push(...extractAllText(item, maxDepth - 1));
2334
+ }
2335
+ return texts;
2336
+ }
2190
2337
  function sleep2(ms) {
2191
2338
  return new Promise((resolve) => setTimeout(resolve, ms));
2192
2339
  }