Greasemonkey Manual:Installing Scripts: Difference between revisions

From GreaseSpot Wiki
Jump to navigationJump to search
No edit summary
No edit summary
Line 34: Line 34:
// フッター
// フッター
// null か空文字列 ('') にすると表示されなくなります
// null か空文字列 ('') にすると表示されなくなります
var FOOTER = '[miro_bot]';
var FOOTER = 'miro_bot';


// 投稿する間隔 (単位は分)
// 投稿する間隔 (単位は分)

Revision as of 18:33, 1 April 2010

// ==UserScript== // @name Twitter bot modoki reply random // @namespace http://sakuratan.biz/ // @description Twitter bot もどき // @include http://twitter.com/ // @include http://twitter.com/#* // @include https://twitter.com/ // @include https://twitter.com/#* // ==/UserScript==

//==================================================================== // 設定 //====================================================================

// bot が投稿するメッセージ var MESSAGES = [

   'びゃああああああああああああああああああああ',
   'バタッ',
   '昨日が終らない(´;ω;`)',
   'キャッキャッ',
   'gkbr!',
   'ぱあああああああ(*´ν`*)あああああああっっっっ',
   '…………((((´;ω;`))))ブワッ',
   'おえ〜',
   '永久就職マダー(・∀・ )っ/凵⌒☆チンチン',
   '現実ドコー',
   'タイムリープ♡',
   'ひさっちゃんはぁはぁ',
   'えへっ(*´ν`*)',
   'いちごmogmog',
   '(`・ω・´)!',

];

// フッター // null か空文字列 () にすると表示されなくなります var FOOTER = 'miro_bot';

// 投稿する間隔 (単位は分) var TIMER_INTERVAL = 1;

//==================================================================== // 以下も設定ですが、変更するとプログラムが壊れることがありますので // あまり自信の無い方は変更しないようにしてください //====================================================================

// デバッグフラグ var DEBUG = true;

// メッセージ判定(POST_MODE=='post' 時は強制的に none になります) // none メッセージ判定を行わない // disable_RT RT QT を含まないメッセージのみリプライします // hello 挨拶モード(おはように対してリプライを返す) var MESSAGE_COND = 'none';

// メッセージモード // oneshot ツイッターアクセスごとに先頭から順に一度投稿 // loop 先頭から順に繰り返し投稿 // random ランダムに繰り返し投稿 var MESSAGE_MODE = 'random';

// 投稿モード // post 自動ポスト // reply 自動リプライ // timeline タイムラインからリプライ // ※(MESSAGE_COND == hello && POST_MODE != post) はエラーになります var POST_MODE = 'reply';

// (MESSAGE_COND == hello) のとき挨拶か判定する関数 // 適当なのでお好きに直してください function is_hello(text) {

   if (text.search(/^おは(?:や[ーう]|[よゆ]ー?)/) >= 0) {
       return true;
   }
   if (text.search(/^こんに?ち[はわ]/) >= 0) {
       return true;
   }
   if (text.search(/^(?:こん?ばん?[わみ]|こん?ば[やゆよ])/) >= 0) {
       return true;
   }
   return false;

}

// true なら起動時に MAX_REPLY_ID_KEY をタイムラインの最新の id で // 更新します(タイムライン取得に成功するまでつぶやきを開始しません) var MAX_REPLY_ID_SYNC_REBOOT = true;

// プレフィクス var PREFIX = ['twitter-bot-modoki', MESSAGE_COND, MESSAGE_MODE, POST_MODE, ]

            .join('-');

// 最後に投稿した時刻を保存するキー var LAST_POSTED_KEY = PREFIX + 'last-posted';

// 排他制御用のシリアルを保存するキー var POST_SERIAL_KEY = PREFIX + 'post-serial';

// 次に投稿する MESSAGES のインデクスを保存するキー var LAST_INDEX_KEY = PREFIX + 'last-index';

// 最後にリプライした投稿の id を保存するキー var MAX_REPLY_ID_KEY = PREFIX + 'max-reply-id';

//==================================================================== // プログラムの挙動を変更する場合は以下を編集してください // ※プログラミングの仕方が分からない場合は編集しないでください //====================================================================

// 自分のスクリーンネーム var MY_SCREEN_NAME;

// フック var COND_HOOK; var MESSAGE_HOOK; var POST_HOOK;

// インターバルタイマ var TIMER;

// タブ間の排他処理用のシリアル var SERIAL;

// POSTエラー時は真 var RETRING;

// // ツイッター //

function get_auth_token() {

   var auth_token = document.getElementById('authenticity_token');
   if (auth_token) {
       return auth_token.value;
   } else {
       return null;
   }

}

