しょぼいカレンダーとRubyを使って今日放送のアニメをslackに送る

# 完成イメージ
![ezgif.com-gif-maker.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1748419/c856db82-26b8-aec8-384b-85df6710c7c7.gif)


# 完成コード

```slack.rb
require 'slack-ruby-client'

def send_data(info)
  Slack.configure do |config|
    # APIトークンを設定
    config.token = "xoxb-xxxxxxx-xxxxxxxx-xxxxxxxxxxxxx"
  end

  # APIクライアントを生成
  client = Slack::Web::Client.new


  # #チャンネル名 of @ユーザー名
  channel = '投稿先のチャンネル名'

  # メッセージ
  text = info

  # メッセージの送信
  client.chat_postMessage(channel: '#slack_test', text: text, as_user: true)
end
```
```anime.rb
require "rexml/document"
require "open-uri"

def get_programs(span)
  url = "http://cal.syoboi.jp/cal_chk.php?days=#{span}" # span: どれくらい先まで取得するかの期間を日にちで指定
  xml = REXML::Document.new(URI.open(url).read)

  programs = []

  xml.elements.each("syobocal/ProgItems/ProgItem") { |item|
    start_time = Time.parse(item.attribute("StTime").to_s).hour
    night_time = 20
    midnigth_time = 4
    if night_time < start_time || start_time < midnigth_time
      programs << {
        title: item.attribute("Title").to_s,                # タイトル
        sub_title: item.attribute("SubTitle").to_s,         # サブタイトル
        st_time: Time.parse(item.attribute("StTime").to_s), # 開始時刻
        ed_time: Time.parse(item.attribute("EdTime").to_s), # 終了時刻
        ch_name: item.attribute("ChName").to_s,             # チャンネル名
        ch_id: item.attribute("ChID").to_s.to_i             # チャンネルID
      }
    end
  }

  programs.select{ |program|
    # データの取得期間を現在時刻から翌日の朝5時までに絞る
    st = Time.now
    day = Time.now.hour < 5 ? Date.today : Date.today + 1
    ed = Time.new(day.year, day.month, day.day, 5)

    # 自分が住んでいる地域で放送されているチャンネルのIDを選択
    # チャンネルIDの調べ方: http://cal.syoboi.jp/mng?Action=ShowChList
    ch_ids = [
      1,  # NHK総合
      3,  # フジテレビ
      4,  # 日本テレビ
      5,  # TBS
      6,  # テレビ朝日
      7,  # テレビ東京
      19, # TOKYO MX
    ]

    st < program[:st_time] and program[:st_time] < ed and ch_ids.include?(program[:ch_id])
  }.sort_by{ |program| program[:st_time] }
end
```
```main.rb
require "./slack.rb"
require "./anime.rb"


def format_time(time)
  ymd = time.to_s.split(" ")[0]
  hms = time.to_s.split(" ")[1]

  "#{ymd} #{hms}"
end



programs = get_programs(1)
info = ""
programs.each do |program|
info += <<~TEXT
  開始時刻：#{format_time(program[:st_time])}
  チャンネル：#{program[:ch_name]}
  タイトル：#{program[:title]}
  --------------------------------------------------
  TEXT
end

send_data(info)
```


# 実行環境
Ruby : 2.7.3

# 概要手順
1. slackのアプリを作る
1. Rubyで`しょぼいカレンダー`のデータを受信するコードを作成
1. Rubyでslackにメッセージを送る
1. 2で取得したデータをslackに送信するコードを作る

