From 5f04ae7cd9f85468423a144fe408b86402d395f6 Mon Sep 17 00:00:00 2001
From: g-div
Date: Thu, 7 Dec 2017 15:25:33 +0100
Subject: [PATCH] feat(importer): add disqus importer
---
README.md | 8 +++
package.json | 4 +-
src/importer.js | 95 ++++++++++++++++++++++++++++++++++
test/disqus.xml | 133 ++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 239 insertions(+), 1 deletion(-)
create mode 100644 src/importer.js
create mode 100644 test/disqus.xml
diff --git a/README.md b/README.md
index bf74f89..cb3b3eb 100644
--- a/README.md
+++ b/README.md
@@ -90,6 +90,14 @@ In order to be able to edit your config file and your SQL database files, you ma
docker run -p 3000:3000 -v $(pwd):/usr/src/app -d gka/schnack
```
+### Import comments from disqus
+
+You can import comments from your [disqus XML export](https://help.disqus.com/customer/portal/articles/472149-comments-export) as following:
+
+```
+npm run import -- disqus.xml
+```
+
### Who is behind Schnack?
Schnack is [yet another](https://github.com/gka/canvid/) happy collaboration between [Webkid](https://webkid.io/) and [Gregor Aisch](https://www.vis4.net).
diff --git a/package.json b/package.json
index b54aae2..41f59d1 100644
--- a/package.json
+++ b/package.json
@@ -57,6 +57,8 @@
"rollup-plugin-node-resolve": "^3.0.0",
"rollup-plugin-string": "^2.0.2",
"rollup-plugin-template": "^1.0.10",
- "rollup-plugin-uglify": "^2.0.1"
+ "rollup-plugin-uglify": "^2.0.1",
+ "to-markdown": "^3.1.0",
+ "xml2js": "^0.4.19"
}
}
diff --git a/src/importer.js b/src/importer.js
new file mode 100644
index 0000000..aa3f434
--- /dev/null
+++ b/src/importer.js
@@ -0,0 +1,95 @@
+const fs = require('fs');
+const path = require('path');
+const sqlite = require('sqlite');
+const queries = require('./db/queries');
+const xml2js = require('xml2js');
+const toMarkdown = require('to-markdown');
+const dbPromise = sqlite.open('./comments.db', { Promise });
+
+const filename = process.argv.slice(2, 3).pop();
+if (!filename) {
+ console.error('Pass the filepath to your XML file as argument');
+ process.exit(1);
+}
+
+function run() {
+ const parser = new xml2js.Parser({explicitArray: false});
+ const xmlFilePath = path.resolve(__dirname, '..', filename);
+ fs.readFile(xmlFilePath, (err, data) =>
+ parser.parseString(data, async function (err, result) {
+ if (err) console.error(`Error parsing the XML file '${xmlFilePath}':`, err);
+ const db = await dbPromise;
+
+ // for each post save it and store the id
+ // for each post that is a reply replace reply_to with the stored id
+ const threads = result.disqus.thread;
+ const posts = result.disqus.post.map(comment => importComment(threads, comment));
+
+ for (post of posts) {
+ const newComment = await saveComment(post);
+ post.new_id = newComment;
+ }
+
+ for (post of posts) {
+ const replies = posts.filter(p => p.comment[3] === post.id); // replies to current post
+ if (replies) {
+ for (reply of replies) {
+ const { id, new_id } = post;
+ const res = await db.run(`UPDATE comment SET reply_to = ? WHERE reply_to = ?`, [new_id, id]);
+ }
+ }
+ }
+ })
+ );
+};
+
+async function saveComment(post) {
+ const db = await dbPromise;
+ const { comment, author } = post;
+
+ if (!author.username) {
+ author.username = 'Anonymous Guest';
+ }
+ const user = [
+ 'disqus',
+ author.username,
+ author.name,
+ author.username,
+ 0
+ ];
+
+ try {
+ await db.run(queries.create_user, user);
+ const newUser = await db.get(queries.find_user, ['disqus', author.username]);
+ if (newUser.id) comment.unshift(newUser.id); // push user_id to the front
+ const res = await db.run(`INSERT INTO comment
+ (user_id, slug, comment, reply_to, created_at, approved, rejected)
+ VALUES (?,?,?,?,?,?,0)`, comment);
+ return res.lastID;
+ } catch (err) {
+ console.error(`Error saving the comment for the slug ${comment[0]}:`, err);
+ }
+};
+
+function importComment(threads, comment) {
+ const { author } = comment;
+ const thread = threads.filter(thread => thread.$['dsq:id'] === comment.thread.$['dsq:id'])[0].id
+ const reply_to = comment.parent ? comment.parent.$['dsq:id'] : null;
+ const message = toMarkdown(comment.message.trim());
+ const timestamp = comment.createdAt;
+ const approved = (comment.isDeleted === "true" || comment.isSpam === "true") ? 0 : 1;
+
+ return {
+ comment: [
+ thread,
+ message,
+ reply_to,
+ timestamp,
+ approved
+ ],
+ id: comment.$['dsq:id'],
+ author
+ };
+};
+
+run();
diff --git a/test/disqus.xml b/test/disqus.xml
new file mode 100644
index 0000000..088f231
--- /dev/null
+++ b/test/disqus.xml
@@ -0,0 +1,133 @@
+
+
+
+ webkid
+ General
+ true
+
+
+ foo
+ webkid
+
+ http://localhost:3000/foo/
+ Welcome
+
+ 2014-09-26T20:11:04Z
+
+ info@webkid.io
+ webkid
+ false
+ webkid
+
+ 127.0.0.1
+ false
+ false
+
+
+ bar
+ webkid
+
+ http://localhost:3000/bar/
+ Bar!
+
+ 2014-09-26T20:11:04Z
+
+ info@webkid.io
+ webkid
+ false
+ webkid
+
+ 127.0.0.1
+ false
+ false
+
+
+
+
+ Hey, wow!
// Amazing post.
Check example.
]]>
+
+ 2014-11-01T15:06:24Z
+ false
+ false
+
+ moriz@webkid.io
+ Moritz
+ false
+ moritz
+
+ 127.0.0.1
+
+
+
+
+
+ Hi Moritz,thanks for reading!
]]>
+
+ 2014-11-01T16:59:29Z
+ false
+ false
+
+ christopher@webkid.io
+ Christopher
+ false
+ christopher
+
+ 127.0.0.1
+
+
+
+
+
+
+ Hi Christopher!Thanks!
]]>
+
+ 2014-11-13T09:03:26Z
+ false
+ false
+
+ moriz@webkid.io
+ Moritz
+ false
+ moritz
+
+ 127.0.0.1
+
+
+
+
+
+
+ Bar is the best.]]>
+
+ 2014-11-01T15:06:24Z
+ false
+ false
+
+ moriz@webkid.io
+ Moritz
+ false
+ moritz
+
+ 127.0.0.1
+
+
+
+
+
+ Nice! :)]]>
+
+ 2014-11-13T10:40:03Z
+ false
+ false
+
+ christopher@webkid.io
+ Christopher
+ false
+ christopher
+
+ 127.0.0.1
+
+
+
+