function twit(data) {

   var auth_token = document.getElementById('authenticity_token');
   if (!auth_token) {
       return;
   }
   var postdata = 'authenticity_token=' +
                  encodeURIComponent(auth_token.value) +
                  '&status=' + encodeURIComponent(data.status) +
                  '&twttr=true';
   if (data.in_reply_to_status_id) {
       postdata += '&in_reply_to_status_id=' +
                   encodeURIComponent(data.in_reply_to_status_id);
       if (DEBUG) {
           GM_log('twit: status=' + data.status +
                  '&in_reply_to_status_id=' + data.in_reply_to_status_id);
       }
   } else {
       if (DEBUG) {
           GM_log('twit: status=' + data.status);
       }
   }
   var params = {
       type: 'POST',
       url: 'http://twitter.com/status/update',
       data: postdata
   };
   if (data.success) {
       params.success = data.success;
   }
   params.error = function() {
       if (data.error) {
           data.error();
       }
       window.setTimeout(function() {
           unsafeWindow.$.ajax(params);
       }, 300000);
       throw new Error('Ajax error, retring after 300 seconds...');
   };
   unsafeWindow.$.ajax(params);

}

// // COND_FUNCS // mixed COND_HOOK(mesg); // mesg (リプライメッセージもしくは null) を受け取りメッセージを // 返すなら false 以外を、返さないなら false を返す // false 以外が返した場合、その値は MESSAGE_HOOK の cond 引数に渡される // var COND_FUNCS = {};

COND_FUNCS.none = function(mesg) {

   return true;

};

COND_FUNCS.disable_RT = function(mesg) {

   return (mesg.search(/\b(?:RT|QT)\b/) < 0);

};

COND_FUNCS.hello = function(mesg) {

   var can_reply = true;
   var flagment = unsafeWindow.$(mesg);
   var reply_me = flagment.filter('a[href="/' + MY_SCREEN_NAME + '"]');
   if (reply_me.length <= 0) {
       var links = flagment.filter('a[href^="/"]')
           .filter('a[href!="/' + MY_SCREEN_NAME + '"]');
       if (links.length > 0) {
           can_reply = false;
       }
   }
   if (can_reply) {
       // タグを除去して挨拶か調べる
       var text = mesg.replace(/<[^>]*>/g, );
       if (is_hello(text)) {
           if (DEBUG) {
               GM_log('hello: repling=' + mesg);
           }
           return true;
       }
   }
   if (DEBUG) {
       GM_log('hello: skipped,reply=' + mesg);
   }
   return false;

};

// // MESSAGE_FUNCS // string or null MESSAGE_HOOK(mesg, cond); // mesg (リプライメッセージもしくは null) と COND_HOOK からの戻り値 // cond を受け取りメッセージ文字列を返す // 文字列の代わりに null を返すとメッセージは送信されない // var MESSAGE_FUNCS = {};

MESSAGE_FUNCS.oneshot = function(mesg, cond) {

   var i = GM_getValue(LAST_INDEX_KEY, 0);
   var next = i + 1;
   if (next >= MESSAGES.length) {
       GM_setValue(LAST_INDEX_KEY, MESSAGES.length);
       stop_timer();
       if (i >= MESSAGES.length) {
           if (DEBUG) {
               GM_log('oneshot: last message was posted');
           }
           return null;
       }
   }
   if (DEBUG) {
       GM_log('oneshot: index=' + i);
   }
   GM_setValue(LAST_INDEX_KEY, next);
   return MESSAGES[i];

};

MESSAGE_FUNCS.loop = function(mesg, cond) {

   var i = GM_getValue(LAST_INDEX_KEY, 0);
   if (i >= MESSAGES.length) {
       i = 0;
   }
   if (DEBUG) {
       GM_log('loop: index=' + i);
   }
   var next = i + 1;
   GM_setValue(LAST_INDEX_KEY, next);
   return MESSAGES[i];

};

MESSAGE_FUNCS.random = function(mesg, cond) {

   var i = Math.floor(Math.random() * MESSAGES.length);
   if (DEBUG) {
       GM_log('random: index=' + i);
   }
   return MESSAGES[i];

};

// // POST_FUNCS // void POST_HOOK(); // タイマから起動されメッセージを送信する // var POST_FUNCS = {};

function get_message(reply_mesg, cond) {

   var mesg = MESSAGE_HOOK(reply_mesg, cond);
   if (mesg == null) {
       return null;
   }
   if (FOOTER) {
       mesg += ' ';
       mesg += FOOTER;
   }
   return mesg;

}

