Created
December 4, 2010 04:07
-
-
Save xulapp/727909 to your computer and use it in GitHub Desktop.
p2Watcher.uc.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name p2Watcher.uc.js | |
| // @description | |
| // @include main | |
| // @charset utf-8 | |
| // @compatibility Firefox | |
| // @namespace http://twitter.com/xulapp | |
| // @author xulapp | |
| // @license MIT License | |
| // @version 2011/02/19 00:50 +09:00 | |
| // ==/UserScript== | |
| // @version 2010/12/04 13:00 +09:00 | |
| // @version 2010/06/16 14:10 +09:00 | |
| // @version 2009/07/16 03:10 +09:00 | |
| let P2Watcher = (function P2W_main() { | |
| const {classes: Cc, interfaces: Ci, utils: Cu} = Components; | |
| const BASE_URI = newURI('http://p2.2ch.net/p2/'); | |
| const MADAKANA_URI = 'http://qb7.2ch.net/_403/madakana.cgi'; | |
| const [ | |
| NORMAL_ICON_URI, | |
| NOTIFY_ICON_URI, | |
| ERROR_ICON_URI | |
| ] = let ( | |
| prefix = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVE', | |
| suffix = 'AAAAaElEQVR4nGNgQAGs6VCGklICRMDBUZnBxaGUgZmBTZmBQ9WxmYGBUYCBUZiljYHB0oCBUZlZjIFBBSgozKzGwA5UxqgAZCw1dmVglGKfwtKoNJuBUTzIgTE0NAGkEQIYlaEMJzFU+xkAKKQLMcNzWEkAAAAASUVORK5CYII=' | |
| ) [ | |
| prefix + 'W+vr5kZGRZWVmvr6+mpqZ4eHtzc3OFhYVMTExAQECYmJihoaGjIbDW' + suffix, | |
| prefix + 'X+/v6GhoZ3d3fq6urd3d2goKSZmZmysrJmZmZVVVXLy8vX19djwr1s' + suffix, | |
| prefix + 'X+vr6kZGSZWVnvr6/mpqa4eHuzc3PFhYWMTEyAQEDYmJjhoaEbNh/6' + suffix | |
| ]; | |
| var subjects = []; | |
| var timer; | |
| registerStyle(<><![CDATA[ | |
| @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); | |
| @-moz-document url("chrome://browser/content/browser.xul") { | |
| #p2watcher-status .statusbarpanel-icon { | |
| list-style-image: url("]]>{ NORMAL_ICON_URI }<![CDATA["); | |
| } | |
| #p2watcher-status[label]:not([label="0"]) .statusbarpanel-icon { | |
| list-style-image: url("]]>{ NOTIFY_ICON_URI }<![CDATA["); | |
| } | |
| #p2watcher-status[p2w-state="error"] .statusbarpanel-icon { | |
| list-style-image: url("]]>{ ERROR_ICON_URI }<![CDATA["); | |
| } | |
| #p2watcher-status[p2w-busy="true"] .statusbarpanel-icon { | |
| list-style-image: url("chrome://global/skin/icons/loading_16.png") !important; | |
| } | |
| #p2watcher-status:not([label]) .statusbarpanel-text, | |
| #p2watcher-status[label="0"] .statusbarpanel-text { | |
| display: none !important; | |
| } | |
| } | |
| ]]></>); | |
| setCommand( | |
| 'p2w-cmd-openNewRes', | |
| 'P2Watcher.resetTimer(); P2Watcher.openURI("read_new.php?spmode=fav"); P2Watcher.clearSubject(-1);' | |
| ); | |
| setCommand( | |
| 'p2w-cmd-checkForUpdate', | |
| 'P2Watcher.resetTimer(); P2Watcher.checkForUpdate();' | |
| ); | |
| setCommand( | |
| 'p2w-cmd-viewMadakana', | |
| 'P2Watcher.viewMadakana(event);' | |
| ); | |
| var statusbarpanel = $E( | |
| <statusbarpanel | |
| id="p2watcher-status" | |
| class="statusbarpanel-iconic-text" | |
| tooltiptext="p2 watcher" | |
| context="P2W_menuPopup" | |
| oncommand="P2Watcher.doCommand(event.type === 'command' ? 'p2w-cmd-openNewRes' : 'p2w-cmd-checkForUpdate');" | |
| onclick="checkForMiddleClick(this, event);" | |
| ondragover="P2Watcher.handleEvent(event);" | |
| ondrop="P2Watcher.handleEvent(event);" | |
| /> | |
| ); | |
| $('status-bar').appendChild(statusbarpanel); | |
| var menuseparator = $E(<menuseparator hidden="true"/>); | |
| var menupopup = $E( | |
| <menupopup id="P2W_menuPopup"> | |
| <menuitem label={U('お気にスレ')} | |
| accesskey="o" | |
| oncommand="P2Watcher.openURI('subject.php?spmode=fav');"/> | |
| <menuitem label={U('お気にスレ新着')} | |
| accesskey="n" | |
| oncommand="P2Watcher.openURI('subject.php?spmode=fav&sb_view=shinchaku');"/> | |
| <menuitem label={U('最近読んだスレ')} | |
| accesskey="r" | |
| oncommand="P2Watcher.openURI('subject.php?spmode=recent');"/> | |
| <menuitem label={U('設定管理')} | |
| accesskey="p" | |
| oncommand="P2Watcher.openURI('editpref.php');"/> | |
| <menuitem label={U('まだかな')} | |
| accesskey="m" | |
| command="p2w-cmd-viewMadakana" | |
| onclick="checkForMiddleClick(this, event);"/> | |
| <menuseparator/> | |
| <menuitem label={U('新着まとめ読み')} | |
| accesskey="r" | |
| default="true" | |
| command="p2w-cmd-openNewRes"/> | |
| <menuitem label={U('新着を確認')} | |
| accesskey="c" | |
| command="p2w-cmd-checkForUpdate"/> | |
| </menupopup> | |
| ); | |
| menupopup.insertBefore(menuseparator, menupopup.firstChild); | |
| $('mainPopupSet').appendChild(menupopup); | |
| function HTMLLoader() { | |
| } | |
| HTMLLoader.prototype = { | |
| constructor: HTMLLoader, | |
| // charset: 'UTF-8', | |
| timeout: 60 * 1000, | |
| get _req() { | |
| if ('__req' in this) return this.__req; | |
| var req = new XMLHttpRequest(); | |
| req.onreadystatechange = let (self = this) function(event) self._handleLoad(event); | |
| return this.__req = req; | |
| }, | |
| _handleLoad: function _handleLoad(event) { | |
| var req = event.target; | |
| if (req.readyState !== 4) return; | |
| clearTimeout(this._timerId); | |
| if (req.status !== 200) { | |
| try { | |
| this._onerror(); | |
| } catch (e) { | |
| } | |
| return; | |
| } | |
| // var converter = Cc['@mozilla.org/intl/scriptableunicodeconverter'] | |
| // .createInstance(Ci.nsIScriptableUnicodeConverter); | |
| // converter.charset = this.charset; | |
| // var text = converter.ConvertToUnicode(req.responseText); | |
| var doc = parseHTML(req.responseText); | |
| try { | |
| this._onload(doc); | |
| } catch (e) { | |
| } | |
| }, | |
| load: function load(uri, onload, onerror) { | |
| this._timerId = setTimeout(let (self = this) function() self.abort(), this.timeout); | |
| this._uri = newURI(uri); | |
| this._onload = onload; | |
| this._onerror = onerror; | |
| this._req.open('GET', this._uri.spec, true); | |
| this._req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; | |
| this._req.send(null); | |
| }, | |
| abort: function() { | |
| this._req.abort(); | |
| this._onerror(); | |
| }, | |
| }; | |
| setTimeout(function() { | |
| if (!timer) | |
| doCommand('p2w-cmd-checkForUpdate'); | |
| }, 3 * 60 * 1000); | |
| return { | |
| get statusbarpanel() statusbarpanel, | |
| get subjects() subjects, | |
| doCommand: doCommand, | |
| openURI: openURI, | |
| handleEvent: handleEvent, | |
| resetTimer: resetTimer, | |
| clearSubject: clearSubject, | |
| checkForUpdate: checkForUpdate, | |
| viewMadakana: viewMadakana | |
| }; | |
| function U(text) 1 < 'あ'.length ? decodeURIComponent(escape(text)) : text; | |
| function $A(arr) Array.slice(arr); | |
| function $(id) document.getElementById(id); | |
| function $$(selector, context) $A((context || document).querySelectorAll(selector)); | |
| function $E(xml, doc) { | |
| doc = doc || document; | |
| xml = <root xmlns={doc.documentElement.namespaceURI}/>.appendChild(xml); | |
| var settings = XML.settings(); | |
| XML.prettyPrinting = false; | |
| var root = new DOMParser().parseFromString(xml.toXMLString(), 'application/xml').documentElement; | |
| XML.setSettings(settings); | |
| doc.adoptNode(root); | |
| var range = doc.createRange(); | |
| range.selectNodeContents(root); | |
| var frag = range.extractContents(); | |
| range.detach(); | |
| return frag.childNodes.length < 2 ? frag.firstChild : frag; | |
| } | |
| function removeNode(node) { | |
| if (node && node.parentNode) | |
| node.parentNode.removeChild(node); | |
| } | |
| function parseHTML(source) { | |
| // HTMLDocument の動的な作成: Days on the Moon | |
| // http://nanto.asablo.jp/blog/2009/10/29/4660197#htmldoc-moz-cid | |
| const NS_HTMLDOCUMENT_CID = '{5d0fcdd0-4daa-11d2-b328-00805f8a3859}'; | |
| var doc = Components.classesByID[NS_HTMLDOCUMENT_CID].createInstance(); | |
| var html = doc.createElement('html'); | |
| doc.appendChild(html); | |
| var range = doc.createRange(); | |
| range.selectNodeContents(html); | |
| html.appendChild(range.createContextualFragment(source)); | |
| return doc; | |
| } | |
| function newURI(spec, charset, base) { | |
| const ios = Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService); | |
| return ios.newURI(spec, charset || null, base || null); | |
| } | |
| function registerStyle(cssText) { | |
| var uri = newURI('data:text/css;utf-8,' + encodeURIComponent(cssText)); | |
| const sss = Cc['@mozilla.org/content/style-sheet-service;1'].getService(Ci.nsIStyleSheetService); | |
| if (!sss.sheetRegistered(uri, sss.USER_SHEET)) | |
| sss.loadAndRegisterSheet(uri, sss.USER_SHEET); | |
| } | |
| function setCommand(id, cmd) { | |
| return $('mainCommandSet').appendChild($E( | |
| <command id={id} oncommand={cmd}/> | |
| )); | |
| } | |
| function doCommand(id) { | |
| var command = $(id); | |
| if (command) | |
| command.doCommand(); | |
| } | |
| function showNotification() { | |
| var args = $A(arguments); | |
| try { | |
| let alertsService = Cc['@mozilla.org/alerts-service;1'].getService(Ci.nsIAlertsService); | |
| alertsService.showAlertNotification.apply(alertsService, args); | |
| } catch(e) { | |
| if (args[5]) | |
| [args[5], args[6]] = [0, args[5]]; | |
| let ww = Cc['@mozilla.org/embedcomp/window-watcher;1'].getService(Ci.nsIWindowWatcher); | |
| let alertWindow = ww.openWindow(null, 'chrome://global/content/alerts/alert.xul', '_blank', 'chrome,titlebar=no,popup=yes', null); | |
| alertWindow.arguments = args; | |
| } | |
| } | |
| function openURI(uri) { | |
| uri = BASE_URI.resolve(uri); | |
| for (let [, tab] in Iterator(gBrowser.mTabs)) { | |
| if (!tab) continue; | |
| let browser = tab.linkedBrowser; | |
| if (browser.currentURI.spec.indexOf(uri) === 0) { | |
| browser.loadURI(uri); | |
| gBrowser.selectedTab = tab; | |
| return; | |
| } | |
| } | |
| gBrowser.loadOneTab(uri); | |
| } | |
| function _openThread(url) { | |
| openURI('read.php?url=' + encodeURIComponent(url)); | |
| } | |
| function handleEvent(event) { | |
| var trans = event.dataTransfer; | |
| switch (event.type) { | |
| case 'dragover': | |
| if (!trans.types.contains('text/plain')) return; | |
| // if (!/^http:\/\/\w+\.2ch\.net\/test\/read\.\w+\/\w+\/\d{10}\//.test(trans.getData('text/plain'))) return; | |
| if (!/^http:\/\//.test(trans.getData('text/plain'))) return; | |
| break; | |
| case 'drop': | |
| _openThread(trans.getData('text/plain')); | |
| break; | |
| } | |
| event.preventDefault(); | |
| // event.stopPropagation(); | |
| } | |
| function _notifyUpdate() { | |
| _notifyUpdate.cb = _notifyUpdate.cb || { | |
| observe: function(subject, topic, data) { | |
| if (topic === 'alertclickcallback') | |
| doCommand('p2w-cmd-openNewRes'); | |
| }, | |
| }; | |
| var resCount = subjects.reduce(function(total, sbj) total + sbj.countNew, 0); | |
| var msg = subjects.length + U(' スレッド ') + resCount + U(' 件の未読レスがあります'); | |
| showNotification(NOTIFY_ICON_URI, 'p2 Watcher', msg, true, '', _notifyUpdate.cb, 'p2 Watcher Notification'); | |
| } | |
| function _syncMenu() { | |
| $$('menuitem[p2w-menuitem="subject"]', menupopup).forEach(removeNode); | |
| if (!subjects.length) { | |
| menuseparator.setAttribute('hidden', 'true'); | |
| return; | |
| } | |
| menuseparator.removeAttribute('hidden'); | |
| var items = <></>; | |
| var template = <menuitem | |
| tooltiptext="" | |
| p2w-menuitem="subject" | |
| oncommand="P2Watcher.openURI(this.getAttribute('p2w-url')); P2Watcher.clearSubject(this.getAttribute('p2w-index'));" | |
| />; | |
| var frag = document.createDocumentFragment(); | |
| subjects.forEach(function(sbj ,i) { | |
| var item = template.copy(); | |
| item.@['label'] = sbj.countNew + ' [' + sbj.countAll + '] ' + sbj.title; | |
| item.@['p2w-url'] = sbj.url; | |
| item.@['p2w-index'] = i; | |
| items += item; | |
| }); | |
| menupopup.insertBefore($E(items), menuseparator); | |
| } | |
| function _updateState(state) { | |
| if (state) { | |
| statusbarpanel.setAttribute('p2w-state', state); | |
| } else { | |
| statusbarpanel.removeAttribute('p2w-state'); | |
| } | |
| statusbarpanel.setAttribute('label', subjects.reduce(function(total, sbj) total + sbj.countNew, 0)); | |
| } | |
| function clearSubject(index) { | |
| index = +index; | |
| if (index === -1) { | |
| subjects = []; | |
| } else { | |
| subjects.splice(index, 1); | |
| } | |
| _updateState(); | |
| _syncMenu(); | |
| } | |
| function _parse(doc) {try{ | |
| if (!doc) { | |
| _updateState('error'); | |
| return; | |
| } | |
| var lastSubjectsLength = subjects.length; | |
| subjects = []; | |
| var nodes = $$('.un_a, tr:not(.tableheader) > .tn, .tn2, .thre_title', doc); | |
| for (let i = 0, len = nodes.length; i < len; ) { | |
| let countNew = nodes[i++]; | |
| let countAll = nodes[i++]; | |
| let title = nodes[i++]; | |
| subjects.push({ | |
| countNew: +countNew.textContent, | |
| countAll: +countAll.textContent, | |
| title: title.textContent, | |
| url: title.getAttribute('href') | |
| }); | |
| } | |
| _updateState(); | |
| _syncMenu(); | |
| statusbarpanel.removeAttribute('p2w-busy'); | |
| if (!lastSubjectsLength && subjects.length) | |
| _notifyUpdate(); | |
| } catch(e) { Cu.reportError(e); } } | |
| function resetTimer() { | |
| clearInterval(timer); | |
| timer = setInterval(checkForUpdate, 60 * 60 * 1000); | |
| } | |
| function checkForUpdate() { | |
| statusbarpanel.setAttribute('p2w-busy', 'true'); | |
| var url = BASE_URI.resolve('subject.php?spmode=fav&sb_view=shinchaku'); | |
| new HTMLLoader().load(url, _parse, _parse); | |
| } | |
| function _parseMadakana(doc) { | |
| const COMMENT_XPATH = 'normalize-space(preceding-sibling::text()[starts-with(normalize-space(), "#") and normalize-space(preceding-sibling::text()[1])=""][1])'; | |
| var message = []; | |
| if (doc) { | |
| var elements = $$('font[color="red"]', doc); | |
| for (var [, element] in Iterator(elements)) { | |
| var text = doc.evaluate(COMMENT_XPATH, element, null, XPathResult.STRING_TYPE, null).stringValue; | |
| message.push( | |
| text.replace('#', ''), | |
| element.textContent.replace(/_\w+_(\w+)_/, '[$1] ').replace(/\s+/g, ' ').trim() | |
| ); | |
| } | |
| } else { | |
| message.push(U('取得できませんでした')); | |
| } | |
| Cc['@mozilla.org/embedcomp/prompt-service;1'] | |
| .getService(Ci.nsIPromptService) | |
| .alert(null, U('まだかな、まだかな'), message.join('\n')); | |
| } | |
| function viewMadakana(event) { | |
| if (event.button === 1) { | |
| openURI(MADAKANA_URI); | |
| return; | |
| } | |
| var loader = new HTMLLoader(); | |
| loader.charset = 'Shift_JIS'; | |
| loader.load(MADAKANA_URI, _parseMadakana, _parseMadakana); | |
| } | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment