チュートリアル

Markdownでブログコンテンツが書けるCLIクライアントを作ろう!(その4)

ECHOPFのJavaScript SDKを使ってMarkdown記法で書いたドキュメントをブログにアップロードできるソフトウェアを作ります。JavaScript SDKはNode.jsで利用します。

前回まででECHOPFのブログ記事のCRUD操作を行う一通りの流れができあがりました。今回は前回までのコマンドを使いつつ、メールマガジンを作成する機能を追加します。

メールマガジンベースの作成

まずはメールマガジンのベースになるファイルを生成する機能を作ります。これは次のようなコマンドで生成するものとします。

$ echopf mm

index.js に記述する全体的な流れは以下のようになります。この流れは以前作成したブログ記事のベースを作成するものと同じです。

const MailMagazineNew = require('./libs/mm/new');
program
  .command('mm')
  .description('新しいメールマガジンのベースを作成します')
  .action(() => {
    const mmNew = new MailMagazineNew({
      dir: '.',
    });

    mmNew
      .generate()
      .then(() => {
        console.log(`メールマガジンのベースを生成しました。./${mmNew.mailmag.refid}.md`);
      }, (err) => {
        console.log(err);
      });
  });

MailMagazineNewの作成

MailMagazineNewは上記コードにもある通り、 libs/mm/new.js として作成します。メソッドは二つで、メールテンプレートの生成を行う generate メソッドとファイルとして書き込む saveMailMagazine メソッドになります。

const path = require('path');
const strftime = require('strftime');
const MailMagazineBase = require('./base');

module.exports = (() => {
  class MailMagazineNew extends MailMagazineBase {
    constructor(options) {
      super(options);
      this.dir = path.resolve(options.dir);
    }
    
    // メールテンプレートの生成処理
    generate() {
    }
  }
  return MailMagazineNew;
})();

また、メールマガジン系の共通処理を担う MailMagazineBase クラスを libs/mm/base.js として作成します。

const ECHOPF = require('../../ECHO');
const path = require('path');
const fs = require('fs');
const yaml = require('js-yaml');
const BlogBase = require('../base');
const showdown = require('showdown');