function stop_timer() {

   if (TIMER) {
       if (DEBUG) {
           GM_log('stop_timer: timer=stop');
       }
       window.clearInterval(TIMER);
       TIMER = null;
   }

}

POST_FUNCS.post = function() {

   var rv = COND_HOOK(null);
   if (!rv) {
       return;
   }
   var mesg = get_message(null, rv);
   if (mesg == null) {
       stop_timer();
       return;
   }
   twit({
       status: mesg
   });

};

function update_max_reply_id(id) {

   var i = parseInt(GM_getValue(MAX_REPLY_ID_KEY, 0));
   if (parseInt(id) > i) {
       if (DEBUG) {
           GM_log('update_max_reply_id: ' + MAX_REPLY_ID_KEY + '=' + id);
       }
       // Twitter の id の範囲が不明なので文字列のまま保存
       GM_setValue(MAX_REPLY_ID_KEY, id);
   }

}

function dequeue_replies(replies) {

   var reply = replies.pop();
   if (!reply) {
       return;
   }
   if (DEBUG) {
       GM_log('dequeue_replies: screen_name=' + reply.screen_name +
              ',in_reply_to_status_id=' + reply.id);
   }
   twit({
       status: reply.status,
       in_reply_to_status_id: reply.id,
       success: function() {
           window.setTimeout(function() {
               update_max_reply_id(reply.id);
               dequeue_replies(replies);
           }, 0);
       }
   });

}

function parse_timeline(res) {

   var doc = unsafeWindow.$(res);
   return unsafeWindow.$('#timeline li.status', doc);

}

function parse_replies(res) {

   var max_reply_id = parseInt(GM_getValue(MAX_REPLY_ID_KEY, 0));
   var reply_screen_names = {};
   var replies = [];
   var statuses = parse_timeline(res);
   var last_id = null;
   for (var i = 0; i < statuses.length; ++i) {
       var status = unsafeWindow.$(statuses[i]);
       var id = status.attr('id').replace(/^status_/, );
       if (parseInt(id) <= max_reply_id) {
           break;
       }
       last_id = id;
       // 自分へはリプライしない
       var action_reply = unsafeWindow.$('.actions,.actions-hover .reply', status);
       if (action_reply.length <= 0) {
           continue;
       }
       var screen_name = unsafeWindow.$('.screen-name', status).text();
       if (reply_screen_names[screen_name]) {
           if (DEBUG) {
               GM_log('parse_replies: already sent to ' + screen_name +
                      ' in this session');
           }
           continue;
       }
       var s = unsafeWindow.$('.entry-content', status).html();
       var cond = COND_HOOK(s);
       if (DEBUG) {
           GM_log('parse_replies: status=' + s + ',cond=' + cond);
       }
       if (cond) {
           var t = get_message(s, cond);
           if (t != null) {
               reply_screen_names[screen_name] = true;
               replies.push({
                   status: '@' + screen_name + ' ' + t,
                   screen_name: screen_name,
                   id: id
               });
           }
       }
   }
   if (DEBUG) {
       GM_log('parse_replies: max_reply_id=' + max_reply_id +
              ',replies=' + replies.length);
   }
   if (replies.length == 0) {
       if (last_id != null) {
           update_max_reply_id(last_id);
       }
   } else {
       // XXX dequeue_replies が完了してから
       // last_id で MAX_REPLY_ID_KEY を更新した方が良いかもしれない
       dequeue_replies(replies);
   }

}

function post_replies_success(res) {

   window.setTimeout(function() {
       parse_replies(res);
   }, 0);

}

POST_FUNCS.reply = function() {

   unsafeWindow.$.ajax({
       type: 'GET',
       url: 'http://twitter.com/replies?twttr=true',
       success: post_replies_success
   });

};

POST_FUNCS.timeline = function() {

   unsafeWindow.$.ajax({
       type: 'GET',
       url: 'http://twitter.com/home?twttr=true',
       success: post_replies_success
   });

};

// // Main //

function start(wait) {

   SERIAL = GM_getValue(POST_SERIAL_KEY, 0);
   if (DEBUG) {
       GM_log('start: SERIAL=' + SERIAL);
   }
   window.setTimeout(on_timer, wait);

}

