Difference between revisions of "Greasemonkey Manual:Installing Scripts"

From GreaseSpot Wiki
Jump to navigationJump to search
(Created page with 'meu no,e e patrick')
Line 1: Line 1:
// ==UserScript==
meu no,e e patrick
// @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();

Revision as of 15:04, 1 April 2010

meu no,e e patrick