├── .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 | 
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 |
--------------------------------------------------------------------------------