Botkitで論文をpostするSlack Botを作る

#概要
現在、個人レベルで深層学習などの研究しています。
それにあたり、未だに目にしていない論文に目を通す機会が欲しいため、slackに論文をpostすることでslackメンバーや自分の機会が増えると思い作りました。
![slack.jpg](https://qiita-image-store.s3.amazonaws.com/0/177955/d89c3192-60d8-5a05-b4cc-f372e4999e79.jpeg)



#必要なもの
Node.js と MySQLを使います。
MySQLの導入は、[こちら](http://qiita.com/kojionilk/items/bdace886c3664d75c5f7)を参考にさせていただきました。

#部分的な解説
コードの全体は[コチラ](https://github.com/TrsNium/SlackBot_PostPaper)

まずMySQLにてデータベースを作ります。

``` 
mysql> create database paper;
mysql> CREATE TABLE paper_content(id INT NOT NULL AUTO_INCREMENT, title TEXT, summary TEXT);
mysql> CREATE TABLE paper_info(id INT NOT NULL AUTO_INCREMENT, arxiv_id TEXT, published DATE, content INT, count INT);
```
以上のsqlでデータベースの作成をしてください。
次に、論文の情報取得ですがarxivで[API](https://arxiv.org/help/api/index)が公開されているので、それを使用します。

```
http://export.arxiv.org/api/query?search_query=all:%22Deep%20Learning%22&start=0&max_results=4000&sortBy=submittedDate&sortOrder=descending
```

たとえば上記の様にリクエストすると、title, Abstract, Category...どれかにDeepLearningが含まれてりるものを日付で降順にし4000件を取得すると言った感じです。(たぶん)
node-mysqlを使い取得した情報をためていきます。
node-mysqlについて詳しく知りたい方は[コチラ](http://qiita.com/PianoScoreJP/items/7ed172cd0e7846641e13)を参照してください。
以下でコネクションを作ります。

```main.js
var mysql = require('mysql');
var mysql_con;

mysql_con = mysql.createPool({
	host : 'localhost',
	user : 'root',
	password : password,
	database : 'paper'});
```

次にarxivのAPIがxmlフォーマットで返ってくるためパースします

```main.js
var request = require('sync-request');
var DOMParser = require('xmldom').DOMParser;

//取得する件数
size = 4000;
//マッチさせる文字
query = 'Deep%20Learning'
var url = "http://export.arxiv.org/api/query?search_query=all:%22"+
				query + "%22&start=0&max_results=" + String(size)+'&sortBy=submittedDate&sortOrder=descending';

var res = request('GET',url);
if (res.statusCode == 200){
    body = res.getBody('utf-8')
    var parser = new DOMParser();
    xmlDoc = parser.parseFromString(body,'text/xml');
    var p = new Promise(function(res) { res(); });
    for (var i=0; i<size; i++){
		try{
			var arxiv_id = xmlDoc.getElementsByTagName('feed')[0].getElementsByTagName('entry')[i].getElementsByTagName('id')[0].textContent;
			var title = xmlDoc.getElementsByTagName('feed')[0].getElementsByTagName('entry')[i].getElementsByTagName('title')[0].textContent;
			var published = xmlDoc.getElementsByTagName('feed')[0].getElementsByTagName('entry')[i].getElementsByTagName('published')[0].textContent;
		    var summary = xmlDoc.getElementsByTagName('feed')[0].getElementsByTagName('entry')[i].getElementsByTagName('summary')[0].textContent;
		}catch(e){
			continue;
		}
		console.log(arxiv_id)
		p = p.then(makePromiseFunc2InsertPaper(arxiv_id, title, published, summary, xmlDoc, i));
	}
}		
```

パースした文字列の挿入

```main.js
function makePromiseFunc2InsertPaper(arxiv_id, title, published, summary, xmlDoc, i) {
  return function(prevValue) {
    return new Promise(function(res, rej) {

	mysql_con.query("SELECT id FROM paper_info WHERE arxiv_id=?;", [arxiv_id], (err, results)=>{
		if(err)console.log('error!');
        //すでに存在しているか
		if (results.length > 0){
			console.log(arxiv_id + "arleady exits");
			res();
			return;
		}
		

		mysql_con.query('INSERT INTO paper_content(title, summary) VALUES(?,?);',[title, summary],(err, results)=>{
			if(err)console.log('INSERT ERROR!: paper content');
		});

		mysql_con.query('INSERT INTO paper_info(arxiv_id, published, content, count) SELECT ?, ?, max(id),0 FROM paper_content',[arxiv_id, published.split('T')[0]],
		(err, results)=>{
			if(err)console.log('INSERT ERROR!: paper info');
		});
	});
	res();
    });
  };
}
```

Bot準備

```main.js
var Botkit = require('botkit');
var controller = Botkit.slackbot();
var bot = controller.spawn({token: API_KEY});
bot.config.retry = true;

function start_rtm(){
	bot.startRTM((err,bot,payload)=>{
		if (err){throw new Error('Could not connect to Slack');}
		//毎日0時に実行
		new Cron({
			cronTime: '00 00 0 * * 0-6',
			onTick:()=>{
                //新規投稿された論文の挿入
				query2InsertDBHelper('query.json',5000);
				console.log("--------------Insert DB---------------");
			},
			start: true,
			timeZone: 'Asia/Tokyo'
		});
        //毎日12時に実行
		new Cron({
			cronTime: '00 00 12 * * 0-6',
			onTick: ()=>{
				bot.say({
					channel: "paper_arxiv",
					text: 'ｷｮｳﾉﾛﾝﾌﾞﾝﾊ,　ｺﾁﾗ.'+'\n',
					username: 'ラッキービースト',
					icon_url: 'http://dic.nicovideo.jp/oekaki/823138.png'
				});
                //論文を選んでpost
				choiceFromDB(bot, 20);			
			},
			start: true,
			timeZone: 'Asia/Tokyo'
		});
	});
}

start_rtm();
```
論文のセレクトとpost

```main.js
function choiceFromDB(bot, size){
	var p = new Promise(function(res) { res(); });
	mysql_con.query('SELECT paper_info.id,  paper_info.arxiv_id, paper_info.published, paper_info.count, paper_content.title, paper_content.summary FROM paper_info, paper_content WHERE paper_info.content = paper_content.id ORDER BY paper_info.count LIMIT ?;',[size],(e, r)=>{
		if(e)console.log('error');
		var p = new Promise(function(res) { res(); });
		for (var i=0;i<size;i++){
			mess = r[i].arxiv_id + '\n' + r[i].published + '\n' + r[i].title + "\n" + r[i].summary + "\n" + '-------------------------------------------------';
			bot.say({
				channel: "paper_arxiv",
				text: mess,
				username: 'ラッキービースト',
				icon_url: 'http://dic.nicovideo.jp/oekaki/823138.png'
			});
            //countの更新
			p = p.then(makePromiseFunc2UpdatePaper(r[i].count+1, r[i].id));
		}
	});
}
```

#参考
forループの中でmysqlクエリを同期的に実行したい[https://teratail.com/questions/44488]