Arrow Judge（オンラインジャッジシステム）への解答状況をSlackへ通知する

# はじめに

[前回の記事](https://qiita.com/hideo-works/items/cb8c7a8c4d177be1c08d)で紹介した社内プログラミングコンテストへの参加者は、東京、静岡、大阪の各拠点に分かれていました。「つながってる感」「ひとりじゃない感」が欲しかったので、解答状況をSlackへ通知するしくみを作ることにしました。

# やりたいこと

[Arrow Judge](https://www.slideshare.net/hiromu1996/arrow-judge)（オンラインジャッジシステム）へ解答が投稿されたら、その評価結果をSlackへ通知する。

実際にできた通知内容はこんな感じ。

![Hubot.png](https://qiita-image-store.s3.amazonaws.com/0/60193/0e3c9387-835a-c628-1235-32aac0b19628.png)

# どうやったか

## 処理フロー

Slackへの通知処理を一元管理するため、arrow-judge（ジャッジサーバ）→Hubot→Slackという処理フローにしました。

![ProcessFlow.png](https://qiita-image-store.s3.amazonaws.com/0/60193/32e89322-e63b-56e0-c113-a8f94c721f9c.png)

ジャッジサーバが評価を行ったタイミングで、その結果をHubotへPOSTし、Slackへ流します。

前提として、Slackと連携できているHubotがすでにあるものとします。

## Hubotスクリプトの準備

ジャッジサーバから評価結果をPOSTで受け取り、Slack内の#contestチャンネルへ通知します。

```coffee:arrow-judge.coffee
convertStatus = (status) ->
  statuses =
    '0'  : 'Waiting Judge'
    '1'  : 'Pending Judge'
    '2'  : 'Compile Error'
    '3'  : 'Runtime Error'
    '4'  : 'Resource Limit Exceed'
    '5'  : 'Wrong Answer'
    '6'  : 'Accept'
    '7'  : 'Judge Error'
  if status of statuses
    return statuses[status]
  else
    return "Unknown status..."

module.exports = (robot) ->
  robot.router.post "/hubot/arrow-judge/", (req, res) ->
    msg = "Arrow Judge - The answer to problem ##{req.body.problem_id} has been submitted.\n"
    msg += "Status: #{convertStatus(req.body.submission_status)}\n"
    msg += "http://WebサーバのURL/judge/submissions/detail/#{req.body.submission_id}"
    robot.messageRoom 'contest', msg
    res.status(201).send 'created'
```

## [arrow-judge](https://github.com/hiromu/arrow-judge)の改良

評価結果をHubotへPOSTするため、ジャッジサーバのスクリプトを修正します。/usr/lib/python2.7/dist-packeges/arrow-judge/daemon.py へhubot関数を追加し、run関数内のself.post呼出し後に実行します。

```diff:daemon.py.diff
--- daemon.py	(revision x)
+++ daemon.py	(working copy)
@@ -101,6 +101,24 @@
 		except:
 			self.trace()
 
+	def hubot(self, params, result):
+		if result['problem'] == '0':
+			try:
+				data = {}
+				data['problem_id'] = params['problem_id']
+				data['submission_id'] = result['id']
+				data['submission_status'] = result['status']
+				urllib2.urlopen('http://HubotのURL:8080/hubot/arrow-judge/', urllib.urlencode(data))
+			except urllib2.HTTPError, e:
+				if e.code != 404:
+					self.error_log.error('HTTP Error %d: %s' % (e.code, e.msg))
+					line = e.fp.readline()
+					while line:
+						self.error_log.error(line)
+						line = e.fp.readline()
+			except:
+				self.trace()
+
 	def run(self):
 		while True:
 			params = self.get()
@@ -108,5 +126,6 @@
 				result = self.judge(params)
 				if result:
 					self.post(result)
+					self.hubot(params, result)
 				continue
 			time.sleep(self.interval)
```

## [arrow-judge-web](https://github.com/hiromu/arrow-judge-web)の改良

最後に、上記diffで追加した'problem_id'が利用できるよう、Webサーバのスクリプトも修正します。修正対象は /usr/share/arrow-judge/app/Controller/JudgeController.php です。

```diff:JudgesController.php.diff
--- JudgesController.php	(revision x)
+++ JudgesController.php	(working copy)
@@ -75,6 +75,7 @@
 			$json['answer'] = $answers;
 			$json['cpu'] = $judge['Problem']['cpu'];
 			$json['memory'] = $judge['Problem']['memory'] * 1024;
+			$json['problem_id'] = $judge['Problem']['id'];
 			foreach(array('id', 'source') as $key) {
 				$json[$key] = $judge['Submission'][$key];
 			}
```

# おわりに

社内プログラミングコンテストは、実際にこのSlack通知を利用しながら行いました。実施後のアンケートで

- Slackのおかげで遠隔でも雰囲気を共有できた気がする
- 解いてる最中にSlackのポップアップ通知がガンガン来て焦った

といったコメントをもらうことができ「やっぱ、Slackは最高！」との思いが、より一層強くなりました。いや、「ガンガン」は失敗だったかもしれませんが...。