--- /dev/null
+// Libraries used
+const fs = require('fs');
+const path = require('path');
+const axios = require('axios');
+const mammoth = require('mammoth');
+
+// BookStack API variables
+// Uses values on the environment unless hardcoded
+// To hardcode, add values to the empty strings in the below.
+const bookStackConfig = {
+ base_url: '' || process.env.BS_URL,
+ token_id: '' || process.env.BS_TOKEN_ID,
+ token_secret: '' || process.env.BS_TOKEN_SECRET,
+};
+
+// Script Logic
+////////////////
+
+// Check arguments provided
+if (process.argv.length < 4) {
+ console.error('Both <docx_file> and <book_slug> arguments need to be provided');
+ return;
+}
+
+// Get arguments passed via command
+const [_exec, _script, docxFile, bookSlug] = process.argv;
+
+// Check the docx file exists
+if (!fs.existsSync(docxFile)) {
+ console.error(`Provided docx file "${docxFile}" could not be found`);
+ return;
+}
+
+// Create an axios instance for our API
+const api = axios.create({
+ baseURL: bookStackConfig.base_url.replace(/\/$/, '') + '/api/',
+ timeout: 5000,
+ headers: { 'Authorization' : `Token ${bookStackConfig.token_id}:${bookStackConfig.token_secret}` },
+});
+
+// Wrap the rest of our code in an async function so we can await within.
+(async function() {
+
+ // Fetch the related book to ensure it exists
+ const {data: bookSearch} = await api.get(`/books?filter[slug]=${encodeURIComponent(bookSlug)}`);
+ if (bookSearch.data.length === 0) {
+ console.error(`Book with a slug of "${bookSlug}" could not be found`);
+ return;
+ }
+ const book = bookSearch.data[0];
+
+ // Convert our document
+ const {value: html, messages} = await mammoth.convertToHtml({path: docxFile});
+
+ // Create a name from our document file name
+ let {name} = path.parse(docxFile);
+ name = name.replace(/[-_]/g, ' ');
+
+ // Upload our page
+ const {data: page} = await api.post('/pages', {
+ book_id: book.id,
+ name,
+ html,
+ });
+
+ // Output the results
+ console.info(`File converted and created as a page.`);
+ console.info(` - Page ID: ${page.id}`);
+ console.info(` - Page Name: ${page.name}`);
+ console.info(`====================================`);
+ console.info(`Conversion occurred with ${messages.length} message(s):`);
+ for (const message of messages) {
+ console.warn(`[${message.type}] ${message.message}`);
+ }
+
+})().catch(err => {
+ // Handle API errors
+ if (err.response) {
+ console.error(`Request failed with status ${err.response.status} [${err.response.statusText}]`);
+ return;
+ }
+ // Output all other errors
+ console.error(err)
+});
\ No newline at end of file
--- /dev/null
+{
+ "name": "docx-to-page",
+ "version": "1.0.0",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "docx-to-page",
+ "version": "1.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "axios": "^0.21.1",
+ "mammoth": "^1.4.17"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/axios": {
+ "version": "0.21.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
+ "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
+ "dependencies": {
+ "follow-redirects": "^1.10.0"
+ }
+ },
+ "node_modules/bluebird": {
+ "version": "3.4.7",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz",
+ "integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM="
+ },
+ "node_modules/dingbat-to-unicode": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dingbat-to-unicode/-/dingbat-to-unicode-1.0.1.tgz",
+ "integrity": "sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w=="
+ },
+ "node_modules/duck": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/duck/-/duck-0.1.12.tgz",
+ "integrity": "sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==",
+ "dependencies": {
+ "underscore": "^1.13.1"
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz",
+ "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jszip": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/jszip/-/jszip-2.5.0.tgz",
+ "integrity": "sha1-dET9hVHd8+XacZj+oMkbyDCMwnQ=",
+ "dependencies": {
+ "pako": "~0.2.5"
+ }
+ },
+ "node_modules/lop": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/lop/-/lop-0.4.1.tgz",
+ "integrity": "sha512-9xyho9why2A2tzm5aIcMWKvzqKsnxrf9B5I+8O30olh6lQU8PH978LqZoI4++37RBgS1Em5i54v1TFs/3wnmXQ==",
+ "dependencies": {
+ "duck": "^0.1.12",
+ "option": "~0.2.1",
+ "underscore": "^1.13.1"
+ }
+ },
+ "node_modules/mammoth": {
+ "version": "1.4.17",
+ "resolved": "https://registry.npmjs.org/mammoth/-/mammoth-1.4.17.tgz",
+ "integrity": "sha512-/YTeOtKsrPWwFt6dufmqXyJEq4En1GXloU4cYU7x4nnIa51H6AIRelkSba/m+6Emg7rZwIxGxa1hg7S3dK8M9Q==",
+ "dependencies": {
+ "argparse": "~1.0.3",
+ "bluebird": "~3.4.0",
+ "dingbat-to-unicode": "^1.0.1",
+ "jszip": "~2.5.0",
+ "lop": "^0.4.1",
+ "path-is-absolute": "^1.0.0",
+ "sax": "~1.1.1",
+ "underscore": "^1.13.1",
+ "xmlbuilder": "^10.0.0"
+ },
+ "bin": {
+ "mammoth": "bin/mammoth"
+ }
+ },
+ "node_modules/option": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/option/-/option-0.2.4.tgz",
+ "integrity": "sha1-/Udc35jcq7PLOXo7pShP60Xtv+Q="
+ },
+ "node_modules/pako": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
+ "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU="
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/sax": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.1.6.tgz",
+ "integrity": "sha1-XWFr6KXmB9VOEUr65Vt+ry/MMkA="
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
+ },
+ "node_modules/underscore": {
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz",
+ "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g=="
+ },
+ "node_modules/xmlbuilder": {
+ "version": "10.1.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz",
+ "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==",
+ "engines": {
+ "node": ">=4.0"
+ }
+ }
+ },
+ "dependencies": {
+ "argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "requires": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "axios": {
+ "version": "0.21.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
+ "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
+ "requires": {
+ "follow-redirects": "^1.10.0"
+ }
+ },
+ "bluebird": {
+ "version": "3.4.7",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz",
+ "integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM="
+ },
+ "dingbat-to-unicode": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dingbat-to-unicode/-/dingbat-to-unicode-1.0.1.tgz",
+ "integrity": "sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w=="
+ },
+ "duck": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/duck/-/duck-0.1.12.tgz",
+ "integrity": "sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==",
+ "requires": {
+ "underscore": "^1.13.1"
+ }
+ },
+ "follow-redirects": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz",
+ "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg=="
+ },
+ "jszip": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/jszip/-/jszip-2.5.0.tgz",
+ "integrity": "sha1-dET9hVHd8+XacZj+oMkbyDCMwnQ=",
+ "requires": {
+ "pako": "~0.2.5"
+ }
+ },
+ "lop": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/lop/-/lop-0.4.1.tgz",
+ "integrity": "sha512-9xyho9why2A2tzm5aIcMWKvzqKsnxrf9B5I+8O30olh6lQU8PH978LqZoI4++37RBgS1Em5i54v1TFs/3wnmXQ==",
+ "requires": {
+ "duck": "^0.1.12",
+ "option": "~0.2.1",
+ "underscore": "^1.13.1"
+ }
+ },
+ "mammoth": {
+ "version": "1.4.17",
+ "resolved": "https://registry.npmjs.org/mammoth/-/mammoth-1.4.17.tgz",
+ "integrity": "sha512-/YTeOtKsrPWwFt6dufmqXyJEq4En1GXloU4cYU7x4nnIa51H6AIRelkSba/m+6Emg7rZwIxGxa1hg7S3dK8M9Q==",
+ "requires": {
+ "argparse": "~1.0.3",
+ "bluebird": "~3.4.0",
+ "dingbat-to-unicode": "^1.0.1",
+ "jszip": "~2.5.0",
+ "lop": "^0.4.1",
+ "path-is-absolute": "^1.0.0",
+ "sax": "~1.1.1",
+ "underscore": "^1.13.1",
+ "xmlbuilder": "^10.0.0"
+ }
+ },
+ "option": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/option/-/option-0.2.4.tgz",
+ "integrity": "sha1-/Udc35jcq7PLOXo7pShP60Xtv+Q="
+ },
+ "pako": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
+ "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU="
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+ },
+ "sax": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.1.6.tgz",
+ "integrity": "sha1-XWFr6KXmB9VOEUr65Vt+ry/MMkA="
+ },
+ "sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
+ },
+ "underscore": {
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz",
+ "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g=="
+ },
+ "xmlbuilder": {
+ "version": "10.1.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz",
+ "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg=="
+ }
+ }
+}
--- /dev/null
+# Convert docx to Page
+
+This script will take a docx file,
+attempt to convert it to a BookStack suitable format, then upload
+it into a BookStack book via the API.
+
+This is a simplistic example of a NodeJS script. You will likely want to
+alter and extend this script to suit your use-case.
+
+## Requirements
+
+You will need NodeJS installed (Tested on v14, may work on earlier versions).
+Images can be converted and uploaded via this but this requires Base64 image support
+by BookStack so you'll need to be using BookStack v21.05.1 or greater.
+
+## Running
+
+First, download all the files in the same directory as this readme to a folder on your system
+and run the below from within that directory.
+
+```bash
+# Install NodeJS dependencies via NPM
+npm install
+
+# Setup
+# ALTERNATIVELY: Open the script and add to the empty strings in the variables at the top.
+export BS_URL=https://bookstack.example.com # Set to be your BookStack base URL
+export BS_TOKEN_ID=abc123 # Set to be your API token_id
+export BS_TOKEN_SECRET=123abc # Set to be your API token_secret
+
+# Running the script
+node index.js <docx_file> <book_slug>
+```
+
+- `<docx_file>` - File you want to convert & upload.
+- `<book_slug>` - The unique book identifier shown in the URL bar within BookStack (Autogenerated from the name).
+ - For example:
+ - Book URL: https://example.com/books/bookstack-user-guide
+ - Book Slug: bookstack-user-guide
+
+## Examples
+
+```bash
+# Convert the 'my_content.docx' file and upload to the Book with slug 'bookstack-user-guide'
+node index.js my_content.docx bookstack-user-guide
+```
\ No newline at end of file