Skip to content

Instantly share code, notes, and snippets.

@xulapp
Created December 4, 2010 04:07
Show Gist options
  • Select an option

  • Save xulapp/727909 to your computer and use it in GitHub Desktop.

Select an option

Save xulapp/727909 to your computer and use it in GitHub Desktop.
p2Watcher.uc.js
// ==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&amp;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