Greasemonkey Manual:Installing Scripts: Difference between revisions
m Reverted edits by 187.1.195.43 (Talk) to last revision by Arantius |
No edit summary |
||
Line 1: | Line 1: | ||
// ==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(); |
Revision as of 18:13, 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();