├── .gitignore ├── lib ├── sandbox.pdf ├── remote_api.png ├── exporter.js └── confluence-client-promise.js ├── envvar ├── .eslintrc.json ├── cse.js ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /lib/sandbox.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jc1518/Confluence-Space-Exporter/HEAD/lib/sandbox.pdf -------------------------------------------------------------------------------- /lib/remote_api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jc1518/Confluence-Space-Exporter/HEAD/lib/remote_api.png -------------------------------------------------------------------------------- /envvar: -------------------------------------------------------------------------------- 1 | # Environment variables sample 2 | export PROTOCOL="http" 3 | export HOST="localhost" 4 | export PORT="8090" 5 | export USERNAME="admin" 6 | export PASSWORD="admin" 7 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "mocha": true, 6 | "commonjs": true, 7 | "es6": true 8 | }, 9 | "extends": [ 10 | "standard" 11 | ], 12 | "globals": { 13 | "Atomics": "readonly", 14 | "SharedArrayBuffer": "readonly" 15 | }, 16 | "parserOptions": { 17 | "ecmaVersion": 2018 18 | }, 19 | "rules": { 20 | } 21 | } -------------------------------------------------------------------------------- /cse.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * Confluence Space Exporter Starter 4 | */ 5 | const exporter = require('./lib/exporter') 6 | 7 | const argv = require('yargs') 8 | .usage('Usage: $0 -k [key] -t [type]') 9 | .example('$0 -k CAP -t xml', 'Export Confluence space CAP to XML file') 10 | .alias('k', 'key') 11 | .describe('k', 'Confluence space key') 12 | .alias('t', 'type') 13 | .describe('t', 'Export file type: xml, html or pdf') 14 | .demandOption(['k', 't']) 15 | .argv 16 | 17 | exporter(argv.key, argv.type) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "confluence-space-exporter", 3 | "version": "1.0.6", 4 | "description": "A command line for exporting Confluence space", 5 | "main": "cse.js", 6 | "directories": { 7 | "lib": "lib" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/jc1518/Confluence-Space-Exporter.git" 15 | }, 16 | "keywords": [ 17 | "confluence", 18 | "space", 19 | "export" 20 | ], 21 | "author": "Jackie Chen", 22 | "license": "ISC", 23 | "bugs": { 24 | "url": "https://github.com/jc1518/Confluence-Space-Exporter/issues" 25 | }, 26 | "homepage": "https://github.com/jc1518/Confluence-Space-Exporter#readme", 27 | "dependencies": { 28 | "eslint": "^6.5.1", 29 | "fs": "0.0.1-security", 30 | "moment": "^2.24.0", 31 | "path": "^0.12.7", 32 | "promise": "^8.0.3", 33 | "request": "^2.88.0", 34 | "request-promise": "^4.2.4", 35 | "yargs": "^14.2.0" 36 | }, 37 | "bin": { 38 | "confluence-space-exporter": "./cse.js" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Confluence Space Exporter 2 | 3 | ## Overview 4 | 5 | Confluence Space Exporter is a handy command line tool that can export a Confluence space into XML or HTML or PDF file. A good use case is archiving space to an offsite storage. 6 | 7 | XML export includes almost everything in the existing space. To restore the space, you need to import the export file into a compatible Confluence server. This is the recommended export type. 8 | 9 | PDF and HTML export only covers part of the contents in the space, the details can be found in the following table. The pro is obvious that the export file is immediately readble therefore easy for distribution. 10 | 11 | |Content|PDF export|XML export|HTML export| 12 | |-------------|-------------|-------------|-------------| 13 | |Pages|Yes|Yes|Yes| 14 | |Blogs|No|Yes|No| 15 | |Comments|No|Optional|Optional| 16 | |Attachments|Images only|Yes|Yes| 17 | |Unpublished changes|No|Yes|No| 18 | |Page numbers|Optional|N/A|N/A| 19 | 20 | ## Install 21 | 22 | ``` 23 | npm i -g confluence-space-exporter 24 | ``` 25 | 26 | ## Configuration 27 | Enable Remote API (XML-RPC & SOAP) in Confluence server if you have not done it yet. 28 | 29 | ![remote_api](./lib/remote_api.png) 30 | 31 | Setup the Confluence login information environment variables, please refer the [sample](./envvar). 32 | ``` 33 | source envvar 34 | ``` 35 | 36 | ## Usage 37 | 38 | ``` 39 | Usage: confluence-space-exporter -k [key] -t [type] 40 | 41 | Options: 42 | --help Show help [boolean] 43 | --version Show version number [boolean] 44 | -k, --key Confluence space key [required] 45 | -t, --type Export file type: xml, html or pdf [required] 46 | 47 | Examples: 48 | confluence-space-exporter -k CAP -t xml Export Confluence space CAP to XML file 49 | ``` 50 | 51 | ## Example 52 | 53 | ``` 54 | $ confluence-space-exporter -k SAN -t xml 55 | Generating export file for space SAN ... 56 | SAN space archiving file download link: http://localhost:8090/download/temp/Confluence-space-export-052036-20.xml.zip 57 | SAN space download starting time: 2019-10-18 04:20:36 58 | Downloading... 59 | status code is: 200 60 | SAN space export file size: 0.38 MB 61 | 3.98 % has been downloaded for SAN 62 | 20.31 % has been downloaded for SAN 63 | 36.65 % has been downloaded for SAN 64 | 46.86 % has been downloaded for SAN 65 | 61.15 % has been downloaded for SAN 66 | 63.20 % has been downloaded for SAN 67 | 69.32 % has been downloaded for SAN 68 | 71.36 % has been downloaded for SAN 69 | 75.45 % has been downloaded for SAN 70 | 77.49 % has been downloaded for SAN 71 | 79.53 % has been downloaded for SAN 72 | 83.62 % has been downloaded for SAN 73 | 87.70 % has been downloaded for SAN 74 | 89.74 % has been downloaded for SAN 75 | 93.83 % has been downloaded for SAN 76 | 95.87 % has been downloaded for SAN 77 | 97.91 % has been downloaded for SAN 78 | 99.95 % has been downloaded for SAN 79 | 100.00 % has been downloaded for SAN 80 | SAN space download finished! localhost-SAN-sandbox.xml.zip 81 | SAN space download ending time: 2019-10-18 04:20:36 82 | ``` 83 | 84 | ## Sample 85 | 86 | [Sandbox space pdf export](./lib/sandbox.pdf) 87 | 88 | -------------------------------------------------------------------------------- /lib/exporter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Export a Confluence space into XML or HTML or PDF 3 | */ 4 | 5 | 'use strict' 6 | 7 | const ConfluenceClient = require('./confluence-client-promise') 8 | const Promise = require('promise') 9 | const fs = require('fs') 10 | const path = require('path') 11 | const request = require('request') 12 | const moment = require('moment') 13 | 14 | const confluence = new ConfluenceClient(process.env.PROTOCOL, process.env.HOST, process.env.PORT, process.env.USERNAME, process.env.PASSWORD) 15 | 16 | function downloadProgress (key, received, total) { 17 | const percentage = ((received * 100) / total).toFixed(2) 18 | console.log(percentage + " % has been downloaded for " + key) 19 | } 20 | 21 | async function downloadExportFile (key, type, downloadLink) { 22 | return new Promise(async function (resolve, reject) { 23 | const spaceDetail = await confluence.getSpace({ key: key }) 24 | let savePath = path.join(process.env.HOST + '-' + key + '-' + spaceDetail.name.replace(/[^A-Z0-9]/ig, '_') + '.' + type + '.zip') 25 | if (type === 'pdf') { 26 | savePath = path.join(process.env.HOST + '-' + key + '-' + spaceDetail.name.replace(/[^A-Z0-9]/ig, '_') + '.pdf') 27 | } 28 | const writer = fs.createWriteStream(savePath) 29 | let receivedBytes = 0 30 | let totalBytes = 0 31 | const options = { 32 | url: downloadLink, 33 | headers: { 'Content-Type': 'application/json' }, 34 | auth: { 35 | user: process.env.USERNAME, 36 | password: process.env.PASSWORD 37 | } 38 | } 39 | request 40 | .get(options) 41 | .on('error', function (err) { 42 | console.error('Oops, something went wrong. ', err) 43 | reject(err) 44 | }) 45 | .on('response', function (data) { 46 | console.log('status code is:', data.statusCode) 47 | if (data.statusCode !== 200) { 48 | reject('status code is: ' + data.statusCode) 49 | } else { 50 | totalBytes = data.headers[ 'content-length' ] 51 | console.log(key, 'space export file size:', (data.headers[ 'content-length' ] / 1048576).toFixed(2), 'MB') 52 | } 53 | }) 54 | .on('data', function (chunk) { 55 | receivedBytes += chunk.length 56 | downloadProgress(key, receivedBytes, totalBytes) 57 | }) 58 | .on('end', function () { 59 | if ((totalBytes / 1048576).toFixed(2) > 0) { 60 | console.log(key, 'space download finished!', savePath) 61 | console.log(key, 'space download ending time:', moment().format('YYYY-MM-DD hh:mm:ss')) 62 | resolve() 63 | } else { 64 | reject('File size does not look right, is it a empty space?') 65 | } 66 | }) 67 | .pipe(writer) 68 | }) 69 | } 70 | 71 | async function exportSpace (key, type) { 72 | try { 73 | console.log('Generating export file for space', key, '...') 74 | let downloadLink = '' 75 | if (!['xml', 'pdf', 'html'].includes(type.toLowerCase())) { 76 | console.error('Error: The export file type can only be xml, html or pdf.') 77 | process.exit(1) 78 | } 79 | if (type.toLowerCase() === 'xml') { 80 | downloadLink = await confluence.exportSpace2Xml({ key: key }) 81 | downloadLink = JSON.parse(downloadLink) 82 | } 83 | if (type.toLowerCase() === 'html') { 84 | downloadLink = await confluence.exportSpace2Html({ key: key}) 85 | downloadLink = JSON.parse(downloadLink) 86 | } 87 | if (type.toLowerCase() === 'pdf') { 88 | downloadLink = await confluence.exportSpace2Pdf({ key: key }) 89 | } 90 | console.log(key, 'space archiving file download link:', downloadLink) 91 | console.log(key, 'space download starting time:', moment().format('YYYY-MM-DD hh:mm:ss'), '\nDownloading...') 92 | await downloadExportFile(key, type, downloadLink) 93 | } catch (err) { 94 | console.log('Oops, something went wrong\n', err) 95 | process.exit(1) 96 | } 97 | } 98 | 99 | module.exports = exportSpace -------------------------------------------------------------------------------- /lib/confluence-client-promise.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Confluence promise client 3 | */ 4 | 5 | 'use strict' 6 | 7 | const request = require('request-promise') 8 | const Promise = require('promise') 9 | 10 | class ConfluenceClient { 11 | constructor (protocol, host, port, username, password) { 12 | this.protocol = protocol 13 | this.host = host 14 | this.port = port 15 | this.username = username 16 | this.password = password 17 | this.baseurl = `${this.protocol}://${this.host}:${this.port}` 18 | this.headers = { 'Content-Type': 'application/json' } 19 | this.auth = { 20 | user: this.username, 21 | password: this.password 22 | } 23 | } 24 | 25 | // API request generator 26 | async makeRequest (options) { 27 | try { 28 | const response = await request(options) 29 | return response 30 | } catch (err) { 31 | console.log(err.message) 32 | process.exit(1) 33 | } 34 | } 35 | 36 | // Get a space 37 | // Params: 38 | // key - space key 39 | getSpace (params) { 40 | const options = { 41 | url: `${this.baseurl}/rest/api/space/${params.key}`, 42 | method: 'GET', 43 | headers: this.headers, 44 | auth: this.auth, 45 | json: true 46 | } 47 | return this.makeRequest(options) 48 | } 49 | 50 | // Set a space status to archive 51 | // Params: 52 | // key - space key 53 | archiveSpace (params) { 54 | const options = { 55 | url: `${this.baseurl}/rpc/json-rpc/confluenceservice-v2/setSpaceStatus`, 56 | method: 'POST', 57 | headers: this.headers, 58 | auth: this.auth, 59 | body: `[ "${params.key}", "ARCHIVED" ]` 60 | } 61 | return this.makeRequest(options) 62 | } 63 | 64 | // Export a space to XML or HTML file 65 | // Params: 66 | // key - space key 67 | // type - export file type (TYPE_XML or TYPE_HTML) 68 | exportSpace (params) { 69 | const options = { 70 | url: `${this.baseurl}/rpc/json-rpc/confluenceservice-v2/exportSpace`, 71 | method: 'POST', 72 | headers: this.headers, 73 | auth: this.auth, 74 | timeout: 600000, 75 | body: `["${params.key}", "${params.type}", "true"]` 76 | } 77 | return this.makeRequest(options) 78 | } 79 | 80 | // Params: 81 | // key - space key 82 | exportSpace2Xml (params) { 83 | return this.exportSpace({ key: params.key, type: 'TYPE_XML' }) 84 | } 85 | 86 | // Params: 87 | // key - space key 88 | exportSpace2Html (params) { 89 | return this.exportSpace({ key: params.key, type: 'TYPE_HTML' }) 90 | } 91 | 92 | // Retrive token to access plugin 93 | async pluginLogin () { 94 | const loginString = '' + 95 | '' + 96 | '' + 97 | '' + 98 | '' + this.username + '' + 99 | '' + this.password + '' + 100 | '' + 101 | '' + 102 | '' 103 | const options = { 104 | url: `${this.baseurl}/plugins/servlet/soap-axis1/pdfexport`, 105 | method: 'POST', 106 | headers: { 'Content-Type': 'text/html', 'SOAPAction': '' }, 107 | auth: this.auth, 108 | body: loginString 109 | } 110 | const data = await this.makeRequest(options) 111 | const pattern = /xsd:string">(.*)<\/loginReturn/ 112 | const token = data.match(pattern)[1] 113 | return token 114 | } 115 | 116 | // Export a space to PDF file 117 | // Params: 118 | // key - space key 119 | async exportSpace2Pdf (params) { 120 | const token = await this.pluginLogin() 121 | const exportPdfString = '' + 122 | '' + 123 | '' + 124 | '' + 125 | '' + token + '' + 126 | '' + params.key + '' + 127 | '' + 128 | '' + 129 | '' 130 | const options = { 131 | url: `${this.baseurl}/plugins/servlet/soap-axis1/pdfexport`, 132 | method: 'POST', 133 | headers: { 'Content-Type': 'text/html', 'SOAPAction': '' }, 134 | timeout: 600000, 135 | body: exportPdfString 136 | } 137 | const data = await this.makeRequest(options) 138 | const pattern = /xsd:string">(.*)<\/exportSpaceReturn/ 139 | const downloadLink = data.match(pattern)[1] 140 | return downloadLink 141 | } 142 | 143 | } 144 | 145 | module.exports = ConfluenceClient 146 | --------------------------------------------------------------------------------