##概要
社内メンバーのQiita投稿をSlackに通知する仕組みをRubyとAWS lambdaで実装する機会があったので、その手順をまとめます。
おおまかな仕組みとしては、
- 会社のQiitaページのフィードを解析し、1時間以内の投稿を取得する
- Slackに通知する
- それを1時間ごとに実行する
という形式をとりました。
##環境
Ruby 2.5
slack-notifier 2.3.2
###ディレクトリ構成
.
├── Gemfile
├── Gemfile.lock
├── lambda_function.rb
└── vendor
└── bundle
##1時間以内の投稿を取得する
投稿の取得にはフィードを利用します。フィードには記事の著者や投稿日、更新日といった情報が記載されているので、フィードを解析することで、その投稿が1時間以内になされたものかを判定することができます。
###フィードの取得
QiitaではAtomという形式でフィードを配信しており、Organizationのフィードは、OrganizationのURL に/activities.atom
を加えることで取得できます。
http://qiita.com/organizations/<NAME_ORGANIZATION>/activities.atom
参考:Qiita 記事やユーザのフィード URL(フォローしたいユーザーやタグのXML/ATOMのURL)
###フィードの解析
フィードの解析にはRubyの標準ライブラリであるrssライブラリが利用できます。
RSS::Parser.parse
にパースしたいフィードを渡すことで、解析した結果のオブジェクトを返してくれます。
require 'rss'
atom_organization = "http://qiita.com/organizations/<NAME_ORGANIZATION>/activities.atom"
atom = RSS::Parser.parse(atom_organization, false)
※parse
メソッドの第二引数にfalseを指定していますが、これはパースする際にバリデーションを行わないことを意味します。現状Qiitaが提供しているAtomはAtomの構文に完全に即していないのかバリデーション付きでパースを行うとRSS::MissingAttributeError
が発生します。
entries
にフィードの全ての投稿が入るので、eachで1件ずつ取り出し、投稿日時が1時間以内である記事のURLを全て取得します。
require 'rss'
require 'time'
links = []
current_time = Time.now
atom.entries.each do |entry|
published_time = entry.published.to_s.gsub(/<.+>(.+)Z<.+>/, '\1')
published_time = Time.parse(published_time)
links.push(entry.link.href) if published_time >= current_time - 3600
end
entry.published.content
としてやれば、gsubやTime.parseを使わなくても直で日時を取得できるのですが、タイムゾーンがUTCになります。そのため、正しい時刻から(日本の場合)9時間ずれることになるので、私は上記のよう方法をとりました。
(ここら辺に関してはもっとスマートな方法がありそうです…)
##Slackに通知
slack-notifierというgemを使うことで、簡単にSlackへ通知をすることができます。
https://github.com/stevenosloan/slack-notifier
まずは事前準備として、以下の2つが必要です。
- gem slack-notifierのインストール
gem 'slack-notifier'
- SlackのWebhook URLを入手
参考:SlackのWebhook URL取得手順
下記のようにすることで、先ほど取得したQiita記事のリンクをSlackに通知することができます。
require 'slack-notifier'
notifier = Slack::Notifier.new('<取得したWebhook URL>')
notifier.ping('<Qiita記事のリンク>', unfurl_links: true)
unfurl_links: true
はリンクのプレビューを表示するための引数です。無くても問題ありません。
参考:slack-notifierでrailsアプリからslackへ通知
##定期実行
ここまでで①会社のQiitaページのフィードを解析し、1時間以内の投稿を取得すること②その投稿のURLをSlackに通知することができました。最後に①と②を1時間ごとに実行できれば、社内メンバーのQiita投稿をSlackに通知する仕組みの完成です。
###AWS lambda
定期実行の方法としては、概要でも述べたようにAWS lambdaを使用しました。AWS lambdaとは、関数を登録しておくことで、クラウド上で自動的にコードを実行してくれるサービスです。それ以上の深い説明は私にはできません...
####関数の作成
まずは、ローカルでAWS lambdaに登録するSlack通知の関数を作成します。
関数を書くのはAWS lambdaのコンソールで行うこともできますが、Lambdaに標準でインストールされていないライブラリ(今回の場合slack-notifier)を使うには、ローカルでパッケージングしたものをデプロイする必要があります。
私の場合、以下のようなプログラムにしました。
require 'slack-notifier'
require 'rss'
require 'time'
def get_recent_links(atom, current_time)
links = []
atom.entries.each do |entry|
published_time = entry.published.to_s.gsub(/<.+>(.+)Z<.+>/, '\1')
published_time = Time.parse(published_time)
links.push(entry.link.href) if published_time >= current_time - 3600
end
return links
end
def slack_notifier(message)
#環境変数SLACK_WEBHOOK_URLはlambdaのコンソールで設定します
notifier = Slack::Notifier.new(ENV['SLACK_WEBHOOK_URL'])
notifier.ping(message, unfurl_links: true)
end
def lambda_handler(event:, context:)
#同じくTARGET_ATOM_URLはlambdaのコンソールで設定します
atom_organization = ENV['TARGET_ATOM_URL']
atom = RSS::Parser.parse(atom_organization, false)
current_time = Time.now
qiita_links = get_recent_links(atom, current_time)
return if qiita_links.empty?
message = <<~EOS
[Qiita新着投稿]
Qiitaに新着投稿がありました。
#{qiita_links.join("\n")}
EOS
slack_notifier(message)
end
####デプロイ
デプロイ方法については以下の2つが参考になりました。私と同じくAWSに慣れていない方は、zipに圧縮してコンソールからアップロードするというやり方が一番わかりやすいかと思います(以下はこの方法をとった前提で進めます)。
Ruby support for AWS Lambdaを使ってみる
AWS Lambda の Ruby ランタイムを試す
リンク先にも書かれていますが、AWS LambdaのRubyバージョンは2.5です(2019年6月現在)。開発側のRubyのバージョンが2.5でないとデプロイを行ってもrequireするときにエラーになります。
####環境変数の設定
デプロイ後は環境変数の設定をします。
関数のエディタの下に設定できる欄があるので、プログラム中で用いている2つのURLに加え、タイムゾーンをJSTに変更するため、TZの値をAsia/Tokyoに設定します。
参考:AWS Lambdaのタイムゾーン変更
####トリガーの設定
トリガーは画像中の左側のリストからCloudWatch Eventsを選択します。
すると下側にトリガーの設定が表示されるので、新規ルールの作成を選択し、1時間ごとに実行するよう設定します。
設定が完了したらトリガーを追加し、関数を保存して完成です。