LoginSignup
2
2

More than 3 years have passed since last update.

GROWI の特定のページを定期的にメール送信する (AWS Serverless Framework)

Last updated at Posted at 2019-02-17

※2019年5月19日追記: プログラムをリファクタリング&特定階層下の複数記事送信に対応したコードを追加

オープンソースの Wiki システム GROWI の API を利用して、特定のページを定期的にメール送信してみます。

環境

また、AWSの下記サービスを利用します

  • AWS Lambda: API取得とメール送信プログラムを実行します
  • Amazon SES: メールを送信します
  • Amazon CloudWatch: 今回は、Lambdaを定期的に実行するために利用します

GROWI の準備

  • 「ユーザー設定」→「API設定」→「現在のAPI Token」の値をメモしておく
  • 「管理」→「セキュリティ設定」→「ゲストユーザーのアクセス」を「閲覧のみ許可」にしておく

GROWI には現在APIのドキュメントは無いようですが、どのような物があるかはルーティングファイルから推測できます。今回用いる記事単体のAPIは /_api/pages.get?access_token=(アクセストークン)&path=(パス) というURLになります。実際にアクセスしてみると、ページの情報がJSONで取得できることを確認しておきます。GETなのでブラウザで直接叩くだけで良いです。

AWS SES の利用準備

E メールアドレスの検証 - Amazon Simple Email Service に沿って、メールアドレスを認証しておきます。

Serverless Framework の利用準備

Serverless Framework をインストールしておきます。

$ npm install --global serverless

デプロイするために、 Serverless Framework - AWS Lambda Guide - Credentials に沿って認証情報を設定しておきます。必要に応じて awslabs/git-secrets もインストールしておきましょう。

Serverlessプロジェクトの作成

準備ができたら、 Serverless のコードを書いていきます。

$ serverless create --template aws-nodejs-typescript --path growi-to-mail
$ cd growi-to-mail
$ npm install --save-dev
$ npm install --save aws-sdk

Git 管理する場合は、お好みで下記も実行しておきます。

$ git init
$ curl https://raw.githubusercontent.com/github/gitignore/master/Node.gitignore > .gitignore # お好みで
$ echo 'package-lock.json binary' >> .gitattributes # お好みで
$ git remote  add origin https://github.com/USERNAME/REPOSITORY.git # GitHubに上げるなら

デプロイの設定

serverless.yml にデプロイ用の設定を記述していきます。

serverless.yml
service:
  name: growi-to-mail

plugins:
  - serverless-webpack

provider:
  name: aws
  runtime: nodejs8.10
  region: us-west-2
  iamRoleStatements:
    - Effect: 'Allow'
      Action:
        - ses:SendEmail
      Resource: '*'

functions:
  main:
    handler: handler.main
    events:
      - schedule: cron(0/3 * * * ? *)

schedule の設定にはcron記法が利用できます。詳細は Serverless Framework のドキュメントAmazon CloudWatch のドキュメントを参照してください。

特定の単一ページをメール送信するプログラム

下記のようなプログラムで、GROWIの特定ページをメール送信できます。

handler.ts
import * as AWS from 'aws-sdk';
import * as https from 'https';

const aws_region = 'us-west-2';
const mail_to = ['受信用E-mailアドレス'];
const mail_from = '送信用E-mailアドレス';
const growi_url = 'GROWIのURL 例: https://demo.growi.org/';
const growi_access_token = 'メモしたアクセストークン';
const growi_pages_path = '送信したいパス';

export const main = async (event, context) => {
  const ses = new AWS.SES({region: aws_region});
  const page = await getGrowiPage(growi_pages_path);

  return await ses.sendEmail({
    Source: mailFrom,
    Destination: { ToAddresses: mailTo },
    Message: {
      Subject: { Data: page['page']['path'] },
      Body: { Text: { Data: page['page']['revision']['body'] } }
    }
  }).promise();
}

function getGrowiPage(growi_pages_path: string) {
  return new Promise(function(resolve, reject) {
    const url = `${growi_url}_api/pages.get?access_token=${growi_access_token}&path=${growi_pages_path}`;

    https.get(url, (resp) => {
      let data = '';
      resp.on('data', (chunk) => { data += chunk; });
      resp.on('end', () => {
        resolve(JSON.parse(data));
      });
    });
  });
}

デプロイ

$ serverless deploy

これで、上記設定なら3分に1度メールが飛んでくると思います。

また、デプロイしたlambdaは下記コマンドで即時実行できます。

$ serverless invoke --function main

片付け

下記コマンドで、デプロイしたlambda等を削除できます。

$ serverless remove

特定階層以下にあるすべてのページをメール送信するプログラム

/_api/pages.list APIを使うことで、指定した階層以下にあるすべてのページのパスが取得できます。取得したパスを/_api/pages.getで叩いていくことで、特定階層以下にあるすべてのページのメール送信が実現できます。

index.ts
import * as AWS from 'aws-sdk';
import * as https from 'https';

const aws_region = 'us-west-2';
const mail_to = ['受信用E-mailアドレス'];
const mail_from = '送信用E-mailアドレス';
const growi_url = 'GROWIのURL 例: https://demo.growi.org/';
const growi_access_token = 'メモしたアクセストークン';
const growi_pages_path = '送信したいパス';

export const main = async (event, context) => {
  const ses = new AWS.SES({region: aws_region});
  const pageList = await getGrowiList(growi_pages_path);

  let contents = [];
  for (const pagePath of pageList['pages']) {
    // TODO: 環境のキャパシティに応じて調整してください
    const page = await getGrowiPage(pagePath['path']);
    contents.push(page['page']['revision']);
  }

  const title = `${growi_pages_path}の一覧`;
  const body = contents.reduce((accumulator, revision) => {
    const time = new Date(revision['createdAt']);
    // TODO: 記事のフォーマットは適当に変更してください
    const article = `
--------------------------------
${revision['path']}
--------------------------------
${time.getFullYear()}${time.getMonth()}${time.getDate()}${revision['author']['name']}

${revision['body']}

url: ${growi_url}${revision['path']}
`;
    return `${accumulator}${article}`;
  }, `${title}
================================================================
`);

  return await ses.sendEmail({
    Source: mailFrom,
    Destination: { ToAddresses: mailTo },
    Message: {
      Subject: { Data: title },
      Body: { Text: { Data: body } }
    }
  }).promise();
}

function getGrowiAPI(api: ('pages.get' | 'pages.list'), growi_pages_path) {
  return new Promise(function(resolve, reject) {
    const url = `${growi_url}_api/${api}?access_token=${growi_access_token}&path=${growi_pages_path}`;

    http.get(url, (resp) => {
      let data = '';
      resp.on('data', (chunk) => { data += chunk; });
      resp.on('end', () => {
        resolve(JSON.parse(data));
      });
    });
  });
}

function getGrowiPage(growi_pages_path: string) {
  return getGrowiAPI('pages.get', growi_pages_path);
}
function getGrowiList(growi_pages_path: string) {
  return getGrowiAPI('pages.list', growi_pages_path);
}

APIを多く叩くことになるので、環境に応じて負荷を下げるためにsleepを入れたり、逆にスペックに余裕がある環境なら並列実行したりして調整してください。

また記事の量が多い場合、AWS Lambdaのタイムアウト(現在は15分)に引っかかる可能性があります。その場合は最終的に、APIで取得した結果をS3に配置していくLambdaと、完成したS3からメールを送信するLambdaに分ける、といった事が必要になるのではないかと思います。

参考

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2