diff options
author | Jagua | 2012-07-12 23:05:01 +0900 |
---|---|---|
committer | Jagua | 2012-07-12 23:05:01 +0900 |
commit | 135390113bd5a830f09bbc36ffb15dccf4c8de21 (patch) | |
tree | 1c87071b7ed335368f343677e5d634dc073add7c /twittperator | |
parent | 27950925687f7c19135268a35b18a7efa643b4ac (diff) | |
download | vimperator-plugins-135390113bd5a830f09bbc36ffb15dccf4c8de21.tar.bz2 |
modified scrolling
Diffstat (limited to 'twittperator')
-rw-r--r-- | twittperator/twsidebar.tw | 1212 |
1 files changed, 606 insertions, 606 deletions
diff --git a/twittperator/twsidebar.tw b/twittperator/twsidebar.tw index bc81f28..a9cc9ce 100644 --- a/twittperator/twsidebar.tw +++ b/twittperator/twsidebar.tw @@ -1,606 +1,606 @@ -liberator.modules.TWAnekoSB = ANekoSB = (function () {
-
- /*********************************************************************************
- * Config
- *********************************************************************************/
-
- let Config = liberator.globalVariables.twittperator_sidebar_config || {
- // for Keyword タグ
- keyword: /neko|vimp|cat|猫/i,
-
- // ツイート内に含まれると、表示上抹殺される (reply とか除く
- vanish: /うぎぃいいい/i,
-
- // 自分のスクリーンネーム
- screenName: 'anekos',
-
- // 自分のその他の名前
- myNames: /anekos|悪魔猫将軍/i,
-
- // ログファイル てけとーなフォーマットで保存されます
- //logFile: io.File('~/.chirpstream'),
- //myLogFile: io.File('~/.mychirpstream'),
-
- // 各イベント時に音がなる
- sound: {
- meow: makeAudio('file:///home/anekos/sound/my/meow.wav'),
- fanfare: makeAudio('file://C:/sound-data/fanfare.wav', 0.5),
- retweet: makeAudio('file:///home/anekos/sound/my/meow.wav', 0.8),
- favorite: makeAudio('file:///home/anekos/sound/my/meow.wav', 0.6),
- reply: makeAudio('file:///home/anekos/sound/my/meow.wav', 1.0),
- debug: makeAudio('file:///home/anekos/sound/my/meow.wav', 1.0),
- filter: makeAudio('file:///home/anekos/sound/my/meow.wav', 1.0),
- },
-
- // 文字のサイズ
- fontSize: 15,
-
- // リストの最大保持数
- listMax: 100,
-
- // リストの表示順(昇順/降順)
- listAscendingOrder: true,
-
- // ツイートされる度に最新ツイート位置までスクロールする
- listAutoScroll: true,
-
- // 日本語だけ for filter stream
- jpOnly: true,
-
- // 地震ツイートの本文に場所をくっつける
- earthquake: true,
-
- // サイドバーを閉じても機能を停止しない
- dontStop: true,
-
- // サイドバーが閉じていても、こっそり開始しておく
- silentStart: false,
-
- // 配列かオブジェクトを返すと、変更できる。
- // 文字列 "reject" を返すと、そもそもツイートが表示されなくなる。
- modifier: function (msg, tab, streamName) {
- return [msg, tab, streamName];
- }
- };
-
- // 日本語判定
- JP = new RegExp("[\\u4e00-\\u9fa0\\u30A1-\\u30F6\\u30FC\\u3042-\\u3093\\u3001\\u3002\\uFF01\\uFF1F]");
-
- /*********************************************************************************
- * Main
- *********************************************************************************/
-
- // util {{{
-
- function className (n)
- ('tw-anekos-sb-plugin-' + n);
-
- function px (n)
- parseInt(n, 10) + 'px';
-
- // }}}
-
- function formatText (str) { // {{{
- str = str.trim();
- let reg = /https?:\/\/[^\s]+|[#@]\w+/g;
- XML.ignoreWhitespace = false;
- let m, i = 0, buf = "", x = <xhtml:p class="twlist-text" xmlns:xhtml="http://www.w3.org/1999/xhtml"/>;
- while((m=reg.exec(str))){
- buf = str.substring(i, m.index);
- if (buf)
- x.appendChild(buf);
- let klass = "twlist-link", href = "";
- switch (m[0].charAt(0)){
- case "@":
- klass += " twlist-user";
- href = "http://twitter.com/" + m[0].substr(1);
- break;
- case "#":
- klass += " twlist-hash";
- href = "http://twitter.com/search?q=%23" + m[0].substr(1);
- break;
- default:
- klass += " twlist-url";
- href = m[0];
- }
- x.appendChild(<xhtml:a class={klass} href={href}
- onclick="twlist.onClick(event)" xmlns:xhtml={XHTML}>{m[0]}</xhtml:a>);
- i=reg.lastIndex;
- }
- buf = str.substr(i);
- if (buf)
- x.appendChild(buf);
- return x;
- } // }}}
-
- function escapeBreakers (text) // {{{
- text.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]+/g, function(c) uneval(c)); // }}}
-
- function getSidebarWindow ()
- document.getElementById('sidebar')._contentWindow;
-
- let appendTweet = (function () { // {{{
- function messageToXML (t) {
- XML.prettyPrinting = true;
- XML.ignoreWhitespace = true;
- let xml;
- let sbWidth = getSidebarWindow().document.width;
- xml =
- <richlistitem
- class={[className('tweet-panel'), className('tweet-' + t.type)].join(' ')}
- style={[
- "font-size: " + px(Config.fontSize - (t.text.length > 70 ? 2 : 0)),
- "width: " + px(sbWidth - 100) + ' !important'
- ].join(';')}
- xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
- <hbox>
- <vbox>
- <image src={t.img} height="48" width="48" />
- </vbox>
- <vbox style={"width: " + (sbWidth - 48 - 35) + "px !important"}>
- <hbox>
- <label style="font-weight: bold">{escapeBreakers(t.name)}</label>
- <spacer flex="1"/>
- <label>{t.sub || ''}</label>
- </hbox>
- <description width="100%">{escapeBreakers(t.text)}</description>
- </vbox>
- </hbox>
- </richlistitem>;
- return xml;
- }
-
- function xmlToDom(xml, xmlns) {
- XML.prettyPrinting = true;
- XML.ignoreWhitespace = true;
- var doc = (new DOMParser).parseFromString(
- '<root xmlns="' + xmlns + '">' + xml.toXMLString() + "</root>",
- "application/xml");
- var imported = document.importNode(doc.documentElement, true);
- var range = document.createRange();
- range.selectNodeContents(imported);
- var fragment = range.extractContents();
- range.detach();
- return fragment.childNodes.length > 1 ? fragment : fragment.firstChild;
- }
-
- let latest = {};
- let latestNode = null;
-
- function append (t, tab, streamName) {
- tab = tab || 'home';
-
- let modified = Config.modifier && Config.modifier(t, tab, streamName);
- if (modified) {
- if (modified instanceof Array) {
- [t, tab, streamName] = modified;
- } if (modified === 'reject') {
- return;
- } else {
- t = modified;
- }
- }
-
- let now = JSON.stringify({name: t.name, text: t.text, tab: tab});
- if (latest === now) {
- if (latestNode)
- latestNode.setAttribute(
- 'class',
- latestNode.getAttributeNode('class') + className('tweet-' + t.type)
- );
- return;
- }
- latest = now;
-
- let cntr = getSidebarWindow().document.getElementById('tw-anekos-sb-' + tab + '-list');
- let dom = xmlToDom(messageToXML(t));
- let repDom = dom.cloneNode(true);
- let visibleIndex = cntr.getIndexOfFirstVisibleRow();
- let len = cntr.itemCount;
- if (Config.listAscendingOrder) {
- cntr.appendChild(repDom);
- } else {
- cntr.insertBefore(repDom, cntr.firstChild);
- visibleIndex += 1;
- }
- latestNode = repDom;
-
- if (len > Config.listMax) {
- if (Config.listAscendingOrder) {
- cntr.removeChild(cntr.firstChild);
- visibleIndex -= 1;
- } else {
- cntr.removeChild(cntr.lastChild);
- }
- len -= 1;
- }
-
- if (Config.listAutoScroll) {
- if (Config.listAscendingOrder) {
- cntr.scrollToIndex(len - 1);
- } else {
- cntr.scrollToIndex(0);
- }
- } else {
- if (Config.listAscendingOrder) {
- if (len - visibleIndex < 10) { // 10 = 絶妙な値!これでいいのか!
- cntr.scrollToIndex(len - 1);
- } else {
- cntr.scrollToIndex(visibleIndex);
- }
- } else {
- if (visibleIndex < 3) { // 3 = 絶妙な値!これでいいのか!
- cntr.scrollToIndex(0);
- } else {
- cntr.scrollToIndex(visibleIndex);
- }
- }
- }
- }
-
- return append;
- })(); // }}}
-
- function objectToString (obj, head) { // {{{
- if (!head)
- head = '';
-
- let nextHead = head + ' ';
-
- let result = '';
- for (let [n, v] in Iterator(obj)) {
- let vstr =
- (v && typeof v === 'object') ? objectToString(v, nextHead)
- : (v || '').toString();
- result += head + n + ':\n' + vstr.split(/\n/).map(function(s) nextHead + s).join('\n') + '\n';
- }
-
- return result.trim();
- } // }}}
-
- function onMsg (real, msg, raw, streamName) { // {{{
- if (real) {
- Tweets.unshift(msg);
- if (Tweets.length > Config.listMax)
- Tweets.splice(Config.listMax);
- }
-
- if (sidebarClosed)
- return;
-
- let screenName = Config.screenName;
- let my = (msg.retweeted_status && msg.retweeted_status.user.screen_name === screenName)
- ||
- (msg.target_object && msg.event && (
- (msg.event === 'favorite' && msg.target_object.user.screen_name == screenName)
- ||
- (msg.event === 'list_member_added' && msg.target.screen_name == screenName)
- ))
- ||
- (msg.user && msg.text && Config.myNames.test(msg.text))
- ||
- (msg.user && msg.text && msg.in_reply_to_screen_name == screenName)
- ||
- (msg.direct_message);
-
- // Fav test
- try {
- //liberator.log(JSON.stringify(msg, null, 2));
- if (msg.event && msg.event === 'favorite' && msg.source && msg.source.screen_name === screenName) {
- let t = {
- name: '>' + msg.target_object.user.screen_name + '<',
- img: msg.target_object.user.profile_image_url,
- text: msg.target_object.text,
- type: 'favorite'
- };
- appendTweet(t, 'home', streamName);
- }
- } catch (e) {
- liberator.log(e);
- }
-
- // Ignore not JP
- if (!my && streamName === 'filter' && msg.text && Config.jpOnly && !JP.test(msg.text)) {
- return;
- }
-
- if (msg.text && msg.user && msg.user && msg.user.screen_name === screenName)
- my = false;
-
- let t, dummy;
-
- if (msg.direct_message) {
- t = {
- name: msg.direct_message.sender.screen_name,
- img: msg.direct_message.sender.profile_image_url,
- text: msg.direct_message.text,
- sub: 'DM',
- type: 'DM'
- };
- } else if (msg.retweeted_status) {
- t = {
- name: my ? msg.user.screen_name : msg.retweeted_status.user.screen_name,
- img: my ? msg.user.profile_image_url : msg.retweeted_status.user.profile_image_url,
- text: msg.retweeted_status.text,
- sub: '\u21BB ' + msg.user.screen_name,
- type: 'retweet'
- };
- dummy = true;
- } else if (my && msg.target && msg.event) {
- if (msg.event === 'favorite' && msg.target_object && !msg.target_object.retweeted_status) {
- t = {
- name: msg.source.screen_name,
- img: msg.source.profile_image_url,
- text: msg.target_object.text,
- type: 'favorite',
- sub: 'fav'
- };
- dummy = true;
- } else if (msg.event === 'list_member_added' && msg.target) {
- // 結構漏れがある?
- t = {
- name: msg.source.screen_name,
- img: msg.source.profile_image_url,
- text:
- '\u3042\u306A\u305F\u3092\u30EA\u30B9\u30C8\u300C' +
- msg.target_object.name +
- '\u300D\u306B\u8FFD\u52A0\u3057\u307E\u3057\u305F\u3002\n' +
- 'http://twitter.com' + msg.target_object.uri,
- type: 'list-member-added',
- sub: 'listed'
- };
- dummy = true;
- }
- } else if (msg.event === 'follow' && msg.target && msg.source) {
- t = {
- name: msg.source.screen_name,
- img: msg.source.profile_image_url,
- text: 'follow ' + msg.target.screen_name,
- type: 'follow'
- };
- my = msg.target.screen_name === screenName;
- dummy = true;
- } else if (msg.user && msg.text && msg.in_reply_to_screen_name == screenName) {
- t = {
- name: msg.user.screen_name,
- img: msg.user.profile_image_url,
- text: msg.text,
- type: 'reply'
- };
- } else if (msg.user && msg.text) {
- t = {
- name: msg.user.screen_name,
- img: msg.user.profile_image_url,
- text: msg.text,
- type: 'normal'
- };
- }
-
- if (t) {
- if (Config.earthquake && /\u5730\u9707/.test(t.text) && msg.text.length < 20 && msg.user && msg.user.location) {
- t.text += ' [\u5730\u57DF: ' + msg.user.location + ']';
- }
-
- if (!t.sub && msg.created_at) {
- t.sub = new Date(msg.created_at).toLocaleTimeString().replace(/:\d+$/,'');;
- }
-
- if (real && dummy) {
- if (typeof dummy != 'object') {
- dummy = {
- user: {
- screen_name: t.name || '',
- profile_image_url: t.img
- },
- text: '[' + t.type + '] ' + t.text + ' - http://twitter.com/' + t.name
- };
- }
- plugins.twittperator.Twittperator.onMessage(dummy);
- }
-
- if (my || !Config.vanish.test([t.name, t.text, t.sub].join(' '))) {
- if (my) {
- if (real) {
- let sound = Config.sound[t.type] || Config.sound.fanfare;
- sound.play();
- }
- t.type += '-my';
- } else {
- if (t.type === 'normal' && Config.keyword.test(t.text))
- t.type = 'keyword';
- }
-
- if (streamName === 'filter') {
- if (!msg.retweeted_status) {
- t.type = 'filter';
- appendTweet(t, 'home', streamName);
- appendTweet(t, 'filter', streamName);
- let (s = Config.sound.filter) (s && s.play());
- }
- } else if (/^(keyword)$/.test(t.type)) {
- appendTweet(t, 'home', streamName);
- appendTweet(t, t.type, streamName);
- } else if (my) {
- appendTweet(t, 'home', streamName);
- appendTweet(t, 'my', streamName);
- } else {
- appendTweet(t, 'home', streamName);
- }
- }
- }
-
- if (real) {
- let s =
- '----------------------------------------\n' +
- objectToString(msg).replace(/\x0D\x0A|\x0D|\x0A/g, '\n');
- if (Config.logFile)
- Config.logFile.write(s, '>>');
- if (my && Config.myLogFile)
- Config.myLogFile.write(s, '>>');
- }
- } // }}}
-
- function makeOnMsg (real, streamName) // {{{
- function (msg, raw)
- onMsg(real, msg, raw, streamName); // }}}
-
- function addCommands () { // {{{
- commands.addUserCommand(
- ['tws[idebar'],
- 'nosidebar commands',
- function (args) {
- },
- {
- subCommands: [
- new Command(
- ['v[anish]'],
- 'Vanish matched tweets',
- function (args) {
- Config.vanish = new RegExp(args.literalArg, 'i');
- },
- {
- literal: 0,
- completer: function (context, args) {
- context.completions = [
- [util.escapeRegex(Config.vanish.source), '']
- ];
- }
- }
- ),
- new Command(
- ['k[eyword]'],
- 'Show matched tweets in keyword tab',
- function (args) {
- Config.keyword = new RegExp(args.literalArg, 'i');
- },
- {
- literal: 0,
- completer: function (context, args) {
- context.completions = [
- [util.escapeRegex(Config.keyword.source), '']
- ];
- }
- }
- ),
- new Command(
- ['j[ponly]'],
- 'Show only Japanese Tweet',
- function (args) {
- Config.jpOnly = /yes/i.test(args.literalArg);
- },
- {
- literal: 0,
- completer: function (context, args) {
- context.completions = [
- ['yes', 'yes'],
- ['no', 'no']
- ];
- }
- }
- ),
- new Command(
- ['t[ab]'],
- 'select tab',
- function (args) {
- let tabbox = getSidebarWindow().document.getElementById('tw-anekos-sb-tabbox');
- let index = parseInt(args.literalArg, 10);
- tabbox.selectedIndex = index;
- },
- {
- literal: 0,
- completer: function (context, args) {
- let tabs = getSidebarWindow().document.getElementById('tw-anekos-sb-tabbox').querySelectorAll('tab');
- context.completions = [
- [i + ': ' + tab.getAttribute('label'), tab.getAttribute('label')]
- for ([i, tab] in Iterator(Array.slice(tabs)))
- ];
- }
- }
- )
- ]
- },
- true
- );
- } // }}}
-
- /*********************************************************************************
- * Install
- *********************************************************************************/
-
- let Store = storage.newMap("twittperator-anekos-sb", {store: true});
- let started = false;
- let readyToStart = false;
- let sidebarClosed = true;
-
- let Tweets = __context__.Tweets;
- if (!Tweets)
- Tweets = __context__.Tweets = Store.get("history", []);
-
- let added = {};
-
- function start (isOpen, silent) { // {{{
- function restore () {
- Array.slice(Tweets).reverse().forEach(makeOnMsg(false));
- }
-
- if (silent && (started || readyToStart))
- return;
-
- if (isOpen && sidebarClosed) {
- sidebarClosed = false;
- if (started) {
- restore();
- return;
- }
- }
-
- if (readyToStart)
- return;
- if (started)
- stop();
-
- readyToStart = true;
- started = true;
- setTimeout(
- function () {
- readyToStart = false;
- restore();
- plugins.twittperator.ChirpUserStream.addListener(added.chirp = makeOnMsg(true, 'chirp'));
- plugins.twittperator.TrackingStream.addListener(added.filter = makeOnMsg(true, 'filter'));
- },
- 1000
- );
- } // }}}
-
- function stop (isClose) { // {{{
- if (!started)
- return liberator.echoerr('TWAnekoSB has not been started!');
-
- if (isClose && Config.dontStop) {
- sidebarClosed = true;
- return;
- }
-
- plugins.twittperator.ChirpUserStream.removeListener(added.chirp);
- plugins.twittperator.TrackingStream.removeListener(added.filter);
- Store.set("history", Tweets);
- } // }}}
-
- function makeAudio (path, volume) { // {{{
- let audio = new Audio(path);
- // XXX 効いてない
- if (volume)
- audio.volume = volume;
- return audio;
- } // }}}
-
- __context__.onUnload = function() { stop(); };
-
- addCommands();
-
- if (Config.silentStart)
- start(false, true);
-
- return {start: start, stop: stop};
-
-})();
+liberator.modules.TWAnekoSB = ANekoSB = (function () { + + /********************************************************************************* + * Config + *********************************************************************************/ + + let Config = liberator.globalVariables.twittperator_sidebar_config || { + // for Keyword タグ + keyword: /neko|vimp|cat|猫/i, + + // ツイート内に含まれると、表示上抹殺される (reply とか除く + vanish: /うぎぃいいい/i, + + // 自分のスクリーンネーム + screenName: 'anekos', + + // 自分のその他の名前 + myNames: /anekos|悪魔猫将軍/i, + + // ログファイル てけとーなフォーマットで保存されます + //logFile: io.File('~/.chirpstream'), + //myLogFile: io.File('~/.mychirpstream'), + + // 各イベント時に音がなる + sound: { + meow: makeAudio('file:///home/anekos/sound/my/meow.wav'), + fanfare: makeAudio('file://C:/sound-data/fanfare.wav', 0.5), + retweet: makeAudio('file:///home/anekos/sound/my/meow.wav', 0.8), + favorite: makeAudio('file:///home/anekos/sound/my/meow.wav', 0.6), + reply: makeAudio('file:///home/anekos/sound/my/meow.wav', 1.0), + debug: makeAudio('file:///home/anekos/sound/my/meow.wav', 1.0), + filter: makeAudio('file:///home/anekos/sound/my/meow.wav', 1.0), + }, + + // 文字のサイズ + fontSize: 15, + + // リストの最大保持数 + listMax: 100, + + // リストの表示順(昇順/降順) + listAscendingOrder: true, + + // ツイートされる度に最新ツイート位置までスクロールする + listAutoScroll: true, + + // 日本語だけ for filter stream + jpOnly: true, + + // 地震ツイートの本文に場所をくっつける + earthquake: true, + + // サイドバーを閉じても機能を停止しない + dontStop: true, + + // サイドバーが閉じていても、こっそり開始しておく + silentStart: false, + + // 配列かオブジェクトを返すと、変更できる。 + // 文字列 "reject" を返すと、そもそもツイートが表示されなくなる。 + modifier: function (msg, tab, streamName) { + return [msg, tab, streamName]; + } + }; + + // 日本語判定 + JP = new RegExp("[\\u4e00-\\u9fa0\\u30A1-\\u30F6\\u30FC\\u3042-\\u3093\\u3001\\u3002\\uFF01\\uFF1F]"); + + /********************************************************************************* + * Main + *********************************************************************************/ + + // util {{{ + + function className (n) + ('tw-anekos-sb-plugin-' + n); + + function px (n) + parseInt(n, 10) + 'px'; + + // }}} + + function formatText (str) { // {{{ + str = str.trim(); + let reg = /https?:\/\/[^\s]+|[#@]\w+/g; + XML.ignoreWhitespace = false; + let m, i = 0, buf = "", x = <xhtml:p class="twlist-text" xmlns:xhtml="http://www.w3.org/1999/xhtml"/>; + while((m=reg.exec(str))){ + buf = str.substring(i, m.index); + if (buf) + x.appendChild(buf); + let klass = "twlist-link", href = ""; + switch (m[0].charAt(0)){ + case "@": + klass += " twlist-user"; + href = "http://twitter.com/" + m[0].substr(1); + break; + case "#": + klass += " twlist-hash"; + href = "http://twitter.com/search?q=%23" + m[0].substr(1); + break; + default: + klass += " twlist-url"; + href = m[0]; + } + x.appendChild(<xhtml:a class={klass} href={href} + onclick="twlist.onClick(event)" xmlns:xhtml={XHTML}>{m[0]}</xhtml:a>); + i=reg.lastIndex; + } + buf = str.substr(i); + if (buf) + x.appendChild(buf); + return x; + } // }}} + + function escapeBreakers (text) // {{{ + text.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]+/g, function(c) uneval(c)); // }}} + + function getSidebarWindow () + document.getElementById('sidebar')._contentWindow; + + let appendTweet = (function () { // {{{ + function messageToXML (t) { + XML.prettyPrinting = true; + XML.ignoreWhitespace = true; + let xml; + let sbWidth = getSidebarWindow().document.width; + xml = + <richlistitem + class={[className('tweet-panel'), className('tweet-' + t.type)].join(' ')} + style={[ + "font-size: " + px(Config.fontSize - (t.text.length > 70 ? 2 : 0)), + "width: " + px(sbWidth - 100) + ' !important' + ].join(';')} + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <hbox> + <vbox> + <image src={t.img} height="48" width="48" /> + </vbox> + <vbox style={"width: " + (sbWidth - 48 - 35) + "px !important"}> + <hbox> + <label style="font-weight: bold">{escapeBreakers(t.name)}</label> + <spacer flex="1"/> + <label>{t.sub || ''}</label> + </hbox> + <description width="100%">{escapeBreakers(t.text)}</description> + </vbox> + </hbox> + </richlistitem>; + return xml; + } + + function xmlToDom(xml, xmlns) { + XML.prettyPrinting = true; + XML.ignoreWhitespace = true; + var doc = (new DOMParser).parseFromString( + '<root xmlns="' + xmlns + '">' + xml.toXMLString() + "</root>", + "application/xml"); + var imported = document.importNode(doc.documentElement, true); + var range = document.createRange(); + range.selectNodeContents(imported); + var fragment = range.extractContents(); + range.detach(); + return fragment.childNodes.length > 1 ? fragment : fragment.firstChild; + } + + let latest = {}; + let latestNode = null; + + function append (t, tab, streamName) { + tab = tab || 'home'; + + let modified = Config.modifier && Config.modifier(t, tab, streamName); + if (modified) { + if (modified instanceof Array) { + [t, tab, streamName] = modified; + } if (modified === 'reject') { + return; + } else { + t = modified; + } + } + + let now = JSON.stringify({name: t.name, text: t.text, tab: tab}); + if (latest === now) { + if (latestNode) + latestNode.setAttribute( + 'class', + latestNode.getAttributeNode('class') + className('tweet-' + t.type) + ); + return; + } + latest = now; + + let cntr = getSidebarWindow().document.getElementById('tw-anekos-sb-' + tab + '-list'); + let dom = xmlToDom(messageToXML(t)); + let repDom = dom.cloneNode(true); + let visibleIndex = cntr.getIndexOfFirstVisibleRow(); + let len = cntr.itemCount; + if (Config.listAscendingOrder) { + cntr.appendChild(repDom); + } else { + cntr.insertBefore(repDom, cntr.firstChild); + visibleIndex += 1; + } + latestNode = repDom; + + if (len > Config.listMax) { + if (Config.listAscendingOrder) { + cntr.removeChild(cntr.firstChild); + visibleIndex -= 1; + } else { + cntr.removeChild(cntr.lastChild); + } + len -= 1; + } + + if (Config.listAutoScroll) { + if (Config.listAscendingOrder) { + cntr.scrollToIndex(len - 1); + } else { + cntr.scrollToIndex(0); + } + } else { + if (Config.listAscendingOrder) { + if (len - visibleIndex < 10) { // 10 = 絶妙な値!これでいいのか! + cntr.scrollToIndex(len - 1); + } else { + cntr.scrollToIndex(visibleIndex); + } + } else { + if (visibleIndex < 3) { // 3 = 絶妙な値!これでいいのか! + cntr.scrollToIndex(0); + } else { + cntr.scrollToIndex(visibleIndex); + } + } + } + } + + return append; + })(); // }}} + + function objectToString (obj, head) { // {{{ + if (!head) + head = ''; + + let nextHead = head + ' '; + + let result = ''; + for (let [n, v] in Iterator(obj)) { + let vstr = + (v && typeof v === 'object') ? objectToString(v, nextHead) + : (v || '').toString(); + result += head + n + ':\n' + vstr.split(/\n/).map(function(s) nextHead + s).join('\n') + '\n'; + } + + return result.trim(); + } // }}} + + function onMsg (real, msg, raw, streamName) { // {{{ + if (real) { + Tweets.unshift(msg); + if (Tweets.length > Config.listMax) + Tweets.splice(Config.listMax); + } + + if (sidebarClosed) + return; + + let screenName = Config.screenName; + let my = (msg.retweeted_status && msg.retweeted_status.user.screen_name === screenName) + || + (msg.target_object && msg.event && ( + (msg.event === 'favorite' && msg.target_object.user.screen_name == screenName) + || + (msg.event === 'list_member_added' && msg.target.screen_name == screenName) + )) + || + (msg.user && msg.text && Config.myNames.test(msg.text)) + || + (msg.user && msg.text && msg.in_reply_to_screen_name == screenName) + || + (msg.direct_message); + + // Fav test + try { + //liberator.log(JSON.stringify(msg, null, 2)); + if (msg.event && msg.event === 'favorite' && msg.source && msg.source.screen_name === screenName) { + let t = { + name: '>' + msg.target_object.user.screen_name + '<', + img: msg.target_object.user.profile_image_url, + text: msg.target_object.text, + type: 'favorite' + }; + appendTweet(t, 'home', streamName); + } + } catch (e) { + liberator.log(e); + } + + // Ignore not JP + if (!my && streamName === 'filter' && msg.text && Config.jpOnly && !JP.test(msg.text)) { + return; + } + + if (msg.text && msg.user && msg.user && msg.user.screen_name === screenName) + my = false; + + let t, dummy; + + if (msg.direct_message) { + t = { + name: msg.direct_message.sender.screen_name, + img: msg.direct_message.sender.profile_image_url, + text: msg.direct_message.text, + sub: 'DM', + type: 'DM' + }; + } else if (msg.retweeted_status) { + t = { + name: my ? msg.user.screen_name : msg.retweeted_status.user.screen_name, + img: my ? msg.user.profile_image_url : msg.retweeted_status.user.profile_image_url, + text: msg.retweeted_status.text, + sub: '\u21BB ' + msg.user.screen_name, + type: 'retweet' + }; + dummy = true; + } else if (my && msg.target && msg.event) { + if (msg.event === 'favorite' && msg.target_object && !msg.target_object.retweeted_status) { + t = { + name: msg.source.screen_name, + img: msg.source.profile_image_url, + text: msg.target_object.text, + type: 'favorite', + sub: 'fav' + }; + dummy = true; + } else if (msg.event === 'list_member_added' && msg.target) { + // 結構漏れがある? + t = { + name: msg.source.screen_name, + img: msg.source.profile_image_url, + text: + '\u3042\u306A\u305F\u3092\u30EA\u30B9\u30C8\u300C' + + msg.target_object.name + + '\u300D\u306B\u8FFD\u52A0\u3057\u307E\u3057\u305F\u3002\n' + + 'http://twitter.com' + msg.target_object.uri, + type: 'list-member-added', + sub: 'listed' + }; + dummy = true; + } + } else if (msg.event === 'follow' && msg.target && msg.source) { + t = { + name: msg.source.screen_name, + img: msg.source.profile_image_url, + text: 'follow ' + msg.target.screen_name, + type: 'follow' + }; + my = msg.target.screen_name === screenName; + dummy = true; + } else if (msg.user && msg.text && msg.in_reply_to_screen_name == screenName) { + t = { + name: msg.user.screen_name, + img: msg.user.profile_image_url, + text: msg.text, + type: 'reply' + }; + } else if (msg.user && msg.text) { + t = { + name: msg.user.screen_name, + img: msg.user.profile_image_url, + text: msg.text, + type: 'normal' + }; + } + + if (t) { + if (Config.earthquake && /\u5730\u9707/.test(t.text) && msg.text.length < 20 && msg.user && msg.user.location) { + t.text += ' [\u5730\u57DF: ' + msg.user.location + ']'; + } + + if (!t.sub && msg.created_at) { + t.sub = new Date(msg.created_at).toLocaleTimeString().replace(/:\d+$/,'');; + } + + if (real && dummy) { + if (typeof dummy != 'object') { + dummy = { + user: { + screen_name: t.name || '', + profile_image_url: t.img + }, + text: '[' + t.type + '] ' + t.text + ' - http://twitter.com/' + t.name + }; + } + plugins.twittperator.Twittperator.onMessage(dummy); + } + + if (my || !Config.vanish.test([t.name, t.text, t.sub].join(' '))) { + if (my) { + if (real) { + let sound = Config.sound[t.type] || Config.sound.fanfare; + sound.play(); + } + t.type += '-my'; + } else { + if (t.type === 'normal' && Config.keyword.test(t.text)) + t.type = 'keyword'; + } + + if (streamName === 'filter') { + if (!msg.retweeted_status) { + t.type = 'filter'; + appendTweet(t, 'home', streamName); + appendTweet(t, 'filter', streamName); + let (s = Config.sound.filter) (s && s.play()); + } + } else if (/^(keyword)$/.test(t.type)) { + appendTweet(t, 'home', streamName); + appendTweet(t, t.type, streamName); + } else if (my) { + appendTweet(t, 'home', streamName); + appendTweet(t, 'my', streamName); + } else { + appendTweet(t, 'home', streamName); + } + } + } + + if (real) { + let s = + '----------------------------------------\n' + + objectToString(msg).replace(/\x0D\x0A|\x0D|\x0A/g, '\n'); + if (Config.logFile) + Config.logFile.write(s, '>>'); + if (my && Config.myLogFile) + Config.myLogFile.write(s, '>>'); + } + } // }}} + + function makeOnMsg (real, streamName) // {{{ + function (msg, raw) + onMsg(real, msg, raw, streamName); // }}} + + function addCommands () { // {{{ + commands.addUserCommand( + ['tws[idebar'], + 'nosidebar commands', + function (args) { + }, + { + subCommands: [ + new Command( + ['v[anish]'], + 'Vanish matched tweets', + function (args) { + Config.vanish = new RegExp(args.literalArg, 'i'); + }, + { + literal: 0, + completer: function (context, args) { + context.completions = [ + [util.escapeRegex(Config.vanish.source), ''] + ]; + } + } + ), + new Command( + ['k[eyword]'], + 'Show matched tweets in keyword tab', + function (args) { + Config.keyword = new RegExp(args.literalArg, 'i'); + }, + { + literal: 0, + completer: function (context, args) { + context.completions = [ + [util.escapeRegex(Config.keyword.source), ''] + ]; + } + } + ), + new Command( + ['j[ponly]'], + 'Show only Japanese Tweet', + function (args) { + Config.jpOnly = /yes/i.test(args.literalArg); + }, + { + literal: 0, + completer: function (context, args) { + context.completions = [ + ['yes', 'yes'], + ['no', 'no'] + ]; + } + } + ), + new Command( + ['t[ab]'], + 'select tab', + function (args) { + let tabbox = getSidebarWindow().document.getElementById('tw-anekos-sb-tabbox'); + let index = parseInt(args.literalArg, 10); + tabbox.selectedIndex = index; + }, + { + literal: 0, + completer: function (context, args) { + let tabs = getSidebarWindow().document.getElementById('tw-anekos-sb-tabbox').querySelectorAll('tab'); + context.completions = [ + [i + ': ' + tab.getAttribute('label'), tab.getAttribute('label')] + for ([i, tab] in Iterator(Array.slice(tabs))) + ]; + } + } + ) + ] + }, + true + ); + } // }}} + + /********************************************************************************* + * Install + *********************************************************************************/ + + let Store = storage.newMap("twittperator-anekos-sb", {store: true}); + let started = false; + let readyToStart = false; + let sidebarClosed = true; + + let Tweets = __context__.Tweets; + if (!Tweets) + Tweets = __context__.Tweets = Store.get("history", []); + + let added = {}; + + function start (isOpen, silent) { // {{{ + function restore () { + Array.slice(Tweets).reverse().forEach(makeOnMsg(false)); + } + + if (silent && (started || readyToStart)) + return; + + if (isOpen && sidebarClosed) { + sidebarClosed = false; + if (started) { + restore(); + return; + } + } + + if (readyToStart) + return; + if (started) + stop(); + + readyToStart = true; + started = true; + setTimeout( + function () { + readyToStart = false; + restore(); + plugins.twittperator.ChirpUserStream.addListener(added.chirp = makeOnMsg(true, 'chirp')); + plugins.twittperator.TrackingStream.addListener(added.filter = makeOnMsg(true, 'filter')); + }, + 1000 + ); + } // }}} + + function stop (isClose) { // {{{ + if (!started) + return liberator.echoerr('TWAnekoSB has not been started!'); + + if (isClose && Config.dontStop) { + sidebarClosed = true; + return; + } + + plugins.twittperator.ChirpUserStream.removeListener(added.chirp); + plugins.twittperator.TrackingStream.removeListener(added.filter); + Store.set("history", Tweets); + } // }}} + + function makeAudio (path, volume) { // {{{ + let audio = new Audio(path); + // XXX 効いてない + if (volume) + audio.volume = volume; + return audio; + } // }}} + + __context__.onUnload = function() { stop(); }; + + addCommands(); + + if (Config.silentStart) + start(false, true); + + return {start: start, stop: stop}; + +})(); |