プロジェクト:ウィキ技術部/スクリプト開発/trunk/LimeChatAbuseFilterBot.js

//LimeChat2用のIRCbotスクリプト。apiから編集フィルターのログを取得し、設定したチャンネルに情報を整形して投稿する。
//2010年11月27日 LimeChat 2.38/WindowsXP にて動作を確認。ただし★メモリーリークの為、数時間の動作が今のところ限界★
//LimeChatの「設定」->「スクリプトの設定」->「スクリプトフォルダを開く」で開いたフォルダに、当ファイルを IRCbot.js などと名前を付けて保存。
//対象チャンネルに入室したあと、「スクリプトの設定」でサーバー名の上で右クリックすると、「○」ボタンが表示されてスクリプトが有効化、自動で実行される。

	var yourChannel = '##irc-bot-test-channel';//テキスト送信先のチャンネルの設定。##irc-bot-test-channelはテスト用チャンネルなので自由に投稿可。
	var wgServer     = 'http://ja.wikipedia.org';//サイトの指定
	var wgScriptPath = '/w';//スクリプトパス。Wikimediaプロジェクトであれば、おそらく全て /w で共通。
	
	//対処操作の内容を日本語表示に置き換えるための配列。 \x03 の後ろに番号で色が変わる。再度 \x03 と打つと元に戻る。 \x02 で挟まれている部分は太字
	//色については http://hiki.koubou.com/IRC/?mIrcColors 等を参照
	var lang_filterResult = [
		{ before: '',         after: '\x0301なし\x03'          },
		{ before: 'warn',     after: '\x02\x0304通知\x03\x02' },
		{ before: 'tag',      after: '\x0305タグ付け\x03'       },
		{ before: 'disallow', after: '\x02\x0304防止\x03\x02' }
		];
		
//当スクリプトはapi経由でウィキペディアの最近の更新(Recent changes、こうしたURL↓)を読み込み、
// http://ja.wikipedia.org/w/api.php?action=query&list=recentchanges|logevents|abuselog&rcprop=user|comment|flags|timestamp|title|ids|sizes|tags&format=xml
//そこで得たデータを処理して、IRC上のチャンネルにリアルタイムでテキストを投稿します。
//
//スクリプトは一番下に書かれている関数からまず呼び出され、上へ上へと順に実行されていきます。
//apiの読み込み等に特に問題がなければ、一番上にある filterDescription() および processRC() の二つの関数を変更すれば、大体のカスタマイズが可能です。

//当スクリプトの基幹部分は [[LiveRC]](by [[:fr:User:Educa33e]]他) を元に作成しました。


//編集フィルターについて、id番号からフィルターの内容を取得し、結果を整形してチャンネルに投稿するための関数
function filterDescription(xmlreq, data) {
  	var xmlinfo = xmlreq.responseXML.getElementsByTagName('api')[0]; 
	if (xmlinfo.firstChild.nodeName == "error") return;//取得した内容がエラーであれば処理中止。
	
	//フィルター番号の内容 例えば「小さすぎる記事の作成」といった名称をを取り出す。
	var filterDescription = xmlinfo.getElementsByTagName('abusefilters')[0].getElementsByTagName('filter')[0].getAttribute('description');
    
	var filterlog = data.filterlog;//ログの内容を受け取る。利用者名、ページ名などは全てここに入っている
	
	//対処操作の内容を日本語表示に置き換える。
	var filterResult = filterlog.getAttribute('result');
	for (var _i=0; _i<lang_filterResult.length; _i++){
		if (filterResult == lang_filterResult[_i].before)
			filterResult = lang_filterResult[_i].after;
	}
	
	var postText;//投稿するテキストを格納するための変数
	
	//テキストを作る。 \x03 の後ろに番号で色が変わる。再度 \x03 と打つと元に戻る。 色については http://hiki.koubou.com/IRC/?mIrcColors 等を参照
	postText =  ' [[\x0307' + filterlog.getAttribute('title') + '\x03]]'
				+ ' \x0305*\x0303 ' + filterlog.getAttribute('user') + '\x03 '
				+ ' \x0310' + filterDescription  + ' \x0305*\x03'
				+ ' 対処操作: ' + filterResult  + ' '
				+ wgServer + '/wiki/Special:Contributions/' + filterlog.getAttribute('user');
						
	send(yourChannel, postText);//IRCのチャンネルにテキストを送信。
}