# 詳細手順
## 1. slackアプリを作る
アプリの作り方は[こちら](https://qiita.com/kou_pg_0131/items/56dd81f2f4716ca292ef)のわかりやすい記事をご参照ください。(割愛)
> @kou_pg_0131 さん
>引用元： Slack APIを使用してメッセージを送信する

## 2. Rubyでしょぼいカレンダーのデータを受信する
今回は[こちら](https://qiita.com/kazama1209/items/a5817dee7ffc16327535#%E5%AE%9F%E8%A3%85)の記事を参考にしていきます。
> @kazama1209  さん
>引用元：【Ruby】今夜放送予定のアニメ番組一覧を取得してGoogleカレンダーに書き込む

### 2-1 しょぼいカレンダーから番組を受信する
コードの内容はほぼお借りしてますので違う点はGoogleカレンダーに追加する部分が省かれているくらいです
@kazama1209 さまに感謝です。

```rb
require "rexml/document"
require "open-uri"

# しょぼいカレンダーから番組を取得する
def get_programs(span)
  url = "http://cal.syoboi.jp/cal_chk.php?days=#{span}" # span: どれくらい先まで取得するかの期間を日にちで指定
  xml = REXML::Document.new(open(url).read)

  programs = []

  xml.elements.each("syobocal/ProgItems/ProgItem") { |item|
    programs << {
      title: item.attribute("Title").to_s,                # タイトル
      sub_title: item.attribute("SubTitle").to_s,         # サブタイトル
      st_time: Time.parse(item.attribute("StTime").to_s), # 開始時刻
      ed_time: Time.parse(item.attribute("EdTime").to_s), # 終了時刻
      ch_name: item.attribute("ChName").to_s,             # チャンネル名
      ch_id: item.attribute("ChID").to_s.to_i             # チャンネルID
    }
  }

  programs.select{ |program|
    # データの取得期間を現在時刻から翌日の朝5時までに絞る
    st = Time.now
    day = Time.now.hour < 5 ? Date.today : Date.today + 1
    ed = Time.new(day.year, day.month, day.day, 5)

    # 自分が住んでいる地域で放送されているチャンネルのIDを選択
    # チャンネルIDの調べ方: http://cal.syoboi.jp/mng?Action=ShowChList
    ch_ids = [
      1,  # NHK総合
      2,  # NHK Eテレ
      3,  # フジテレビ
      4,  # 日本テレビ
      5,  # TBS
      6,  # テレビ朝日
      7,  # テレビ東京
      19, # TOKYO MX
    ]

    st < program[:st_time] and program[:st_time] < ed and ch_ids.include?(program[:ch_id])
  }.sort_by{ |program| program[:st_time] }
end

# 日時をISO 8601規格に整形する
def format_time(time)
  ymd = time.to_s.split(" ")[0]
  hms = time.to_s.split(" ")[1]

  "#{ymd}T#{hms}+09:00"
end

# 番組を取得してターミナルに出力する
programs = get_programs(1)
programs.each do |program|
   puts program[:title]
   puts program[:sub_title]
   puts program[:ch_name]
   puts format_time(program[:st_time])
   puts format_time(program[:ed_time])
end
```
最後の`puts`連打は後で改善しますのでとりあえずターミナルで実行すると下記のように出力されると思います
このとき`warning: calling URI.open via Kernel#open is deprecated, call URI.open directly or use URI#open`と出ますが後で対処しますので一旦放置します。

**実行結果のイメージ**

```
無職転生～異世界行ったら本気だす～
無職転生
TOKYO MX
2021-07-12T00:00:00+09:00
2021-07-12T00:30:00+09:00
探偵はもう、死んでいる。
今も、ずっと、憶えてる
TOKYO MX
2021-07-12T00:30:00+09:00
2021-07-12T01:00:00+09:00
指先から本気の熱情2-恋人は消防士-
だから、しっかり涼を充電しとく。
TOKYO MX
2021-07-12T01:00:00+09:00
2021-07-12T01:05:00+09:00
東京リベンジャーズ
Break up
テレビ東京
2021-07-12T01:35:00+09:00
2021-07-12T02:05:00+09:00
不滅のあなたへ
フシの旅路
NHK総合
2021-07-12T01:35:00+09:00
2021-07-12T02:00:00+09:00
闇芝居(第9期)
鼠嫁ぎ
テレビ東京
2021-07-12T03:05:00+09:00
2021-07-12T03:10:00+09:00
```
ちょっと見にくいですが受信はできました！
後で色々と調整するので、まずは次に行きます！

## Rubyからslackにメッセージを送信する
### 3-1 slack-ruby-clientをインストール
下記のコマンドか`Gemfile`を作り追加する

```
$ gem install slack-ruby-client
```
```Gemfile
gem 'slack-ruby-client'
```
### 3-2 slack-ruby-clientの動作確認
**※必須ではないので飛ばしても大丈夫です。**
[公式ドキュメント](https://github.com/slack-ruby/slack-ruby-client#using-the-legacy-api-token)を参考に行います。
**今回はgithubに上げる予定はないので`.env`は使わない方向でやります**

```rb
# ENV['SLACK_API_TOKEN']の部分に自分のトークン(xoxb-~~~~)を入れる
Slack.configure do |config|
  config.token = ENV['SLACK_API_TOKEN']
end

client = Slack::Web::Client.new
client.auth_test
```
上記を記載したファイルを実行
`config.token = xxxx`の部分をミスるとエラーが出ますが成功時は何も出ません。
何も出ないと言う事は疎通可能となります。

### 3-3 Rubyからslackにメッセージを送信する
Rubyからslackにメッセージを送るテストをします

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

Slack.configure do |config|
  # APIトークンを入れる
  config.token = 'xoxb-xxxxxxxxxxxx-xxxxxxxxxxxx-xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
end

# APIクライアント生成
client = Slack::Web::Client.new

# #チャンネル名 か @ユーザー名　今回はチャンネル名
channel = '#投稿するチャンネル名'

# メッセージの送信
client.chat_postMessage(channel: '#general', text: 'Hello World', as_user: true)


# メッセージ部分を動的にする場合
text = '任意のメッセージ'

client.chat_postMessage(channel: '#general', text: text, as_user: true)
```
ファイルを実行するとslackに`Hello World`と表示されているはずです。
これでRubyからslackにメッセージの送信が可能になりました。

## 受信したデータをslackに送信する
大まかな流れとしては
1. まず役割毎にファイルを分割する
2. しょぼいカレンダーがアニメのデータを取得する
3. 取得したデータを`client.chat_postMessage(~~, text: ここに入れたい)`
同じようなコードを繰り返すことになるので3の部分だけの振り返りだけします。
上記を終わらせると最初の完成コードになるので参考にどうぞ。(1部分+αしてるので全く同じではないですが要点は同様です)

### 4-1 slackで送るメッセージを動的にしたい
- 今回の場合はアニメ一覧が日毎に変わるので`client.chat_postMessage(~~, text: アニメ一覧情報)`を入れたい
- アニメ一覧情報の部分を変数で持てれば良い
- アニメ一覧を取得した際の結果を`変数x`に格納する
- その`変数x`を`text: x`部分に持って来れれば実現できる

上記を実装すると完成コードの一部変更になるが

```slack.rb
require 'slack-ruby-client'

def send_data(info)
  Slack.configure do |config|
    # APIトークンを設定
    config.token = "xoxb-xxxxxxx-xxxxxxxx-xxxxxxxxxxxxx"
  end

  # APIクライアントを生成
  client = Slack::Web::Client.new


  # #チャンネル名 of @ユーザー名
  channel = '投稿先のチャンネル名'

  # メッセージ
  text = info # アニメの情報一覧を入れる

  # メッセージの送信
  client.chat_postMessage(channel: '#slack_test', text: text, as_user: true)
end
```
```anime.rb
require "rexml/document"
require "open-uri"

# しょぼいカレンダーから番組を取得する
def get_programs(span)
  url = "http://cal.syoboi.jp/cal_chk.php?days=#{span}" # span: どれくらい先まで取得するかの期間を日にちで指定
  xml = REXML::Document.new(open(url).read)

  programs = []

  xml.elements.each("syobocal/ProgItems/ProgItem") { |item|
    programs << {
      title: item.attribute("Title").to_s,                # タイトル
      sub_title: item.attribute("SubTitle").to_s,         # サブタイトル
      st_time: Time.parse(item.attribute("StTime").to_s), # 開始時刻
      ed_time: Time.parse(item.attribute("EdTime").to_s), # 終了時刻
      ch_name: item.attribute("ChName").to_s,             # チャンネル名
      ch_id: item.attribute("ChID").to_s.to_i             # チャンネルID
    }
  }

  programs.select{ |program|
    # データの取得期間を現在時刻から翌日の朝5時までに絞る
    st = Time.now
    day = Time.now.hour < 5 ? Date.today : Date.today + 1
    ed = Time.new(day.year, day.month, day.day, 5)

    # 自分が住んでいる地域で放送されているチャンネルのIDを選択
    # チャンネルIDの調べ方: http://cal.syoboi.jp/mng?Action=ShowChList
    ch_ids = [
      1,  # NHK総合
      2,  # NHK Eテレ
      3,  # フジテレビ
      4,  # 日本テレビ
      5,  # TBS
      6,  # テレビ朝日
      7,  # テレビ東京
      19, # TOKYO MX
    ]

    st < program[:st_time] and program[:st_time] < ed and ch_ids.include?(program[:ch_id])
  }.sort_by{ |program| program[:st_time] }
end
```
```main.rb
require "./slack.rb"
require "./anime.rb"


def format_time(time)
  ymd = time.to_s.split(" ")[0]
  hms = time.to_s.split(" ")[1]

  "#{ymd} #{hms}+09:00"
end



programs = get_programs(1)
info = ""
programs.each do |program|
info += <<~TEXT
  program[:title]
  program[:sub_title]
  program[:ch_name]
  format_time(program[:st_time])
  format_time(program[:ed_time])
  TEXT
end

send_data(info)
```
これで`main.rb`を実行すると先ほどのターミナルと同様の情報がslackに送信されます！
しかし、これだけでは少し使いづらいので出来ないなりに改善してみます！

# 改良点
- **slackで送られてくるメッセージをもう少し見やすくしたい(時間表示も含めて)**
- **夕方のアニメは見ないので夜のアニメだけに限定したい**
- **実行には影響ないけどターミナルに出るwarningを消したい**

それぞれ対処していきます。
## 1. 送られてくるメッセージを見やすくする
現状のままだとデータが剥き出しで送られてくるので欲しい情報の選定と表示方法を変えようと思います

```main.rb
def format_time(time)
  ymd = time.to_s.split(" ")[0]
  hms = time.to_s.split(" ")[1]

  "#{ymd} #{hms}" # {hms}の後の[ +09:00 ]があると見にくいので削除
end

programs = get_programs(1)
info = ""
programs.each do |program|
# ヒアドキュメント内の情報を自分が欲しいものだけにして区切りの線を引くことで対応しました
info += <<~TEXT
  開始時刻：#{format_time(program[:st_time])}
  チャンネル：#{program[:ch_name]}
  タイトル：#{program[:title]}
  --------------------------------------------------
  TEXT
end
```

## 2. 夜のアニメだけに限定する
僕は朝や夕方のアニメは見ないので夜の一定の時間のものだけに限定しました。
あと初期の段階ではNHK総合も含まれるので不要なデータが増えるので調整します

```anime.rb
require "rexml/document"
require "open-uri"

def get_programs(span)
  url = "http://cal.syoboi.jp/cal_chk.php?days=#{span}" # span: どれくらい先まで取得するかの期間を日にちで指定
  xml = REXML::Document.new(URI.open(url).read)

  programs = []

  xml.elements.each("syobocal/ProgItems/ProgItem") { |item|
    start_time = Time.parse(item.attribute("StTime").to_s).hour # 放送開始時刻
    night_time = 20　 # 20時
    midnigth_time = 4　 # 深夜4時
 
    # 20時以降に放送か深夜4時以内に開始しているアニメに限定
    if night_time < start_time || start_time < midnigth_time
      programs << {
        title: item.attribute("Title").to_s,                # タイトル
        sub_title: item.attribute("SubTitle").to_s,         # サブタイトル
        st_time: Time.parse(item.attribute("StTime").to_s), # 開始時刻
        ed_time: Time.parse(item.attribute("EdTime").to_s), # 終了時刻
        ch_name: item.attribute("ChName").to_s,             # チャンネル名
        ch_id: item.attribute("ChID").to_s.to_i             # チャンネルID
      }
    end
  }

  programs.select{ |program|
    # データの取得期間を現在時刻から翌日の朝5時までに絞る
    st = Time.now
    day = Time.now.hour < 5 ? Date.today : Date.today + 1
    ed = Time.new(day.year, day.month, day.day, 5)

    # 自分が住んでいる地域で放送されているチャンネルのIDを選択
    # チャンネルIDの調べ方: http://cal.syoboi.jp/mng?Action=ShowChList
    ch_ids = [
      1,  # NHK総合
　　　　　　　　　　# 2,   NHK Eテレを削除
      3,  # フジテレビ
      4,  # 日本テレビ
      5,  # TBS
      6,  # テレビ朝日
      7,  # テレビ東京
      19, # TOKYO MX
    ]

    st < program[:st_time] and program[:st_time] < ed and ch_ids.include?(program[:ch_id])
  }.sort_by{ |program| program[:st_time] }
end
```
## 3. ターミナルに出るwarningを消す
Ruby 2.7以降にKernelモジュールのopenメソッドでURLを開くと警告が出るそうです。
詳しくは[こちら](https://qiita.com/jnchito/items/aa4ebf6f0e363a1b8098#kernel%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%81%AEopen%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%A7url%E3%82%92%E9%96%8B%E3%81%8F%E3%81%A8%E8%AD%A6%E5%91%8A%E3%81%8C%E5%87%BA%E3%82%8B%E7%AC%AC12%E7%AB%A0%E3%81%AE%E3%82%B3%E3%83%A9%E3%83%A0)をどうぞ

```anime.rb
def get_programs(span)
  url = "http://cal.syoboi.jp/cal_chk.php?days=#{span}" # span: どれくらい先まで取得するかの期間を日にちで指定
# xml = REXML::Document.new(open(url).read) 下記のように変更する
  xml = REXML::Document.new(URI.open(url).read)

  programs = []
  .... 省略
end
```

# まとめ
これで冒頭に載せたイメージのものができると思います。
自分で初めてAPIを触って自分好みに欲しい情報や機能を付けられるのは頭が一筋縄ではいきませんが面白かったです！
今回は自分で記事を書く事への抵抗を無くす練習でもあるので、メソッドやクラスの命名方法など荒いと思いますがご容赦ください。

# 参考
- [【Ruby】今夜放送予定のアニメ番組一覧を取得してGoogleカレンダーに書き込む](https://qiita.com/kazama1209/items/a5817dee7ffc16327535)
- [slack-ruby-client](https://github.com/slack-ruby/slack-ruby-client#using-the-legacy-api-token)
- [Ruby 2.7で発生する「プロを目指す人のためのRuby入門」との差異について](https://qiita.com/jnchito/items/aa4ebf6f0e363a1b8098#kernel%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%81%AEopen%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%A7url%E3%82%92%E9%96%8B%E3%81%8F%E3%81%A8%E8%AD%A6%E5%91%8A%E3%81%8C%E5%87%BA%E3%82%8B%E7%AC%AC12%E7%AB%A0%E3%81%AE%E3%82%B3%E3%83%A9%E3%83%A0 )