苫小牧高専の授業変更情報を普段使いのカレンダーに取り込む

※この記事は[グロービス Advent Calendar 2017](https://qiita.com/advent-calendar/2017/globis) 17日目の記事です。

## はじめに

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">愛校精神が強すぎて会社のアドベントカレンダーに学校のこと書いてる</p>&mdash; mktakuya (@mktakuya) <a href="https://twitter.com/mktakuya/status/942404747121770497?ref_src=twsrc%5Etfw">2017年12月17日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

自分は北海道の苫小牧高専（以下、本校とする）の学生をしながら、東京都 麹町にある株式会社グロービスのエンジニアチームにてリモートインターンとしてコードを書いたり、SlackのPostに意味のないEmoji Reactionをつけてまわるなどの業務に従事している。インターンとしての業務は、ある程度時間を決めて学校の研究室や近所のカフェ、自宅などから行っているが、たまに授業変更が入ってきて業務時間を変更しなければならないことがある。

本校の授業変更情報はWebサイト上から確認することが出来るが、正直こんなものいちいち見に行くのはめんどくさい。結果、当日まで授業変更に気づかず、ギリギリになって業務時間変更の連絡をすることになり、上司やチーム全体に迷惑がかかってしまう。

[授業変更情報 - 苫小牧工業高等専門学校](http://jyugyou.tomakomai-ct.ac.jp/jyugyou.php)

![jyugyou.png](https://qiita-image-store.s3.amazonaws.com/0/35647/27841702-0a02-f397-21e9-4f1673964456.png)

重要な連絡事項をギリギリにしたり、アドベントカレンダーの担当日に記事の投稿を遅刻してしまうようでは、19年4月から社会人として働くという自覚が足りないと言われても仕方がないであろう。ではどうすれば良いのか。わざわざ授業変更情報をWebサイトを見に行くのがめんどくさいのなら、普段から見ているものに授業変更情報を取り込んでやれば良いのだ。

そこで今回開発したのが、「苫小牧高専授業変更情報 iCalendarフィード」（以下、本システムとする）である。

## 苫小牧高専授業変更情報 iCalendarフィード

[苫小牧高専授業変更情報 iCalendarフィード (Beta版)](https://jyugyou.mktakuya.net)

![ss.png](https://qiita-image-store.s3.amazonaws.com/0/35647/3c6d1466-fea8-80da-4f46-0aea0acaf8f6.png)

本システムを利用すれば、このようにかんたんに普段使いのカレンダー（GoogleカレンダーやApple iCalなど）に授業変更情報を取り込んで閲覧することが出来る。

![gif.gif](https://qiita-image-store.s3.amazonaws.com/0/35647/a2e23cbc-a43d-81a4-d965-d180a0d889c1.gif)


## 本システムの解説

本システムは、授業変更情報の取得部分と配信部分の2つのプログラムからなる。

### 授業変更情報の取得

授業変更情報の取得部分の全ソースコードは、以下のリポジトリにある。

[mktakuya/jyugyou](https://github.com/mktakuya/jyugyou/)

このプログラムは、以下のようなGemを利用している。

- HTMLのパースにNokogiri
- 授業変更情報のDBへの保存にSequel, pg

本校の授業変更情報ページは、以下のようなURLでアクセスすると対応したクラスの授業変更情報を吐いてくれるので、それを`Nokogiri`で適当にパースしてデータベースに突っ込むだけである。

`http://jyugyou.tomakomai-ct.ac.jp/jyugyou.php?class=${クラス名}&all=true`

このプログラムの核となる授業変更情報の取得部は以下のようなコードで実現している。

```ruby
connect_opt =  {"options"=>{"host"=>ENV['JYUGYOU_DB_HOST'], "user"=>ENV['JYUGYOU_DB_USER'], "password"=>ENV['JYUGYOU_DB_PASSWORD']}}
db = Sequel.postgres(ENV['JYUGYOU_DB_NAME'], connect_opt)

base_url = 'http://jyugyou.tomakomai-ct.ac.jp/jyugyou.php'

# CLASSES定数にはクラス名の対応が格納されている
# https://github.com/mktakuya/jyugyou/blob/master/classes.rb
CLASSES.each do |k, v|
  url = base_url + "?class=#{k.to_s.upcase}&all=true"
  html = Nokogiri::HTML(open(url))
  table = html.search('table')[-1]

  table.search('tr')[1..-1].each do |tr|
    data = {class: v}
    td0 = tr.search('td')[0]
    td0_text = td0.text.gsub('　', '').split
    data[:date] = Date.strptime(td0_text[0], '%Y年%m月%d日')
    data[:period] = td0_text[1]

    td1 = tr.search('td')[1]
    data[:content] = td1.text
    data[:content].slice!(0)

    db.transaction do
      db[:jyugyous].insert(data) unless db[:jyugyous].where(data).first
    end
  end
end

db.disconnect
```

これをcronで日に1回程度の間隔でまわしている。


### iCalendarフィードの出力

iCalendarフィードを出力する部分は、Sinatra製のWebアプリとなっている。

[mktakuya/jyugyou-feed](https://github.com/mktakuya/jyugyou-feed)

このプログラムは、以下のようなGemを利用している。

- Webアプリケーション開発用DSLとしてSinatra
- データベース接続用にSequel, pg
- iCalendar出力用にicalendar

iCalendarフィード出力部分は、以下のようなコードで実現している。

```ruby
# CLASSES定数にはクラス名の対応が格納されている
# https://github.com/mktakuya/jyugyou-feed/blob/master/classes.rb
get '/feeds' do
  if CLASSES.has_key? params[:class].downcase.to_sym
    conds = {
      class: CLASSES[params[:class].downcase.to_sym],
      date: Date.today.last_week.beginning_of_week..Date.today.next_week.end_of_week
    }
    calendar = Icalendar::Calendar.new
    calendar.append_custom_property("X-WR-CALNAME;VALUE=TEXT", "TNCT #{conds[:class]} 授業変更情報")
    calendar.timezone do |t|
      t.tzid = 'Asia/Tokyo'
      t.standard do |s|
        s.tzoffsetfrom = '+0900'
        s.tzoffsetto   = '+0900'
        s.tzname       = 'JST'
        s.dtstart      = '19700101T000000'
      end
    end

    @db[:jyugyous].where(conds).all.each do |col|
      calendar.event do |e|
        e.dtstart = Icalendar::Values::Date.new(col[:date])
        e.summary = "#{col[:period]} #{col[:content]}"
        e.description = "#{col[:class]} #{col[:date].to_s}(#{col[:period]}) #{col[:content]}"
      end
    end
    calendar.publish
    content_type 'text/calendar'
    calendar.to_ical
  else
    status 400
    '400 Bad Request'
  end
end
```

このWebアプリケーションに、 `/feeds?class=AP1` というパスでアクセスすると、AP1（自分の所属する電子・生産システム工学専攻科1年を表す）の授業変更情報がiCalendarフィードとして出力される。

正直、iCalendarフィードはリクエストの度に動的生成する必要は無いので、近々作り変えたい。そうすると、Sinatraなんて使う必要なくて、本当にただの静的サイトになってしまうのだけど……。


## おわりに

一人の学生として、そして一人のエンジニアとして、自分が不便だと感じたことは積極的に改善していく。この姿勢を忘れないでいきたい。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">アドベントカレンダーみんなマジメなこと書いてるしここで一発ネタを仕込みたいって思ってたら遅刻した</p>&mdash; mktakuya (@mktakuya) <a href="https://twitter.com/mktakuya/status/942410107568078848?ref_src=twsrc%5Etfw">2017年12月17日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>