RubyでQiitaの投稿をSlackに通知する

##概要
社内メンバーのQiita投稿をSlackに通知する仕組みをRubyとAWS lambdaで実装する機会があったので、その手順をまとめます。

おおまかな仕組みとしては、

1. 会社のQiitaページのフィードを解析し、1時間以内の投稿を取得する
2. Slackに通知する
3. それを1時間ごとに実行する

という形式をとりました。

##環境
Ruby 2.5
slack-notifier 2.3.2

###ディレクトリ構成
```
.
├── Gemfile
├── Gemfile.lock
├── lambda_function.rb
└── vendor
    └── bundle
```

##１時間以内の投稿を取得する
投稿の取得にはフィードを利用します。フィードには記事の著者や投稿日、更新日といった情報が記載されているので、フィードを解析することで、その投稿が1時間以内になされたものかを判定することができます。

###フィードの取得
QiitaではAtomという形式でフィードを配信しており、Organizationのフィードは、OrganizationのURL に`/activities.atom` を加えることで取得できます。

```
http://qiita.com/organizations/<NAME_ORGANIZATION>/activities.atom
```
参考：[Qiita 記事やユーザのフィード URL（フォローしたいユーザーやタグのXML/ATOMのURL）](https://qiita.com/KEINOS/items/f0a5bce2fa9cfec85f8b)

###フィードの解析
フィードの解析にはRubyの標準ライブラリであるrssライブラリが利用できます。
`RSS::Parser.parse`にパースしたいフィードを渡すことで、解析した結果のオブジェクトを返してくれます。

```ruby:
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件ずつ取り出し、投稿日時が１時間以内である記事のURLを全て取得します。

```ruby
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](https://github.com/stevenosloan/slack-notifier)

まずは事前準備として、以下の２つが必要です。

* gem slack-notifierのインストール

```ruby:Gemfile
gem 'slack-notifier'
```

* SlackのWebhook URLを入手  
参考：[SlackのWebhook URL取得手順](https://qiita.com/vmmhypervisor/items/18c99624a84df8b31008)

下記のようにすることで、先ほど取得したQiita記事のリンクをSlackに通知することができます。

```ruby
require 'slack-notifier'

notifier = Slack::Notifier.new('<取得したWebhook URL>')
notifier.ping('<Qiita記事のリンク>', unfurl_links: true)
```
`unfurl_links: true`はリンクのプレビューを表示するための引数です。無くても問題ありません。
参考：[slack-notifierでrailsアプリからslackへ通知](https://qiita.com/michiharujp/items/9302b8075c39780c1418)

##定期実行
ここまでで①会社のQiitaページのフィードを解析し、1時間以内の投稿を取得すること②その投稿のURLをSlackに通知することができました。最後に①と②を1時間ごとに実行できれば、社内メンバーのQiita投稿をSlackに通知する仕組みの完成です。

###AWS lambda
定期実行の方法としては、概要でも述べたようにAWS lambdaを使用しました。AWS lambdaとは、関数を登録しておくことで、クラウド上で自動的にコードを実行してくれるサービスです。~~それ以上の深い説明は私にはできません...~~

####関数の作成
まずは、ローカルでAWS lambdaに登録するSlack通知の関数を作成します。
関数を書くのはAWS lambdaのコンソールで行うこともできますが、Lambdaに標準でインストールされていないライブラリ(今回の場合slack-notifier)を使うには、ローカルでパッケージングしたものをデプロイする必要があります。

私の場合、以下のようなプログラムにしました。

```ruby:lambda_function.rb
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を使ってみる](https://qiita.com/harhogefoo/items/ecac4b0fa9475a8bd393)
[AWS Lambda の Ruby ランタイムを試す](http://blog.akanumahiroaki.com/entry/2018/12/01/170000)

リンク先にも書かれていますが、AWS LambdaのRubyバージョンは2.5です(2019年６月現在)。開発側のRubyのバージョンが2.5でないとデプロイを行ってもrequireするときにエラーになります。

####環境変数の設定
デプロイ後は環境変数の設定をします。
関数のエディタの下に設定できる欄があるので、プログラム中で用いている２つのURLに加え、タイムゾーンをJSTに変更するため、TZの値をAsia/Tokyoに設定します。
参考：[AWS Lambdaのタイムゾーン変更](https://qiita.com/nullian/items/39ecf1f6d0194b72e8e6)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/422411/f8f9622d-6be8-c486-324b-ef38d9fd7f49.png)


####トリガーの設定
トリガーは画像中の左側のリストからCloudWatch Eventsを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/422411/1a558636-98b0-5eb8-838a-c029813ce1a4.png)

すると下側にトリガーの設定が表示されるので、新規ルールの作成を選択し、1時間ごとに実行するよう設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/422411/51a8a2e6-4b9c-d85f-77e3-006c471e6c47.png)
設定が完了したらトリガーを追加し、関数を保存して完成です。

参考
[【AWS】lambdaファンクションを定期的に実行する](https://qiita.com/Toshinori_Hayashi/items/5b0a72dc64ced91717c0)