notebooklm-sdk 0.1.7 → 0.2.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/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
- import { readFileSync } from 'fs';
1
+ import { readFileSync, existsSync } from 'fs';
2
+ import { homedir } from 'os';
3
+ import { join } from 'path';
2
4
 
3
5
  var __defProp = Object.defineProperty;
4
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -451,10 +453,8 @@ function loadCookiesFromFile(filePath) {
451
453
  try {
452
454
  raw = readFileSync(filePath, "utf-8");
453
455
  } catch {
454
- throw new AuthError(
455
- `Cookie file not found: ${filePath}
456
- Provide valid Playwright storage state JSON.`
457
- );
456
+ throw new AuthError(`Session file not found: ${filePath}
457
+ Run: npx notebooklm-sdk login`);
458
458
  }
459
459
  return extractCookiesFromStorageState(JSON.parse(raw));
460
460
  }
@@ -497,7 +497,7 @@ function extractCookiesFromStorageState(storageState) {
497
497
  }
498
498
  if (!cookies["SID"]) {
499
499
  throw new AuthError(
500
- "Missing required cookie: SID. Provide valid Playwright storage state with Google cookies."
500
+ "Missing required cookie: SID. Session may be invalid or expired.\nRun: npx notebooklm-sdk login"
501
501
  );
502
502
  }
503
503
  return cookies;
@@ -536,7 +536,7 @@ function extractCsrfToken(html, finalUrl) {
536
536
  const match = /"SNlM0e"\s*:\s*"([^"]+)"/.exec(html);
537
537
  if (!match?.[1]) {
538
538
  if (isGoogleAuthRedirect(finalUrl) || html.includes("accounts.google.com")) {
539
- throw new AuthError("Authentication expired or invalid. Cookies may need to be refreshed.");
539
+ throw new AuthError("Session expired or invalid.\nRun: npx notebooklm-sdk login");
540
540
  }
541
541
  throw new AuthError("CSRF token (SNlM0e) not found in NotebookLM page HTML.");
542
542
  }
@@ -546,7 +546,7 @@ function extractSessionId(html, finalUrl) {
546
546
  const match = /"FdrFJe"\s*:\s*"([^"]+)"/.exec(html);
547
547
  if (!match?.[1]) {
548
548
  if (isGoogleAuthRedirect(finalUrl) || html.includes("accounts.google.com")) {
549
- throw new AuthError("Authentication expired or invalid. Cookies may need to be refreshed.");
549
+ throw new AuthError("Session expired or invalid.\nRun: npx notebooklm-sdk login");
550
550
  }
551
551
  throw new AuthError("Session ID (FdrFJe) not found in NotebookLM page HTML.");
552
552
  }
