Node.js/twitter bot

Last-modified: 2014-03-05 (水) 01:27:00

導入

0. bot垢を作り、https://dev.twitter.com/ で application を作成

 このとき、settingタブ の Application type の Access を 「Read, Write and Access direct messages」 にしておく。
 DetailsタブのYour access tokeで設定が変わったか確認する。Recreateしたほうがいいのかもしれない。
 OAuth Toolタブの4つの文字とか数字の羅列は後で使うのでメモっとく

1. Node.jsをインストールします
2. コマンドプロンプトとかで

...> node -v
v0.10.18

...> npm -v
1.3.8

以上のようになればインストール成功です
(versionは自分がbot作ったときのなので最新版じゃないかも)

3. matchaフォルダにapp.jsを作ります
4. コマンドプロンプトでmatchaフォルダに移動します
5. npmでtwitterモジュールを導入します。今回はnode-twitterを使いました

.../matcha> npm install twitter

6. ツイートをするプログラムを書いてみる
app.js

var twitter = require('twitter');
var print = require('util').log;

var bot = new twitter({
	consumer_key		: /* 各自記入 */,
	consumer_secret		: /* 各自記入 */,
	access_token_key	: /* 各自記入 */,
	access_token_secret : /* 各自記入 */
});
bot.updateStatus("ぽよ",function(data){
	if(data.data){
		/* エラーが発生したとき */
		var err = JSON.parse(data.data);
		var str = err.errors[0].code + " - " + err.errors[0].message;
		print("[error] " + str);
	}
});

consumer_keyとかは前述のアレ

7. 起動します

.../matcha> node app.js

Twitter APIについて

手動更新しなくてもTLが流れていくやつとかはStreaming APIを利用している。
Streaming APIを使うとリアルタイムでいろいろ情報が流れてくる。
詳しくは公式

Node.jsについて

非同期処理という特徴がある。
重い処理をする際、結果を待たずに次の処理を始める。結果の受け取りをコールバック関数が行う。
詳しくはgoogle先生に

User Stream を使う

var twitter = require('twitter');

var print = reuire('util').log;
var ins = require('util').inspect; // Object型を表示するときに使う

var bot = new twitter({
	consumer_key		: /* 各自記入 */,
	consumer_secret		: /* 各自記入 */,
	access_token_key	: /* 各自記入 */,
	access_token_secret : /* 各自記入 */
});

// User Stream
bot.stream('user', function(stream){
	stream.on('data',function(data){
		// データを受信した時に実行される
		// ツイート以外のもの(ツイ消しとかフォロー情報とかも流れてくる)
		// 接続開始時にFF情報が入ってくる仕様らしい
		if('friends' in data){
			print("接続しました");
			return;
		}
		if(!('text' in data)){return;} // ツイート以外はさよなら
		print(ins(data)) // 表示
		/* ここにbotとしての処理を書く */
	});
});

dataの中にはいろんな情報が流れてくる
dataはJSON形式
dataオブジェクトのなかにtextというプロパティが含まれていれば、ツイートだと判断する
フォローしているユーザーのツイートが流れてくる

  • 使いそうなやつ
    stream.on('data',function(data){
    ...
    var screen_name = data.user.screen_name; // @aaaaaaa
    var user_name = data.user.name; // 垢名
    var user_id = data.user.id_str; // ID(文字列)
    var status_id = data.id_str; // ツイートID(文字列)
    var twtext = data.text; // ツイート内容
    var reply_user_id = data.in_reply_to_user_id_str; // リプ元のツイート
    var reply_status_id = data.in_reply_to_status_id_str; // リプ先のユーザーID(文字列)
    var twdate = new Date(data.created_at); // ツイート日時
    var via = data.source.replace(new RegExp(/<[^>]+>/g),''); // via
    
    var isMention = data.entities.user_mentions.length ? data.entities.user_mentions[0].id_str == BOT_ID : false; // 自分へのreplyかどうか
    var isRetweet = data.retweeted_status ? true : false; // 公式RTかどうか
    var isRetweet2 = (new RegExp(/(R|Q)T @[^\s ]+/g)).test(twtext); // 非公式RT,QTかどうか
    var isLink = data.entities.urls.length ? true : false; // リンクを含むかどうか
    
    /* 自分自身のツイートは除外 */
    if(user_id == BOT_ID){ return; }
    /* リンク付きとRTはさよなら */
    if(isRetweet || isRetweet2 || isLink){ return; }
    
    /* 除外処理 */
    twtext = twtext.replace(new RegExp(/@[^\s ]+/g),'') // @aaaaaは全て除外
    				.replace(new RegExp(/#[^\s ]+/g),'') // ハッシュタグを全て除外
    				.replace(new RegExp(/^[\s ]+/),'') // 前の空白を除外
    				.replace(new RegExp(/[\s ]+$/),''); // 後ろの空白を除外
    
    ...
    });
    BOT_IDに自分のIDを入れとく。IDはwhotwiとかで調べられる。Janetterだとプロフィールから見られる。10桁ぐらいの数字列。
    screen_nameでも判定してもいいけど、変えたとき面倒。
    data.entities.user_mentions[0]は先頭の@だけだから、復数リプの2番目だったときは無視する。
  • 関数化しておく
    // エラー処理用
    var twProcessError = function(data){
    	if(data.data){
    		var err = JSON.parse(data.data);
    		var str = err.errors[0].code + " - " + err.errors[0].message;
    		print("[error] " + str);
    	}
    }
    
    function tweet(text, status_id){
    	bot.updateStatus(text, {in_reply_to_status_id : status_id}, twProcessError);
    }
    
    function reply(screen_name, text, status_id){
    	tweet('@' + screen_name + ' ' + text, status_id);
    }
    status_idを入れてツイートするとin_reply_toが付く
  • 実装例
    stream.on('data',function(data){
    ...
    // オウム返し
    if(isMention){
    	reply(screen_name, twtext, status_id)
    }
    ...
    });
    // 特定ワードに飯能
    if((new RegExp(/.*抹茶.*/)).test(twtext)){
    	tweet("抹茶おいしい!")
    }
  • favる
    function favorite(id_str){
    	bot.favoriteStatus(id_str, twProcessError);
    }
    node-twitterのTwitterAPI ver1.1対応がめっちゃ適当だから、これだと動かない
    • 手直し
      /node_modules/twitter/lib/twitter.js
      createFavoriteのvar urlをいじる
Twitter.prototype.createFavorite = function(id, params, callback) {
	var url = '/favorites/create.json?id=' + escape(id);
	this.post(url, params, null, callback);
	return this;
}

/favorites/create/:id.json はver1の仕様なので、
/favorites/create.json?id=:id のようにver1.1に対応させる
node-twitterの中身見たら、こういうの結構多いし、ver1にしか無いAPIとかある
でも自分で1から作るより楽だから文句言わず手直ししてる
やっぱ自分でモジュール書きました。ほぼコピペだけど

再接続処理

streamはいつか切れるので、再接続処理しないとアレです
割と適当に実装したら何か動くのでこれでいいんだと思います。

function bot_run(){
	bot.stream({},function(stream) {
		stream.on('data', function(data) {
			//...
		});
		stream.on('error', function(error) {
			print("error : " + (ins(error)));
		});
		stream.on('end', function() {
			print("stream end.");
			// 再接続
			setTimeout( function(){ bot_run() }, 1000);
		});
    });
};

bot_run();