저는 블로그 도구로 테터툴즈를 사용해왔습니다. 텍스트큐브로 업데이트하지 않고, 그냥 방치해둔 것이 꽤 오래된 것 같습니다.

이번에 블로그를 Jekyll로 옮기면서 댓글 시스템은 Disqus를 사용하기로 했습니다.

Disqus는 댓글을 WordPress eXtended RSS(WXR) 형식으로 만들면 데이터를 임포트할 수 있습니다(Disqus -> Admin -> Discussions -> Import -> Generic (WXR)).

WXR 파일은 확장자만 wxr일 뿐 내용은 XML입니다.

댓글 데이터를 Disqus로 이전하기 위해 Node.js로 테터툴즈 백업 XML 파일을 WXR 형식으로 변환하는 코드를 만들어봤습니다.

app.js

var builder = require('xmlbuilder')
  , fs = require('fs')
  , libxmljs = require('libxmljs')
  , moment = require('moment');

var blogDomain = 'example.com';

var xmlDoc = libxmljs.parseXml(fs.readFileSync('Tattertools.xml', 'utf8'));
var posts = [];

xmlDoc.root().childNodes().forEach(function (e) {
  if (e.name() == 'post') {
    var slogan = e.attr('slogan').value();
    var post = { url: '/blog/entry/' + slogan, title: e.get('title').text(), comments: [] };
    
    e.childNodes().forEach(function (e) {
      if (e.name() == 'comment') {
        var subComments = [];
        
        e.childNodes().forEach(function (e) {
          if (e.name() == 'comment') {
            subComments.push({
              name: e.get('commenter').get('name').text(),
              content: e.get('content').text(),
              date: moment.unix(e.get('written').text()).format('YYYY-MM-DD HH:mm:ss')
            });
          }
        })
        
        post.comments.push({
          name: e.get('commenter').get('name').text(),
          content: e.get('content').text(),
          date: moment.unix(e.get('written').text()).format('YYYY-MM-DD HH:mm:ss'),
          subComments: subComments
        });
      }
    });
    
    posts.push(post);
  }
});

var wxr = builder.create('rss', {'version': '1.0', 'encoding': 'UTF-8'})
  .att('version', '2.0')
  .att('xmlns:content', 'http://purl.org/rss/1.0/modules/content/')
  .att('xmlns:dsq', 'http://www.disqus.com/')
  .att('xmlns:dc', 'http://purl.org/dc/elements/1.1/')
  .att('xmlns:wp', 'http://wordpress.org/export/1.0/');

var channel = wxr.ele('channel');

posts.forEach(function (e) {
  if (e.comments.length > 0) {
    var item = channel.ele('item');
    item.ele('link', 'http://' + blogDomain + e.url);
    item.ele('title', e.title);
    item.ele('dsq:thread_identifier', e.url);
    item.ele('wp:comment_status', 'open');

    var id = 1;
    e.comments.forEach(function (e) {
      var parentId;

      var comment = item.ele('wp:comment');
      comment.ele('wp:comment_id', id);
      comment.ele('wp:comment_author', e.name);

      comment.ele('wp:comment_date_gmt', e.date);
      comment.ele('wp:comment_content').dat(e.content);
      comment.ele('wp:comment_approved', 1);
      comment.ele('wp:comment_parent', 0);

      var parentId = id;
      id++;
      if (e.subComments) {
        e.subComments.forEach(function (e) {
          var comment = item.ele('wp:comment');
          comment.ele('wp:comment_id', id);
          comment.ele('wp:comment_author', e.name);

          comment.ele('wp:comment_date_gmt', e.date);
          comment.ele('wp:comment_content').dat(e.content);
          comment.ele('wp:comment_approved', 1);
          comment.ele('wp:comment_parent', parentId);
          id++;
        });
      }
    });
  }
});

var text = wxr.end({ pretty: true });
console.log(text);

fs.writeFileSync('comments.wxr', text);