function on_timer() {

   if (RETRING) {
       return;
   }
   if (!TIMER) {
       var intr = TIMER_INTERVAL * 60000;
       if (DEBUG) {
           GM_log('on_timer: setInterval=' + intr);
       }
       TIMER = window.setInterval(on_timer, intr);
   }
   // NOTE 複数タブからグリモンが実行されると
   // この辺がクリティカルセクションになる
   if (SERIAL != GM_getValue(POST_SERIAL_KEY, 0)) {
       // こっちのパスはそれほどクリティカルじゃない
       if (DEBUG) {
           GM_log('on_timer: another process running');
       }
       SERIAL = GM_getValue(POST_SERIAL_KEY, 0);
       return;
   }
   // クリティカルなのはこっち
   GM_setValue(POST_SERIAL_KEY, ++SERIAL);
   // ここまで
   if (DEBUG) {
       GM_log('on_timer: SERIAL=' + SERIAL);
   }
   var dt = new Date();
   GM_setValue(LAST_POSTED_KEY, dt.getTime().toString());
   POST_HOOK();

}

function get_wait() {

   var last_posted = GM_getValue(LAST_POSTED_KEY);
   if (!last_posted) {
       return 0;
   }
   var ms = parseFloat(last_posted) + TIMER_INTERVAL * 60000;
   var dt = new Date();
   var wait = ms - dt.getTime();
   if (wait < 0) {
       return 0;
   }
   return wait;

}

function max_reply_id_sync_success(res) {

   var wait = get_wait();
   var statuses = parse_timeline(res);
   if (statuses.length > 0) {
       var status = unsafeWindow.$(statuses[0]);
       var id = status.attr('id').replace(/^status_/, );
       if (DEBUG) {
           GM_log('max_reply_id_sync_success: '+MAX_REPLY_ID_KEY+'='+id);
       }
       GM_setValue(MAX_REPLY_ID_KEY, id);
   }
   if (DEBUG) {
       GM_log('max_reply_id_sync_success: wait=' + wait);
   }
   start(wait);

}

function max_reply_id_sync() {

   unsafeWindow.$.ajax({
       type: 'GET',
       url: 'http://twitter.com/home?twttr=true',
       success: function(res) {
           window.setTimeout(function() {
               max_reply_id_sync_success(res);
           }, 0);
       },
       error: function() {
           if (DEBUG) {
               GM_log('max_reply_id_sync: Ajax failed, retring...');
           }
           window.setTimeout(function() {
               max_reply_id_sync();
           }, 60000);
       }
   });

}

function main() {

   if (DEBUG) {
       GM_log('main: ' + MESSAGE_MODE + ',' + MESSAGE_COND + ',' +
              POST_MODE);
   }
   if (!document.getElementById('authenticity_token')) {
       if (DEBUG) {
           GM_log('main: No authenticity_token');
       }
       return;
   }
   var metas = document.getElementsByTagName('meta');
   for (var i = 0; i < metas.length; ++i) {
       if (metas[i].name == 'session-user-screen_name') {
           MY_SCREEN_NAME = metas[i].content;
           break;
       }
   }
   if (!MY_SCREEN_NAME) {
       if (DEBUG) {
           GM_log('main: No session-user-screen_name');
       }
       return;
   }
   if (DEBUG) {
       GM_log('main: MY_SCREEN_NAME=' + MY_SCREEN_NAME);
   }
   RETRING = false;
   // 先にエラーチェック
   if (MESSAGE_COND == 'hello' && POST_MODE == 'post') {
       throw new Error('Invalid MESSAGE_COND ' + MESSAGE_COND +
                       ' and POST_MODE ' + POST_MODE);
   }
   // oneshot モード時は必要ならインデクスをリセット
   if (MESSAGE_MODE == 'oneshot' &&
       GM_getValue(LAST_INDEX_KEY, 0) >= MESSAGES.length) {
       if (DEBUG) {
           GM_log('main: ' + LAST_INDEX_KEY + '=0');
       }
       GM_setValue(LAST_INDEX_KEY, 0);
   }
   // 各フックを初期化
   MESSAGE_HOOK = MESSAGE_FUNCS[MESSAGE_MODE];
   if (!MESSAGE_HOOK) {
       throw new Error('Unknown MESSAGE_MODE ' + MESSAGE_MODE);
   }
   COND_HOOK = COND_FUNCS[MESSAGE_COND];
   if (!COND_HOOK) {
       throw new Error('Unknown MESSAGE_COND ' + MESSAGE_COND);
   }
   POST_HOOK = POST_FUNCS[POST_MODE];
   if (!POST_HOOK) {
       throw new Error('Unknown POST_MODE ' + POST_MODE);
   }
   // ボット開始
   if (MAX_REPLY_ID_SYNC_REBOOT && POST_MODE != 'post') {
       max_reply_id_sync(wait);
   } else {
       var wait = get_wait();
       if (DEBUG) {
           GM_log('main: wait='+wait);
       }
       start(wait);
   }

}

main();