@@ -555,7 +555,7 @@ function extractSessionId(html, finalUrl) {
555
555
  function isGoogleAuthRedirect(url) {
556
556
  return url.includes("accounts.google.com") || url.includes("signin");
557
557
  }
558
- async function connect(opts) {
558
+ async function connect(opts = {}) {
559
559
  let cookieMap;
560
560
  let googleCookieHeader = null;
561
561
  if (opts.cookies) {
@@ -572,12 +572,23 @@ async function connect(opts) {
572
572
  }
573
573
  } else {
574
574
  const envCookies = process.env["NOTEBOOKLM_COOKIES"];
575
- if (envCookies) {
575
+ const envFile = process.env["NOTEBOOKLM_COOKIES_FILE"];
576
+ if (envFile) {
577
+ cookieMap = loadCookiesFromFile(envFile);
578
+ } else if (existsSync(DEFAULT_SESSION_FILE)) {
579
+ const raw = readFileSync(DEFAULT_SESSION_FILE, "utf-8");
580
+ const storageState = JSON.parse(raw);
581
+ cookieMap = loadCookiesFromObject(storageState);
582
+ googleCookieHeader = buildGoogleCookieHeader(storageState);
583
+ } else if (existsSync("storage_state.json")) {
584
+ const raw = readFileSync("storage_state.json", "utf-8");
585
+ const storageState = JSON.parse(raw);
586
+ cookieMap = loadCookiesFromObject(storageState);
587
+ googleCookieHeader = buildGoogleCookieHeader(storageState);
588
+ } else if (envCookies) {
576
589
  cookieMap = loadCookiesFromString(envCookies);
577
590
  } else {
578
- throw new AuthError(
579
- "No cookies provided. Pass cookies, cookiesFile, or cookiesObject to connect()."
580
- );
591
+ throw new AuthError("No session found. Run: npx notebooklm-sdk login");
581
592
  }
582
593
  }
583
594
  const { csrfToken, sessionId } = await fetchTokens(cookieMap);
@@ -590,10 +601,11 @@ async function connect(opts) {
590
601
  googleCookieHeader: googleCookieHeader ?? cookieHeader
591
602
  };
592
603
  }
593
- var NOTEBOOKLM_URL;
604
+ var DEFAULT_SESSION_FILE, NOTEBOOKLM_URL;
594
605
  var init_auth = __esm({
595
606
  "src/auth.ts"() {
596
607
  init_errors();
608
+ DEFAULT_SESSION_FILE = join(homedir(), ".notebooklm", "session.json");
597
609
  NOTEBOOKLM_URL = "https://notebooklm.google.com/";
598
610
  }
599
611
  });
@@ -711,26 +723,6 @@ function parseArtifact(data, notebookId) {
711
723
  _raw: Array.isArray(data) ? data : []
712
724
  };
713
725
  }
714
- function parseNote(data) {
715
- const id = typeof data[0] === "string" ? data[0] : "";
716
- const content = typeof data[1] === "string" ? data[1] : "";
717
- const title = typeof data[2] === "string" ? data[2] : null;
718
- let createdAt = null;
719
- let updatedAt = null;
720
- if (Array.isArray(data[3]) && typeof data[3][0] === "number") {
721
- try {
722
- createdAt = new Date(data[3][0] * 1e3);
723
- } catch {
724
- }
725
- }
726
- if (Array.isArray(data[4]) && typeof data[4][0] === "number") {
727
- try {
728
- updatedAt = new Date(data[4][0] * 1e3);
729
- } catch {
730
- }
731
- }
732
- return { id, title, content, createdAt, updatedAt };
733
- }
734
726
 
735
727
  // src/api/artifacts.ts
736
728
  function tripleNest(ids) {
@@ -740,18 +732,13 @@ function doubleNest(ids) {
740
732
  return ids.map((id) => [id]);
741
733
  }
742
734
  var ArtifactsAPI = class {
743
- constructor(rpc, auth) {
735
+ constructor(rpc, auth, notes) {
744
736
  this.rpc = rpc;
745
737
  this.auth = auth;
738
+ this.notes = notes;
746
739
  }
747
740
  async list(notebookId) {
748
- const params = [[2], notebookId, 'NOT artifact.status = "ARTIFACT_STATUS_SUGGESTED"'];
749
- const result = await this.rpc.call(RPCMethod.LIST_ARTIFACTS, params, {
750
- sourcePath: `/notebook/${notebookId}`,
751
- allowNull: true
752
- });
753
- if (!Array.isArray(result) || !result.length) return [];
754
- const rawList = Array.isArray(result[0]) ? result[0] : result;
741
+ const rawList = await this._listRaw(notebookId);
755
742
  const artifacts = [];
756
743
  for (const item of rawList) {
757
744
  if (Array.isArray(item)) {
@@ -763,6 +750,15 @@ var ArtifactsAPI = class {
763
750
  }
764
751
  return artifacts;
765
752
  }
753
+ async _listRaw(notebookId) {
754
+ const params = [[2], notebookId, 'NOT artifact.status = "ARTIFACT_STATUS_SUGGESTED"'];
755
+ const result = await this.rpc.call(RPCMethod.LIST_ARTIFACTS, params, {
756
+ sourcePath: `/notebook/${notebookId}`,
757
+ allowNull: true
758
+ });
759
+ if (!Array.isArray(result) || !result.length) return [];
760
+ return Array.isArray(result[0]) ? result[0] : result;
761
+ }
766
762
  async get(notebookId, artifactId) {
767
763
  const artifacts = await this.list(notebookId);
768
764
  return artifacts.find((a) => a.id === artifactId) ?? null;
@@ -942,6 +938,37 @@ var ArtifactsAPI = class {
942
938
  ];
943
939
  return this._callGenerate(notebookId, params);
944
940
  }
941
+ async createDataTable(notebookId, opts = {}) {
942
+ const language = opts.language ?? "en";
943
+ const sourceIds = opts.sourceIds ?? await this.rpc.getSourceIds(notebookId);
944
+ const triple = tripleNest(sourceIds);
945
+ const params = [
946
+ [2],
947
+ notebookId,
948
+ [
949
+ null,
950
+ null,
951
+ ArtifactTypeCode.DATA_TABLE,
952
+ triple,
953
+ null,
954
+ null,
955
+ null,
956
+ null,
957
+ null,
958
+ null,
959
+ null,
960
+ null,
961
+ null,
962
+ null,
963
+ null,
964
+ null,
965
+ null,
966
+ null,
967
+ [null, [opts.instructions ?? null, language]]
968
+ ]
969
+ ];
970
+ return this._callGenerate(notebookId, params);
971
+ }
945
972
  async createReport(notebookId, opts = {}) {
946
973
  const format = opts.format ?? "briefing_doc";
947
974
  const language = opts.language ?? "en";
@@ -1007,7 +1034,15 @@ ${opts.extraInstructions}` : cfg.prompt;
1007
1034
  sourcePath: `/notebook/${notebookId}`,
1008
1035
  allowNull: true
1009
1036
  });
1010
- return this._parseGenerationResult(result);
1037
+ const mindMapJson = Array.isArray(result) && Array.isArray(result[0]) && typeof result[0][0] === "string" ? result[0][0] : null;
1038
+ if (!mindMapJson) throw new Error("Mind map generation returned no content");
1039
+ let title = "Mind Map";
1040
+ try {
1041
+ const parsed = JSON.parse(mindMapJson);
1042
+ if (typeof parsed["name"] === "string") title = parsed["name"];
1043
+ } catch {
1044
+ }
1045
+ return this.notes.create(notebookId, mindMapJson, title);
1011
1046
  }
1012
1047
  // ---------------------------------------------------------------------------
1013
1048
  // Polling / download
@@ -1063,6 +1098,48 @@ ${opts.extraInstructions}` : cfg.prompt;
1063
1098
  }
1064
1099
  return null;
1065
1100
  }
1101
+ /** Download a completed slide deck as PDF or PPTX. Returns a Buffer. */
1102
+ async downloadSlideDeck(notebookId, artifactId, format = "pdf") {
1103
+ const rawList = await this._listRaw(notebookId);
1104
+ const raw = rawList.find(
1105
+ (a) => a[0] === artifactId && a[2] === ArtifactTypeCode.SLIDE_DECK
1106
+ );
1107
+ if (!raw) throw new ArtifactNotReadyError("slide_deck", { artifactId });
1108
+ const metadata = raw[16];
1109
+ if (!Array.isArray(metadata)) throw new ArtifactNotReadyError("slide_deck", { artifactId });
1110
+ const url = format === "pptx" ? metadata[4] : metadata[3];
1111
+ if (typeof url !== "string" || !url.startsWith("http")) {
1112
+ throw new ArtifactNotReadyError("slide_deck", { artifactId, status: `no ${format} url` });
1113
+ }
1114
+ return this._fetchMediaWithCookies(url);
1115
+ }
1116
+ /** Download a completed infographic as PNG. Returns a Buffer. */
1117
+ async downloadInfographic(notebookId, artifactId) {
1118
+ const rawList = await this._listRaw(notebookId);
1119
+ const raw = rawList.find(
1120
+ (a) => a[0] === artifactId && a[2] === ArtifactTypeCode.INFOGRAPHIC
1121
+ );
1122
+ if (!raw) throw new ArtifactNotReadyError("infographic", { artifactId });
1123
+ let url = null;
1124
+ for (let i = raw.length - 1; i >= 0; i--) {
1125
+ const item = raw[i];
1126
+ if (Array.isArray(item) && Array.isArray(item[2]) && Array.isArray(item[2][0]) && Array.isArray(item[2][0][1]) && typeof item[2][0][1][0] === "string" && item[2][0][1][0].startsWith("http")) {
1127
+ url = item[2][0][1][0];
1128
+ break;
1129
+ }
1130
+ }
1131
+ if (!url) throw new ArtifactNotReadyError("infographic", { artifactId });
1132
+ return this._fetchMediaWithCookies(url);
1133
+ }
1134
+ /** Get parsed headers and rows from a completed data table artifact. */
1135
+ async getDataTableContent(notebookId, artifactId) {
1136
+ const artifacts = await this._listRaw(notebookId);
1137
+ const raw = artifacts.find(
1138
+ (a) => Array.isArray(a) && a[0] === artifactId && a[2] === ArtifactTypeCode.DATA_TABLE
1139
+ );
1140
+ if (!raw || !Array.isArray(raw) || !Array.isArray(raw[18])) return null;
1141
+ return parseDataTable(raw[18]);
1142
+ }
1066
1143
  // ---------------------------------------------------------------------------
1067
1144
  // Internal
1068
1145
  // ---------------------------------------------------------------------------
@@ -1119,6 +1196,34 @@ ${opts.extraInstructions}` : cfg.prompt;
1119
1196
  return { artifactId: null, status: "failed" };
1120
1197
  }
1121
1198
  };
1199
+ function extractCellText(cell) {
1200
+ if (typeof cell === "string") return cell;
1201
+ if (typeof cell === "number") return "";
1202
+ if (Array.isArray(cell)) return cell.map(extractCellText).join("");
1203
+ return "";
1204
+ }
1205
+ function parseDataTable(rawData) {
1206
+ try {
1207
+ const nav = rawData;
1208
+ const rowsArray = nav[0][0][0][0][4][2];
1209
+ if (!rowsArray?.length) throw new Error("Empty data table");
1210
+ const headers = [];
1211
+ const rows = [];
1212
+ for (let i = 0; i < rowsArray.length; i++) {
1213
+ const rowSection = rowsArray[i];
1214
+ if (!Array.isArray(rowSection) || rowSection.length < 3) continue;
1215
+ const cellArray = rowSection[2];
1216
+ if (!Array.isArray(cellArray)) continue;
1217
+ const values = cellArray.map(extractCellText);
1218
+ if (i === 0) headers.push(...values);
1219
+ else rows.push(values);
1220
+ }
1221
+ if (!headers.length) throw new Error("No headers found");
1222
+ return { headers, rows };
1223
+ } catch (e) {
1224
+ throw new Error(`Failed to parse data table: ${e}`);
1225
+ }
1226
+ }
1122
1227
  function sleep(ms) {
1123
1228
  return new Promise((resolve) => setTimeout(resolve, ms));
1124
1229
  }
@@ -1455,61 +1560,69 @@ var NotesAPI = class {
1455
1560
  this.rpc = rpc;
1456
1561
  }
1457
1562
  async list(notebookId) {
1458
- const params = [notebookId, [2]];
1459
- const result = await this.rpc.call(RPCMethod.GET_NOTES_AND_MIND_MAPS, params, {
1460
- sourcePath: `/notebook/${notebookId}`
1461
- });
1462
- const notes = [];
1463
- const mindMaps = [];
1464
- if (!Array.isArray(result)) return { notes, mindMaps };
1465
- try {
1466
- const notesData = result[0];
1467
- if (Array.isArray(notesData)) {
1468
- for (const n of notesData) {
1469
- if (Array.isArray(n)) notes.push(parseNote(n));
1470
- }
1471
- }
1472
- const mapsData = result[1];
1473
- if (Array.isArray(mapsData)) {
1474
- for (const m of mapsData) {
1475
- if (Array.isArray(m)) {
1476
- mindMaps.push({
1477
- id: typeof m[0] === "string" ? m[0] : "",
1478
- title: typeof m[2] === "string" ? m[2] : null,
1479
- content: typeof m[1] === "string" ? m[1] : "",
1480
- createdAt: Array.isArray(m[3]) && typeof m[3][0] === "number" ? new Date(m[3][0] * 1e3) : null
1481
- });
1482
- }
1483
- }
1484
- }
1485
- } catch {
1486
- }
1487
- return { notes, mindMaps };
1563
+ const all = await this._fetchAll(notebookId);
1564
+ return all.filter((n) => !this._isMindMap(n.content));
1565
+ }
1566
+ async listMindMaps(notebookId) {
1567
+ const all = await this._fetchAll(notebookId);
1568
+ return all.filter((n) => this._isMindMap(n.content));
1488
1569
  }
1489
1570
  async create(notebookId, content, title) {
1490
- const params = [notebookId, content, title ?? null, [2]];
1491
- const result = await this.rpc.call(RPCMethod.CREATE_NOTE, params, {
1492
- sourcePath: `/notebook/${notebookId}`
1571
+ const createParams = [notebookId, "", [1], null, "New Note"];
1572
+ const result = await this.rpc.call(RPCMethod.CREATE_NOTE, createParams, {
1573
+ sourcePath: `/notebook/${notebookId}`,
1574
+ allowNull: true
1493
1575
  });
1494
- if (Array.isArray(result)) return parseNote(result);
1495
- throw new Error("Could not parse note creation response");
1576
+ const noteId = Array.isArray(result) && Array.isArray(result[0]) && typeof result[0][0] === "string" ? result[0][0] : Array.isArray(result) && typeof result[0] === "string" ? result[0] : null;
1577
+ if (!noteId) throw new Error("CREATE_NOTE did not return a note ID");
1578
+ await this.update(notebookId, noteId, content, title ?? "New Note");
1579
+ return { id: noteId, title: title ?? null, content, createdAt: null, updatedAt: /* @__PURE__ */ new Date() };
1496
1580
  }
1497
1581
  async update(notebookId, noteId, content, title) {
1498
- const params = [notebookId, noteId, content, title ?? null, [2]];
1499
- const result = await this.rpc.call(RPCMethod.UPDATE_NOTE, params, {
1500
- sourcePath: `/notebook/${notebookId}`
1582
+ const params = [notebookId, noteId, [[[content, title ?? "New Note", [], 0]]]];
1583
+ await this.rpc.call(RPCMethod.UPDATE_NOTE, params, {
1584
+ sourcePath: `/notebook/${notebookId}`,
1585
+ allowNull: true
1501
1586
  });
1502
- if (Array.isArray(result)) return parseNote(result);
1503
1587
  return { id: noteId, title: title ?? null, content, createdAt: null, updatedAt: /* @__PURE__ */ new Date() };
1504
1588
  }
1505
1589
  async delete(notebookId, noteId) {
1506
- const params = [notebookId, noteId, [2]];
1590
+ const params = [notebookId, null, [noteId]];
1507
1591
  await this.rpc.call(RPCMethod.DELETE_NOTE, params, {
1508
1592
  sourcePath: `/notebook/${notebookId}`,
1509
1593
  allowNull: true
1510
1594
  });
1511
1595
  return true;
1512
1596
  }
1597
+ async _fetchAll(notebookId) {
1598
+ const result = await this.rpc.call(RPCMethod.GET_NOTES_AND_MIND_MAPS, [notebookId], {
1599
+ sourcePath: `/notebook/${notebookId}`,
1600
+ allowNull: true
1601
+ });
1602
+ if (!Array.isArray(result) || !Array.isArray(result[0])) return [];
1603
+ const notes = [];
1604
+ for (const item of result[0]) {
1605
+ if (!Array.isArray(item) || typeof item[0] !== "string") continue;
1606
+ if (item[1] === null && item[2] === 2) continue;
1607
+ const content = this._extractContent(item);
1608
+ notes.push(this._parseItem(item, notebookId, content));
1609
+ }
1610
+ return notes;
1611
+ }
1612
+ _isMindMap(content) {
1613
+ return content.includes('"children":') || content.includes('"nodes":');
1614
+ }
1615
+ _extractContent(item) {
1616
+ if (typeof item[1] === "string") return item[1];
1617
+ if (Array.isArray(item[1]) && typeof item[1][1] === "string") return item[1][1];
1618
+ return "";
1619
+ }
1620
+ _parseItem(item, _notebookId, content) {
1621
+ const inner = Array.isArray(item[1]) ? item[1] : null;
1622
+ const title = inner && typeof inner[4] === "string" && inner[4] ? inner[4] : null;
1623
+ const createdAt = Array.isArray(item[3]) && typeof item[3][0] === "number" ? new Date(item[3][0] * 1e3) : null;
1624
+ return { id: item[0], title, content, createdAt, updatedAt: null };
1625
+ }
1513
1626
  };
1514
1627
 
1515
1628
  // src/api/research.ts
@@ -1646,7 +1759,7 @@ var ResearchAPI = class {
1646
1759
  const webSources = sources.filter((s) => s.url && !reportSourceSet.has(s));
1647
1760
  if (!webSources.length && !reportSources.length) return [];
1648
1761
  const sourceArray = [
1649
- ...reportSources.map((s) => buildReportEntry(s.title, s.reportMarkdown)),
1762
+ ...reportSources.filter((s) => s.reportMarkdown).map((s) => buildReportEntry(s.title, s.reportMarkdown)),
1650
1763
  ...webSources.map((s) => buildWebEntry(s.url, s.title))
1651
1764
  ];
1652
1765
  const params = [null, [1], effectiveTaskId, notebookId, sourceArray];
@@ -2376,9 +2489,9 @@ var NotebookLMClient = class _NotebookLMClient {
2376
2489
  const rpc = new RPCCore(auth, opts.timeoutMs);
2377
2490
  this.notebooks = new NotebooksAPI(rpc);
2378
2491
  this.sources = new SourcesAPI(rpc, auth);
2379
- this.artifacts = new ArtifactsAPI(rpc, auth);
2380
- this.chat = new ChatAPI(rpc, auth);
2381
2492
  this.notes = new NotesAPI(rpc);
2493
+ this.artifacts = new ArtifactsAPI(rpc, auth, this.notes);
2494
+ this.chat = new ChatAPI(rpc, auth);
2382
2495
  this.research = new ResearchAPI(rpc);
2383
2496
  this.settings = new SettingsAPI(rpc);
2384
2497
  this.sharing = new SharingAPI(rpc);
@@ -2395,7 +2508,7 @@ var NotebookLMClient = class _NotebookLMClient {
2395
2508
  * Connect to NotebookLM using cookies.
2396
2509
  * Fetches CSRF and session tokens from the NotebookLM homepage.
2397
2510
  */
2398
- static async connect(opts, clientOpts = {}) {
2511
+ static async connect(opts = {}, clientOpts = {}) {
2399
2512
  const auth = await connect(opts);
2400
2513
  return new _NotebookLMClient(auth, clientOpts);
2401
2514
  }