Node.js에는 편리한 모듈이 정말 많아서 간편하게 코드를 만들 수 있었습니다. XML을 읽을 때에는 libxmljs, XML을 생성할 때에는 xmlbuilder를 사용했습니다.

아래는 변환된 예제 WXR 파일입니다.

comments.wxr

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
  xmlns:content="http://purl.org/rss/1.0/modules/content/"
  xmlns:dsq="http://www.disqus.com/"
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xmlns:wp="http://wordpress.org/export/1.0/">
  <channel>
    <item>
      <link>http://example.com/blog/entry/hello</link>
      <title>Hello</title>
      <dsq:thread_identifier>/blog/entry/hello</dsq:thread_identifier>
      <wp:comment_status>open</wp:comment_status>
      <wp:comment>
        <wp:comment_id>1</wp:comment_id>
        <wp:comment_author>foo</wp:comment_author>
        <wp:comment_author_email></wp:comment_author_email>
        <wp:comment_date_gmt>2010-09-20 13:19:10</wp:comment_date_gmt>
        <wp:comment_content><![CDATA[Hello]]></wp:comment_content>
        <wp:comment_approved>1</wp:comment_approved>
        <wp:comment_parent>0</wp:comment_parent>
      </wp:comment>
      <wp:comment>
        <wp:comment_id>2</wp:comment_id>
        <wp:comment_author>bar</wp:comment_author>
        <wp:comment_author_email></wp:comment_author_email>
        <wp:comment_date_gmt>2010-09-21 22:32:05</wp:comment_date_gmt>
        <wp:comment_content><![CDATA[Hello foo]]></wp:comment_content>
        <wp:comment_approved>1</wp:comment_approved>
        <wp:comment_parent>1</wp:comment_parent>
      </wp:comment>
    </item>
  </channel>
</rss>

여기서 가장 중요한 부분은 <wp:comment_id>입니다. 1부터 시작하며 절대 중복되면 안됩니다.

<wp:comment_date_gmt>는 GMT로 넣어줘야 합니다. KST(한국 표준시)로 넣으면 안됩니다. 날짜 형식은 2010-09-20 13:19:10처럼 YYYY-MM-DD HH:mm:ss 형식이고, moment 모듈로 손쉽게 변환할 수 있습니다.

<wp:comment_content>는 댓글 내용인데 댓글에 개행이나 특수문자가 들어갈 수 있으므로 <![CDATA[ ]]>로 만들어줍니다.

테터툴즈에서는 댓글에 1단계 하위 댓글이 가능합니다. 이 부분은 <wp:comment_parent>로 표현해주면 됩니다.

Disqus에서 임포트 할 때 주의할 점은, 한번 임포트 한 댓글은 완전히 삭제할 수 없고 계속 남아있다는 것입니다. 즉 <wp:comment_id>에 설정한 댓글 ID를 계속 점유하고 있습니다. 문법이 맞지 않거나 기타 이유로 임포트에 실패한 뒤 다시 임포트를 시도할 경우 댓글 ID가 중복되면 댓글이 생성되지 않습니다.

실패할 때마다 일일이 댓글 ID를 증가시켜주려면 상당히 귀찮으므로 Disqus에서 테스트용 사이트를 생성하여 사용하면 편리합니다. 임포트에 실패하면 테스트 사이트를 삭제한 뒤 다시 생성하여 시도해봅니다. 에러 없이 완벽하게 임포트가 되면 그때 실제 사이트에 임포트합니다.


저작권 안내

이 웹사이트에 게시된 모든 글의 무단 복제 및 도용을 금지합니다.
  • 블로그, 게시판 등에 퍼가는 것을 금지합니다.
  • 비공개 포스트에 퍼가는 것을 금지합니다.
  • 글 내용, 그림을 발췌 및 요약하는 것을 금지합니다.
  • 링크 및 SNS 공유는 허용합니다.

Published

14 July 2014