module.exports = (() => {
  class MailMagazineBase extends BlogBase {
    constructor(options) {
      super(options)
    }
  }
  
  // メールマガジンをファイルに書き込む処理
  saveMailMagazine(m) {
  }
}

設定ファイルの修正

今回は新しいメンバーインスタンスを ECHOPF の管理画面上で作成し、そのインスタンスIDを設定ファイルにて mailMagazine.InstanceId として定義しています。また、メールの送信元になるメールアドレスをmailMagazine.FromMailAddressとして定義します。

{
  // 省略
  "mailMagazine": {
    "InstanceId": "mailmagazine",
    "FromMailAddress": "atsushi@moongift.jp"
  }
}

配信先になるメールアドレスをユーザとして登録しておいてください。

もう一つ大事な変更点として、ACL設定の中でメールマガジンに対して操作許可を与えておきます。今回は設定ファイルにあるブログ投稿用アカウントに対して許可を与えています。

メールテンプレートの生成処理

まず MailMagazineNew.generate について説明します。ECHOPF でメールマガジンを扱うオブジェクトは ECHOPF.Members.MailmagObject になります。ここにメールマガジンを受け取る対象になるメンバーインスタンスを指定します。

さらにメールマガジンを配信する(メンバープラグイン) | ECHOPFに沿って必要なパラメータを設定します。

this.mailmag = new this.ECHOPF.Members.MailmagObject(this.config.mailMagazine.InstanceId);
this.mailmag.put('distributed', new Date());
this.mailmag.put('title', '');
this.mailmag.put('text_type', 'html');
this.mailmag.put('from_email', this.config.mailMagazine.FromMailAddress);

最後にファイル書き込みを行う saveMailMagazine を呼び出します。

return this.saveMailMagazine(this.mailmag);

MailMagazineNew.generateは次のようになります。

generate() {
  this.mailmag = new this.ECHOPF.Members.MailmagObject(this.config.mailMagazine.InstanceId);
  this.mailmag.refid = strftime('%Y%m%d%H%M%S');
  this.mailmag.put('distributed', new Date());
  this.mailmag.put('title', '');
  this.mailmag.put('text_type', 'html');
  this.mailmag.put('from_email', this.config.mailMagazine.FromMailAddress);
  return this.saveMailMagazine(this.mailmag);
}

ファイルの書き込み処理

次に MailMagazineBase.saveMailMagazine を紹介します。こちらは ECHOPF.Members.MailmagObject から必要なデータを取り出して、ファイル書き込みを行います。使うのは以下のパラメータです。

  • title
    メールの件名です
  • distributed
    配信日時です
  • from_email
    送信元のメールアドレスです
  • text_type
    HTMLメール(html)かテキストメール(text)か指定。今回のデフォルトはHTMLメールです

これらの情報を取りだして、ファイル書き込みを行います。

saveMailMagazine(m) {
  const me = this;
  const data = ['---'];
  const metas = ['title', 'distributed', 'from_email', 'text_type'];
  for (let key of metas) {
    data.push(`${key}: ${m.get(key)}`);
  }
  data.push('---');
  data.push('');
  data.push('');
  return new Promise((res, rej) => {
    fs.writeFile(`${me.dir}/${m.refid}.md`, data.join('\n'), err => (err ? rej(err) : ''));
    res(m);
  });
}

これでメールマガジンのベースを作成する処理が完成になります。

$ echopf-cli mm
メールマガジンのベースを生成しました。./20180125155637.md

ファイルの内容は次のようになります。

---
title: 
distributed: Thu Jan 25 2018 15:56:37 GMT+0900 (JST)
from_email: from@example.com
text_type: html
---

メール本文はブログ記事同様、Markdownで記述できます。その場合には text_type を html としておく必要があります。テキストメールの場合は text とします。

メールを登録する

メールを書き終わったら、ECHOPF上にアップロードします。この処理は以下のコマンドで行うこととします。

echopf send [filePath]

そこで index.js に次のように内容を追加します。メール送信処理を行うのは MailMagazineSend.send メソッドになります。

const MailMagazineSend = require('./libs/mm/send');
program
  .command('send [filePath]')
  .description('メールマガジンを送信します')
  .action((filePath) => {
  const mmSend = new MailMagazineSend({
    dir: '.',
    filePath
  });

  mmSend
    .send()
    .then(() => {
        console.log(`メールマガジンを配信しました。`);
    }, (err) => {
        console.log('エラーです。', err);
    });
  });

MailMagazineSendはまず次のように記述します。

const path = require('path');
const MailMagazineBase = require('./base');

module.exports = (() => {
  class MailMagazineSend extends MailMagazineBase {
    constructor(options) {
      super(options);
      this.filePath = path.resolve(options.filePath);
      this.dir = path.resolve(options.dir);
    }
    
    // メール配信処理を行います
    send() {
    }
  }
  return MailMagazineSend;
})();

そしてsendメソッドの内容は次のようになります。注意点として、loginメソッドがあります。メールマガジンの登録は権限が必要になりますので、まず最初にログインを行います。その上でMarkdownからメールマガジンオブジェクトに展開するメソッド MarkdownToMailMagazine を実行し、メールマガジンオブジェクトができたら送信メソッド sendExecute を実行します。

send() {
  return new Promise((res, rej) => {
    this.login()
      .then(() => this.MarkdownToMailMagazine(),
        (err => rej(err))
      )
      .then(() => {
        return this.sendExecute();
      }, (err => rej(err)))
      .then(() => res(),
        (err => rej(err))
      );
  });
}

Markdownからメールマガジンオブジェクトへ

この処理は以下の2段階の処理で展開されます。ファイルの読み込み readFile とその内容からメールマガジンオブジェクトを作る generateMailMagazine です。readFile はブログ記事作成時のものがそのまま使えます。

MarkdownToMailMagazine() {
  return new Promise((res, rej) => {
    this.readFile(this.filePath)
      .then(contents => this.generateMailMagazine(contents)
        , (err => rej(err)))
      .then(() => res()
        , (err => rej(err)))
  });
}

generateMailMagazine はブログ記事の時とほとんど変わりませんが、もっと簡略化されています。ヘッダー部(---で囲まれた部分)はYAMLとして読み込み、メールマガジンオブジェクトのputメソッドを使って設定します。ただし配信日時であるdistributedは日付形式でないといけないので、別途入れ直しています。また、今回は配信対象を全ユーザとし、 mm.targetAllMembers() を実行しています。

配信形式がHTMLの場合はメール本文をMarkdownからHTMLへ変換します。テキストの場合はそのまま使います。

generateMailMagazine(contents) {
  return new Promise((res, rej) => {
    const mm = new this.ECHOPF.Members.MailmagObject(this.config.mmInstanceId);
    const parts = contents
      .match(/---\n([\s\S]*)\n---\n([\s\S]*)$/);
    // ヘッダー部をYAMLとして読み込みます
    const headers = yaml.safeLoad(parts[1]);
    for (const key in headers) {
      mm.put(key, headers[key]);
    }
    mm.put('distributed', new Date(headers.distributed));
    mm.targetAllMembers();
    if (mm.get('text_type') === 'html') {
      const converter = new showdown.Converter();
      mm.put('text', converter.makeHtml(parts[2]));
    } else {
      mm.put('text', parts[1]);
    }
    this.mailmag = mm;
    res();
  })
}

後はメール配信を実行するだけです。これはメールマガジンオブジェクトの保存処理になります。

sendExecute() {
  return new Promise((res, rej) => {
    this.mailmag.push()
      .then(() => res(),
        err => {
          rej(err)
        })
  });
}

ここまでできあがったら、実際に保存を行ってみましょう。

echopf send ./20180125103453.md 
メールマガジンを配信しました。

このようにメッセージが流れたら正常に終了しています。ECHOPFの管理画面でもメールマガジンオブジェクトが保存されているのが確認できるはずです。メールは時間になったら自動的に送信されます。


今回まででブログ記事の操作、そしてメールマガジンを登録する流れができあがりました。Markdown記法が使えるので、手軽にブログ記事を書いたり、メンテナンスできるようになるでしょう。メールマガジンもHTMLで作るのは意外と難しいですが、Markdownからであれば簡単ではないでしょうか。

ここまでのコードは echopfcom/Echopf_Blog_CLI at v5 にアップロードしてあります。実装時の参考にしてください。