//api経由でデータ取得に成功したら、ここでその内容を処理する。
function processRC(xmlreq, data) {
	var api = xmlreq.responseXML.getElementsByTagName('api')[0];
	if (api.firstChild.nodeName == "error") return; //取得した内容がエラーであれば処理中止。
	
	//普通の編集、ログ、フィルター記録、の三つを切り分けて、別々の変数に格納
	var rcs = api.getElementsByTagName('query')[0].getElementsByTagName('recentchanges')[0].getElementsByTagName('rc');
	var logs = api.getElementsByTagName('query')[0].getElementsByTagName('logevents')[0].getElementsByTagName('item');
	var filters = api.getElementsByTagName('query')[0].getElementsByTagName('abuselog')[0].getElementsByTagName('item');
	
	var	itemLength;//ログの個数を格納するための変数
	var filter_no;//フィルターの番号を入れる変数
	
	itemLength = filters.length;//フィルター記録の長さを取得
	for (i=itemLength-1; i>=0; i--) {//取得したデータの内、古いものから順に表示
		if (filters[i].getAttribute('id') > lastFILTERid) {//一度表示したものは表示しない。idが前回 最後に表示したものより大きい場合のみ、表示。
			
			filter_no = filters[i].getAttribute('filter_id');//フィルターの番号を取得
			//フィルター番号の内容 例えば「小さすぎる記事の作成」といった名称を、次のようなURLからapi経由で取得。
			// http://ja.wikipedia.org/w//api.php?action=query&list=abusefilters&abflimit=1&format=xml&abfstartid=10
			//取得に成功したら、ログのデータを data.filterlog という名前で filterDescription() に渡す。
			wpajax.http({url: wgServer + wgScriptPath + '/api.php?action=query&list=abusefilters&format=xml&abflimit=1&abfstartid=' + filter_no,
						onSuccess: filterDescription, filterlog: filters[i] });
		}
	}
	
	
	//次回、重複表示を避けるために、取得したデータの内、最も新しいもののidを取得して変数に格納
	lastRCid = rcs[0].getAttribute('rcid');
	lastLOGid = logs[0].getAttribute('logid');
	lastFILTERid = filters[0].getAttribute('id');
	
	//次にapiを呼び出す時のために、取得したデータの内、最も新しいもののタイムスタンプを取得して変数に格納
	lastRCtimestamp = rcs[0].getAttribute('timestamp')
	lastLOGtimestamp = logs[0].getAttribute('timestamp')
	lastFILTERtimestamp = filters[0].getAttribute('timestamp')
}

//Ajax用の変数、関数
var wpajax = {
  http: function(bundle) {
    var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
    if (xmlhttp) {
      xmlhttp.onreadystatechange = function() {
        if (xmlhttp.readyState == 4)
          wpajax.httpComplete(xmlhttp,bundle);
      };
	  
      xmlhttp.open('GET', bundle.url, true);
      if (bundle.headers) {
        for (var field in bundle.headers)
          xmlhttp.setRequestHeader(field,bundle.headers[field]);
      }
      xmlhttp.send(bundle.data ? bundle.data : null); 
    }
    return xmlhttp;
  },
 
  httpComplete: function(xmlhttp,bundle) {
    if (xmlhttp.status == 200 || xmlhttp.status == 302) {
      if (bundle.onSuccess)
        bundle.onSuccess(xmlhttp,bundle);
    } else if (bundle.onFailure) {
		bundle.onFailure(xmlhttp,bundle);
    } else {
      // その他の場合の設定
      // alert(xmlhttp.statusText);
    }
  }
};

//この関数をループする。定期的にこの関数を呼び出して、api経由で最近の更新のデータを取る。
//データが取れたら processRC() を呼び出す。
var lastRCid=0,lastFILTERid=0,lastLOGid=0,lastRCtimestamp=1,lastLOGtimestamp=1,lastFILTERtimestamp=1,RCLimitSize=10;
function getRCloop() {
	var interval = 1; //ループの間隔。秒で指定
	setTimeout(getRCloop, 1000*interval);
	
	//apiのURLを設定
	var rcURL = wgServer + wgScriptPath
    + '/api.php?action=query&list=recentchanges|logevents|abuselog'
    + '&rcprop=user|comment|flags|timestamp|title|ids|sizes|tags'
    + '&rcend=' + lastRCtimestamp + '&rclimit=' + RCLimitSize
    + '&leend=' + lastLOGtimestamp + '&lelimit=' + RCLimitSize
    + '&aflend=' + lastFILTERtimestamp + '&afllimit=' + RCLimitSize
    + '&format=xml';
	
	//api経由で最近の更新を読み込む。成功したら processRC() に読んだデータを渡す。失敗したらここで終わり。
	wpajax.http({url:rcURL, onSuccess: processRC});	
} 

//一番最初に一度だけ呼び出される関数。 ここで getRCloop() をロードし、後はそちらで永遠にループ。
function event::onLoad(prefix, channel, text) {
	getRCloop();
}
//[[Category:IRC (ウィキペディア関連)]]