2年目の営業マンが3ヶ月プログラミングを勉強して、RubyでAmazonのランキングを表示してくれるSlack botを作ってみた

# 自己紹介
普段は渋谷のフィンテックベンチャーでセールスマネージャーをしており、プログラミングとは無縁の仕事をしています。ビジネスサイドが主戦場なので、物を作れる人に対する大きな憧れがあったのと、日々の業務の中でも、自分自身のシステムに対する理解の浅さから、エンジニアに迷惑をかけてしまうことがありました。また、技術的知識不足から、競合とのコンペで負けてしまい、悔しい思いをしたことがあったので、プログラミングの勉強をすることにしました。

# はじめに
2月からRubyの勉強を始め、今回の10連休で簡易的なサービスを作れればと思い、UIの設計が不要なSlack botを作ることにしました。

写真のように、botにメンションを飛ばすと、botが反応し、カテゴリを選ぶと、AmazonのTOP10を表示してくれます。

<img width="438" alt="スクリーンショット 2019-05-06 18.59.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392294/84442e10-8143-9e10-3a56-17529b985f19.png">


詳細のコードは[Github](https://github.com/matsukazu1112/amazon_ranking_slack_bot)にて共有しております。rubyのバージョンは`2.3.0` で作っています。

#開発の大まかな流れ
- 必要なデータをAmazonから取得する
- Slackとの連携
- Slack内でのUIの作り込み

## Amazonから必要なデータをスクレイピング

今回はAmazonが提供しているAPIではなく、スクレイピングでデータを取得しています。

```ruby

require 'mechanize'
```
今回は `mechanize` というgemを使ってスクレイピングを進めていきます。

```ruby

# Amazon.comの対象カテゴリのランキングをスクレイピングで取得する
def get_amazon_ranking(category)
	agent = Mechanize.new
	agent.user_agent_alias = "Windows Mozilla" #書かないとエラーが起こるおまじない的なもの。
	page = agent.get("#{get_amazon_ranking_url(category)}")
```
ここでは、変数categoryのデータを引っ張ってくる指示を飛ばしています。

```ruby

# カテゴリー指定
def get_amazon_ranking_url(category)
	if category.include?('ビジネス・経済')
		url = 'https://www.amazon.co.jp/gp/new-releases/books/466282'
	else
		exit
	end

	return url
end
```

カテゴリー指定は上記の様な形で行い、今後、入力したいカテゴリーを増やす際は、上記にカテゴリー名とURLを入力し、if文を作るだけで追加出来ます。

データをタイトルをkeyに、URLをvalueにしてハッシュへ変換していきます。

```ruby

# XMLを配列にする(Titles)
def convert_array_from_xml_titles(xml_titles)
	titles = []
	xml_titles.each_with_index do |xml_title, i|
		titles <<  "#{i + 1}：#{xml_title.inner_text.gsub(/\r\n|\r|\n|\s|\t/, "")}"
		break 1 if i == 9
	end
	return titles
end
```
```ruby

# XMLを配列にする(URLs)
def convert_array_from_xml_urls(xml_urls)
	urls = []
	xml_urls.each_with_index do |xml_url, i|
		# レビューのURLもClass名が同じなので、取得しない
		# 不要なクエリパラメータを削除する
		url = xml_url.get_attribute('href').match(/dp\/[a-zA-Z0-9]+/).to_s

		# レビューを除いた際に、空白が入ってしまうので削除する
		if url != ''
			urls << 'https://www.amazon.co.jp/' + url
		end
		break 1 if i == 9
	end
	return urls
end
```

それぞれ、`convert_array_from_xml` という形で変数を定義し、配列を用意します。Urlsの部分では、本に対するレビューのURLも同じClass名の中に入っていたため、不要なクエリパラメータを削除するための処理を行っています。

<img width="373" alt="スクリーンショット 2019-05-05 10.30.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392294/55d7b0c6-66f6-7c4c-3707-7345aea35979.png">
(タイトルのURLと本のレビューのURLが `a-link-normal` という同一名義のClassに囲われてる）

タイトル、URLともに、10個取り出せた時点でbreakする設定をしています。ここで回したデータを、TitlesとUrlsに返し、指定したクラスからデータを抜き出します。

```ruby

# 必要な項目だけを抜き出す＆XMLを配列にする
	titles = convert_array_from_xml_titles(page.search('.p13n-sc-line-clamp-2'))
	urls   = convert_array_from_xml_urls(page.search('.a-col-left .a-link-normal'))

	rankings = {}
	titles.zip(urls) do |title, url| 
		rankings[title] = url
	end 

	return rankings
end
```
ここで更にrankingsという連想配列を用意し、[.zipメソッド](https://ref.xaio.jp/ruby/classes/enumerable/zip) を活用して、TitleとURLがSlackに交互に出力される指示を出します。


## Slackとの連携
まず、`slack-ruby-client` というgemをインストールし、ひな形をコピペします。

```ruby
require 'slack-ruby-client'

  Slack.configure do |conf|
    conf.token = 'xoxb-*****************' # トークンは後ほど取得します。
  end

  # RTM Clientのインスタンス生成
  client = Slack::RealTime::Client.new

  # Slackに接続できたときの処理
  client.on :hello do
    puts 'connected!'
    client.message channel: 'your_channel_id', text: 'connected!'
  end

  # ユーザからのメッセージを検知したときの処理
  client.on :message do |data|
    if data['text'].include?('こんにちは')
      client.message channel: data['channel'], text: "Hi!"
    end
    if data['text'].include?('かしこい') || data['text'].include?('えらい')
      client.message channel: data['channel'], text: "Thank you!"
    end
    if data['text'].include?('おやすみ')
      client.message channel: data['channel'], text: "Good night"
    end
  end

  # Bot start
  client.start!
```

次に、ブラウザ上のSlackで `Bots` から、botを作成します。
https://xxxxxxxxxxx.slack.com/apps/search?q=bots

<img width="1240" alt="slack_bot_1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392294/1e2f10c8-ee87-6ee3-da4f-6be95cb3e0da.png">

<img width="1221" alt="slack_bot_2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392294/fada5320-67d4-7369-11c4-713ad8e6f141.png">

```ruby
client.message channel: 'your_channel_id', text: 'connected!'
```
上記の部分を今回表示したい自分のSlackのチャンネルに変える作業を行います。

`xoxb-*****************` の部分に、bot作成後のアクセストークンを貼ると、繋込みがされます。

## Slack内でのUIの作り込み
Slack内での挙動に関しては、`@レイワーくん` とスラックで呼び出すと、「僕は現在のAmazonランキング10選を教えることができるよ！★をつけてカテゴリを選んでください！」→「カテゴリー一覧」という流れで、反応するインターフェイスにしました。カテゴリー名をユーザーが入力したかどうかを検出するため、各カテゴリーの頭に★マークを入れ、★があるかどうかで、カテゴリー名と紐づけたデータを引っ張ってくる処理をif文で書いています。

```ruby

# ユーザからのメッセージを検知したときの処理
client.on :message do |data|
	if data['text'].include?('レイワーくん') || data['text'].include?('<@UJA1HUXEG>')
		client.message channel: data['channel'], 
		# 追加していく！
		text: "僕は現在のAmazonランキング10選を教えることができるよ！\n★をつけてカテゴリを選んでください！\n```★ビジネス・経済 ★コンピュータ・IT ★科学・テクノロジー 　★エンターテイメント ★歴史・地理  ★教育・学参・受験 \n★文学・評論 ★社会・政治　★家電＆カメラ　★ホーム＆キッチン　★ホビー　★パソコン・周辺機器 ★ゲーム　★おもちゃ```"
	end

	# FIXME
	# メッセージの中に★があれば、カテゴリとしてみな
	if data['text'].include?('★')
		rankings = get_amazon_ranking(data['text'])

		rankings.each{|title, url|
			client.message channel: data['channel'], text: "#{title} \n#{url}"
		}
	end
end
```

完成！
<img width="438" alt="スクリーンショット 2019-05-06 18.59.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392294/ca4d98cc-9c49-1ef8-01f3-abce9f79407f.png">

Herokuへのアップロードの過程は[こちら](https://qiita.com/matsukazu1112/items/aaac4dffd158f530d2b5)の記事で紹介しております。

#作り終えての感想
簡単に作れるかと思いきや、XMLの変換や、スクレイピングでノイズを除去する作業で、思った以上に苦戦しました。一方、エラーを自力で解決出来たときの快感や、作ったものを人に見せて褒めてもらえたときの喜びは、これまでにあまり経験したことのない感動でした。未知の世界で自分の非力さを実感しながら格闘出来たことも非常に価値のある経験でした。

制作過程で死にそうな自分を何度も助けてくださった[@IZUMIRU0313](https://twitter.com/IZUMIRU0313)さんには感謝しかないです。早くエンジニアの先輩方とも肩を並べて、共闘できるくらいの戦闘力を身に着けていけるよう精進していきます。

サービスに関して、質問があれば、[@matsukazu1995g1](https://twitter.com/matsukazu1995g1)までご連絡ください！

#参考記事
- [Slack Botの種類と大まかな作り方]
(https://qiita.com/namutaka/items/233a83100c94af033575)
- [SlackでBotに何か喋らせたくて、Rubyでコード書いてたら、いくつもエラーが出たけど、奮闘してたらやっと動いた話。]
(https://qiita.com/nekonekonekoe/items/b39cc763945bdcef35fb)
- [Rubyリファレンス zip (Enumerable)]
(https://ref.xaio.jp/ruby/classes/enumerable/zip)
- [【第一回】超簡単！RubyでSlack Botを作る方法]
(http://studio-andy.hatenablog.com/entry/ruby-bot)
- [RubyでXMLファイルをHashとしてパースする方法の比較]
(https://qiita.com/tamano/items/4a6ea57bdb182a0bccce)