diff --git a/.gitignore b/.gitignore index dc2baf8..1781c4b 100644 --- a/.gitignore +++ b/.gitignore @@ -109,3 +109,5 @@ markdown markdown.zip .envrc json/ + +backups \ No newline at end of file diff --git a/README.md b/README.md index 2b7820e..d61cc82 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - # notion-backup This is a very simple tool to export a workspace from [Notion](https://www.notion.so/), designed @@ -18,10 +17,10 @@ Automatically downloading backups from Notion requires two unique authentication 4. Insert `getSpaces` into the search filter of the Network tab. This should give you one result. Click on it. 5. In the Preview tab, look for the key `space`. There you should find a list of all the workspaces you have access to. Unless you're part of shared workspaces there should only be one. 6. Copy the UUID of the workspace you want to backup (e.g. `6e560115-7a65-4f65-bb04-1825b43748f1`). This is your `NOTION_SPACE_ID`. -6. Switch to the Application (Chrome, Edge) or Storage (Firefox, Safari) tab on the top. -7. In the left sidebar, select `Cookies` -> `https://www.notion.so` (Chrome, Edge, Firefox) or `Cookies – https://www.notion.so` (Safari). -8. Copy the value of `token_v2` as your `NOTION_TOKEN` and the value of `file_token` as your `NOTION_FILE_TOKEN`. -9. Set the three environment variables as secrets for actions in your GitHub repository. +7. Switch to the Application (Chrome, Edge) or Storage (Firefox, Safari) tab on the top. +8. In the left sidebar, select `Cookies` -> `https://www.notion.so` (Chrome, Edge, Firefox) or `Cookies – https://www.notion.so` (Safari). +9. Copy the value of `token_v2` as your `NOTION_TOKEN` and the value of `file_token` as your `NOTION_FILE_TOKEN`. +10. Set the three environment variables as secrets for actions in your GitHub repository. **NOTE**: if you log out of your account or your session expires naturally, the `NOTION_TOKEN` and `NOTION_FILE_TOKEN` will get invalidated and the backup will fail. In this case you need to obtain new tokens by repeating this process. There is currently no practical way to automize this until Notion decide to add a backup endpoint to their official API, at which point this script will be able to use a proper authentication token. @@ -35,7 +34,8 @@ This assumes you are looking to set this up to back up Notion to GitHub. 4. Give Actions write access to your repository: `Settings` > `Actions` > `General` > `Workflow permissions` > choose `Read and write permissions` 5. Install the following under `.github/workflows/whatever.yml` in your repo. 6. Configure the frequency by changing the `cron` value. You can use [Crontab.guru](https://crontab.guru/#0_*/4_*_*_*). -7. Push and control your backup workflow from the `Actions` tab of your repository Github page. +7. Configure the time zone and locale by changing the `NOTION_TIME_ZONE` and `NOTION_LOCALE` value. ([Timezones](https://momentjs.com/timezone/)) +8. Push and control your backup workflow from the `Actions` tab of your repository Github page. ```yaml name: "Notion backup" @@ -62,9 +62,6 @@ jobs: with: node-version: '18' - - name: Delete previous backup - run: rm -rf markdown html *.zip - - name: Setup dependencies run: npm install -g notion-backup @@ -74,13 +71,15 @@ jobs: NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }} NOTION_FILE_TOKEN: ${{ secrets.NOTION_FILE_TOKEN }} NOTION_SPACE_ID: ${{ secrets.NOTION_SPACE_ID }} + NOTION_TIME_ZONE: 'America/New_York' + NOTION_LOCALE: 'en' NODE_OPTIONS: "--max-http-header-size 15000" - name: Delete zips run: | rm -f *.zip - rm -f markdown/*-Part*.zip - rm -f html/*-Part*.zip + find markdown -name "*-Part*.zip" -type f -exec rm -f {} \; + find html -name "*-Part*.zip" -type f -exec rm -f {} \; - name: Commit changes run: | @@ -102,3 +101,12 @@ You won't be able to backup files exceeding a size of 100MB unless you enable [G *.jpeg filter=lfs diff=lfs merge=lfs -text *.psd filter=lfs diff=lfs merge=lfs -text ``` + +## [Windows: Git filename too long](https://stackoverflow.com/questions/22575662/filename-too-long-in-git-for-windows) + +1. Enable long paths in Windows: [Tutorial](https://www.microfocus.com/documentation/filr/filr-4/filr-desktop/t47bx2ogpfz7.html) +2. Enable long paths in Git: + +```shell +git config core.longpaths true +``` diff --git a/local-backup.js b/local-backup.js new file mode 100644 index 0000000..e16556c --- /dev/null +++ b/local-backup.js @@ -0,0 +1,47 @@ +const { exec } = require("child_process"); +const { mkdir, opendir } = require("fs/promises"); +require("dotenv").config(); +const nb = require("./notion-backup"); + +async function run() { + if (!process.env.NOTION_BACKUP_DIR) { + console.log("Env variable NOTION_BACKUP_DIR needs to be specified."); + process.exit(1); + } + + await mkdir(process.env.NOTION_BACKUP_DIR, { recursive: true }); + + await nb.run(); + + exec( + `cd ${process.env.NOTION_BACKUP_DIR};` + + "rm -f *.zip;" + + 'find markdown -name "*-Part*.zip" -type f -delete ;' + + 'find html -name "*-Part*.zip" -type f -delete ;', + (_, stdout, stderr) => { + if (stderr) console.log("STDOUT:", stdout, ", STDERR:", stderr); + } + ); +} + +async function dwada() { + const dir = await opendir(process.env.NOTION_BACKUP_DIR, { + recursive: true, + }); + + for await (let dirent of dir) { + console.log(dirent.name); + /* + if (!dirent.isDirectory()) continue; + + const innerDir = await opendir( + `${process.env.NOTION_BACKUP_DIR}\\${dirent.name}` + ); + for await (let innerDirent of innerDir) { + console.log(innerDirent.name); + }*/ + } +} + +//run(); +dwada(); diff --git a/notion-backup-item.js b/notion-backup-item.js index bdf63cb..b2870a1 100755 --- a/notion-backup-item.js +++ b/notion-backup-item.js @@ -1,51 +1,50 @@ #!/usr/bin/env node -let { program } = require('commander') - , { writeFileSync, mkdirSync } = require('fs') - , { join, isAbsolute, dirname } = require('path') - , { NotionAPI } = require('notion-client') - , { parsePageId } = require('notion-utils') - , { NOTION_TOKEN } = process.env - , nc = new NotionAPI({ authToken: NOTION_TOKEN }) - , die = (str) => { - console.error(str); - process.exit(1); - } -; - +let { program } = require("commander"), + { writeFileSync, mkdirSync } = require("fs"), + { join, isAbsolute, dirname } = require("path"), + { NotionAPI } = require("notion-client"), + { parsePageId } = require("notion-utils"), + { NOTION_TOKEN, NOTION_BACKUP_DIR } = process.env, + nc = new NotionAPI({ authToken: NOTION_TOKEN }), + die = (str) => { + console.error(str); + process.exit(1); + }; // --version -program.version(require('./package.json').version); +program.version(require("./package.json").version); program - .option('-i, --id ', 'ID of the Notion document') - .option('-c, --collection ', 'ID of the Notion collection') - .option('-v, --view ', 'ID of the Notion collection view') - .option('-o, --out ', 'File to write to') -; + .option("-i, --id ", "ID of the Notion document") + .option("-c, --collection ", "ID of the Notion collection") + .option("-v, --view ", "ID of the Notion collection view") + .option("-o, --out ", "File to write to"); // now do something program.parse(process.argv); -async function run () { - let { id, collection, view, out } = program.opts() - , refPage - ; +async function run() { + let { id, collection, view, out } = program.opts(), + refPage; if (collection) { - if (!view) die('The --collection option requires --view to also be specified.'); - if (id) console.warn('Warning: --id will be ignored.'); - refPage = await nc.getCollectionData(parsePageId(collection), parsePageId(view)); - } - else if (id) { - if (view) console.warn('Warning: --view will be ignored.'); + if (!view) + die("The --collection option requires --view to also be specified."); + if (id) console.warn("Warning: --id will be ignored."); + refPage = await nc.getCollectionData( + parsePageId(collection), + parsePageId(view) + ); + } else if (id) { + if (view) console.warn("Warning: --view will be ignored."); refPage = await nc.getPage(parsePageId(id)); - } - else die('Must specify one of --id or --collection/--view.'); + } else die("Must specify one of --id or --collection/--view."); let json = JSON.stringify(refPage, null, 2); if (out) { - let file = isAbsolute(out) ? out : join(process.cwd(), out); + let file = isAbsolute(out) + ? out + : join(NOTION_BACKUP_DIR ?? process.cwd(), out); mkdirSync(dirname(file), { recursive: true }); writeFileSync(file, json); - } - else process.stdout.write(json); + } else process.stdout.write(json); } run(); diff --git a/notion-backup.js b/notion-backup.js index ad1aacd..643fcd7 100755 --- a/notion-backup.js +++ b/notion-backup.js @@ -1,70 +1,84 @@ #!/usr/bin/env node /* eslint no-await-in-loop: 0 */ -let axios = require('axios') - , extract = require('extract-zip') - , { retry } = require('async') - , { createWriteStream } = require('fs') - , { mkdir, rm, readdir } = require('fs/promises') - , { join } = require('path') - , notionAPI = 'https://www.notion.so/api/v3' - , { NOTION_TOKEN, NOTION_FILE_TOKEN, NOTION_SPACE_ID } = process.env - , client = axios.create({ - baseURL: notionAPI, - headers: { - Cookie: `token_v2=${NOTION_TOKEN}; file_token=${NOTION_FILE_TOKEN}` - }, - }) - , die = (str) => { - console.error(str); - process.exit(1); - } -; - -if (!NOTION_TOKEN || !NOTION_FILE_TOKEN || !NOTION_SPACE_ID) { - die(`Need to have NOTION_TOKEN, NOTION_FILE_TOKEN and NOTION_SPACE_ID defined in the environment. -See https://github.com/darobin/notion-backup/blob/main/README.md for +let axios = require("axios"), + extract = require("extract-zip"), + { retry } = require("async"), + { createWriteStream } = require("fs"), + { mkdir, readdir } = require("fs/promises"), + { join } = require("path"), + notionAPI = "https://www.notion.so/api/v3", + { + NOTION_TOKEN, + NOTION_FILE_TOKEN, + NOTION_SPACE_ID, + NOTION_TIME_ZONE, + NOTION_LOCALE, + NOTION_BACKUP_DIR, + } = process.env, + client = axios.create({ + baseURL: notionAPI, + headers: { + Cookie: `token_v2=${NOTION_TOKEN}; file_token=${NOTION_FILE_TOKEN}`, + }, + }), + die = (str) => { + console.error(str); + process.exit(1); + }; +if ( + !NOTION_TOKEN || + !NOTION_FILE_TOKEN || + !NOTION_SPACE_ID || + !NOTION_TIME_ZONE || + !NOTION_LOCALE +) { + die(`Need to have NOTION_TOKEN, NOTION_FILE_TOKEN, NOTION_SPACE_ID, NOTION_TIME_ZONE and NOTION_LOCALE defined in the environment. +See https://github.com/jhoffi/notion-backup-action/blob/main/README.md for a manual on how to get that information.`); } -async function post (endpoint, data) { +async function post(endpoint, data) { return client.post(endpoint, data); } -async function sleep (seconds) { +async function sleep(seconds) { return new Promise((resolve) => { setTimeout(resolve, seconds * 1000); }); } // formats: markdown, html -async function exportFromNotion (format) { +async function exportFromNotion(format) { try { - let { data: { taskId } } = await post('enqueueTask', { + let { + data: { taskId }, + } = await post("enqueueTask", { task: { - eventName: 'exportSpace', + eventName: "exportSpace", request: { spaceId: NOTION_SPACE_ID, exportOptions: { exportType: format, - timeZone: 'America/New_York', - locale: 'en', + timeZone: NOTION_TIME_ZONE, + locale: NOTION_LOCALE, }, + shouldExportComments: false, }, }, }); console.warn(`Enqueued task ${taskId}`); - let failCount = 0 - , exportURL - ; + let failCount = 0, + exportURL; while (true) { if (failCount >= 5) break; await sleep(10); - let { data: { results: tasks } } = await retry( - { times: 3, interval: 2000 }, - async () => post('getTasks', { taskIds: [taskId] }) + let { + data: { results: tasks }, + } = await retry({ times: 3, interval: 2000 }, async () => + post("getTasks", { taskIds: [taskId] }) ); - let task = tasks.find(t => t.id === taskId); + let task = tasks.find((t) => t.id === taskId); // console.warn(JSON.stringify(task, null, 2)); // DBG if (!task) { failCount++; @@ -73,60 +87,61 @@ async function exportFromNotion (format) { } if (!task.status) { failCount++; - console.warn(`No task status, waiting. Task was:\n${JSON.stringify(task, null, 2)}`); + console.warn( + `No task status, waiting. Task was:\n${JSON.stringify(task, null, 2)}` + ); continue; } - if (task.state === 'in_progress') console.warn(`Pages exported: ${task.status.pagesExported}`); - if (task.state === 'failure') { + if (task.state === "in_progress") + console.warn(`Pages exported: ${task.status.pagesExported}`); + if (task.state === "failure") { failCount++; console.warn(`Task error: ${task.error}`); continue; } - if (task.state === 'success') { + if (task.state === "success") { exportURL = task.status.exportURL; break; } } let res = await client({ - method: 'GET', + method: "GET", url: exportURL, - responseType: 'stream' + responseType: "stream", }); - let stream = res.data.pipe(createWriteStream(join(process.cwd(), `${format}.zip`))); + let stream = res.data.pipe( + createWriteStream( + join(NOTION_BACKUP_DIR ?? process.cwd(), `${format}.zip`) + ) + ); await new Promise((resolve, reject) => { - stream.on('close', resolve); - stream.on('error', reject); + stream.on("close", resolve); + stream.on("error", reject); }); - } - catch (err) { + } catch (err) { die(err); } } -async function run () { - let cwd = process.cwd() - , mdDir = join(cwd, 'markdown') - , mdFile = join(cwd, 'markdown.zip') - , htmlDir = join(cwd, 'html') - , htmlFile = join(cwd, 'html.zip') - ; - await exportFromNotion('markdown'); - await rm(mdDir, { recursive: true, force: true }); +module.exports.run = async function run() { + let cwd = NOTION_BACKUP_DIR ?? process.cwd(), + mdDir = join(cwd, "markdown"), + mdFile = join(cwd, "markdown.zip"), + htmlDir = join(cwd, "html"), + htmlFile = join(cwd, "html.zip"); + await exportFromNotion("markdown"); await mkdir(mdDir, { recursive: true }); await extract(mdFile, { dir: mdDir }); await extractInnerZip(mdDir); - await exportFromNotion('html'); - await rm(htmlDir, { recursive: true, force: true }); + await exportFromNotion("html"); await mkdir(htmlDir, { recursive: true }); await extract(htmlFile, { dir: htmlDir }); await extractInnerZip(htmlDir); -} +}; -async function extractInnerZip (dir) { - let files = (await readdir(dir)).filter(fn => /Part-\d+\.zip$/i.test(fn)); +async function extractInnerZip(dir) { + let files = (await readdir(dir)).filter((fn) => /Part-\d+\.zip$/i.test(fn)); for (let file of files) { await extract(join(dir, file), { dir }); } } - -run(); diff --git a/package-lock.json b/package-lock.json index 909a6e8..5e5e900 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,19 @@ { "name": "notion-backup", - "version": "1.4.0", + "version": "1.4.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "notion-backup", - "version": "1.4.0", + "version": "1.4.1", "license": "MIT", "dependencies": { "async": "^3.2.4", "axios": "^0.21.1", + "cheerio": "^1.0.0-rc.12", "commander": "^7.2.0", + "dotenv": "^16.3.1", "extract-zip": "^2.0.1", "notion-client": "^4.5.2", "notion-utils": "^4.5.2" @@ -365,6 +367,11 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -450,6 +457,42 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -568,6 +611,32 @@ "semver": "bin/semver" } }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", @@ -653,6 +722,68 @@ "node": ">=6.0.0" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -667,6 +798,17 @@ "once": "^1.4.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -1409,6 +1551,24 @@ "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", "dev": true }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, "node_modules/http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", @@ -2063,6 +2223,17 @@ "node": ">=12" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2321,6 +2492,29 @@ "node": ">=0.10.0" } }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dependencies": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", diff --git a/package.json b/package.json index 2341dae..9914d8d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "notion-backup", - "version": "1.4.0", + "version": "1.4.1", "description": "Notion exporter meant to be used as part of a GitHub workflow", "main": "./notion-backup.js", "bin": { @@ -26,7 +26,9 @@ "dependencies": { "async": "^3.2.4", "axios": "^0.21.1", + "cheerio": "^1.0.0-rc.12", "commander": "^7.2.0", + "dotenv": "^16.3.1", "extract-zip": "^2.0.1", "notion-client": "^4.5.2", "notion-utils": "^4.5.2"