Skip to content

Instantly share code, notes, and snippets.

@azu
Last active December 23, 2015 17:09
Show Gist options
  • Save azu/6666968 to your computer and use it in GitHub Desktop.
Save azu/6666968 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name LDR Full Feed
// @namespace http://d.hatena.ne.jp/Constellation/
// @include http://reader.livedoor.com/reader/*
// @include http://fastladder.com/reader/*
// @description loading full entry on LDR and Fastladder
// @version 0.0.34
// @resource orange https://raw.github.com/Constellation/ldrfullfeed/master/orange.gif
// @resource blue https://raw.github.com/Constellation/ldrfullfeed/master/blue.gif
// @resource css https://raw.github.com/Constellation/ldrfullfeed/master/ldrfullfeed.css
// @author Constellation
// using [ $X + prefix / createHTML ] (c) id:nanto_vi id:os0x
// [ relativeToAbsolutePath ] (c) id:Yuichirou
// [ filter ] copied from LDR-Prefav (c) id:brazil
// [ addStyle ] copied from LDRize (c) id:snj14
// thanks
// ==/UserScript==
// XPath 式中の接頭辞のない名前テストに接頭辞 prefix を追加する
// e.g. '//body[@class = "foo"]/p' -> '//prefix:body[@class = "foo"]/prefix:p'
// http://nanto.asablo.jp/blog/2008/12/11/4003371
function addDefaultPrefix(xpath, prefix) {
var tokenPattern = /([A-Za-z_\u00c0-\ufffd][\w\-.\u00b7-\ufffd]*|\*)\s*(::?|\()?|(".*?"|'.*?'|\d+(?:\.\d*)?|\.(?:\.|\d+)?|[\)\]])|(\/\/?|!=|[<>]=?|[\(\[|,=+-])|([@$])/g;
var TERM = 1, OPERATOR = 2, MODIFIER = 3;
var tokenType = OPERATOR;
prefix += ':';
function replacer(token, identifier, suffix, term, operator, modifier) {
if (suffix) {
tokenType =
(suffix == ':' || (suffix == '::' && (identifier == 'attribute' || identifier == 'namespace')))
? MODIFIER : OPERATOR;
} else if (identifier) {
if (tokenType == OPERATOR && identifier != '*')
token = prefix + token;
tokenType = (tokenType == TERM) ? OPERATOR : TERM;
} else {
tokenType = term ? TERM : operator ? OPERATOR : MODIFIER;
}
return token;
}
return xpath.replace(tokenPattern, replacer);
}
// $X on XHTML
// @target Freifox3, Chrome3, Safari4, Opera10
// @source http://gist.github.com/184276.txt
function $X (exp, context) {
context || (context = document);
var _document = context.ownerDocument || context,
documentElement = _document.documentElement,
isXHTML = documentElement.tagName !== 'HTML' && _document.createElement('p').tagName === 'p',
defaultPrefix = null;
if (isXHTML) {
defaultPrefix = '__default__';
exp = addDefaultPrefix(exp, defaultPrefix);
}
function resolver (prefix) {
return context.lookupNamespaceURI(prefix === defaultPrefix ? null : prefix) ||
documentElement.namespaceURI || "";
}
var result = _document.evaluate(exp, context, resolver, XPathResult.ANY_TYPE, null);
switch (result.resultType) {
case XPathResult.STRING_TYPE : return result.stringValue;
case XPathResult.NUMBER_TYPE : return result.numberValue;
case XPathResult.BOOLEAN_TYPE: return result.booleanValue;
case XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
// not ensure the order.
var ret = [], i = null;
while (i = result.iterateNext()) ret.push(i);
return ret;
}
}
(function(w){
// == [CSS] =========================================================
const CSS = GM_getResourceText('css');
// == [Config] ======================================================
const VERSION = '0.0.34'
const ICON = 'orange' // or blue
const KEY = GM_getValue('key', 'g');
const GET_SITEINFO_KEY = 'G';
const GET_ALL = true;
const GET_ALL_KEY = 'u';
const ADCHECKER = /(^AD:|^PR:)/;
const LOADING_MOTION = true;
const REMOVE_IFRAME = true;
const DISABLE_HATENAKEYWORD = true;
const OPEN = false; //SITEINFOになかった場合にそのエントリを開くかどうか
const ITEMFILTER = true;
const AUTO_SEARCH = true;
const EXTRACT_TEXT = false;
const WIDGET = true;
const CLICKABLE = true;
const USE_AUTOPAGERIZE_SITEINFO = true;
const AUTOPAGER = true;
const XHR_TIMEOUT = 30 * 1000;
//const XHR_TIMEOUT = 15 * 1000;
const DEBUG = false;
const FLASH = false;
// SITEINFO_IMPORT_URLS
// [require ] name => as you like
// [require ] format => JSON or HTML (HTML: LDRFullFeed only)
// [require ] type => AutoPagerize or LDRFullFeed
// [require ] url => SITEINFO URL
// [optional] alternative => alternative SITEINFO URL
const SITEINFO_IMPORT_URLS = [
{
name:'Wedata',
format:'JSON',
type:'LDRFullFeed',
url: 'http://wedata.net/databases/LDRFullFeed/items.json',
alternative: 'http://utatane.heroku.com/siteinfos/LDRFullFeed/items.json'
},
{
name:'Wedata AutoPagerize',
format:'JSON',
type:'AutoPagerize',
url: 'http://wedata.net/databases/AutoPagerize/items.json',
alternative: 'http://utatane.heroku.com/siteinfos/AutoPagerize/items.json'
}
//{format:'HTML', url: 'http://constellation.jottit.com/siteinfo'},
//{format:'HTML', url: 'http://constellation.jottit.com/test'},
];
// == [SITE_INFO] ===================================================
const SITE_INFO = [
];
const MICROFORMATS = [
{
name : 'hAtom-Content',
xpath: '//*[contains(concat(" ",normalize-space(@class)," "), " hentry ")]//*[contains(concat(" ",normalize-space(@class)," "), " entry-content ")]',
},
{
name : 'hAtom',
xpath: '//*[contains(concat(" ",normalize-space(@class)," "), " hentry ")]',
},
{
name : 'xFolk',
xpath: '//*[contains(concat(" ",@class," "), " xfolkentry ")]//*[contains(concat(" ",normalize-space(@class)," "), " description ")]',
},
{
name : 'AutoPagerize(Microformats)',
xpath: '//*[contains(concat(" ",normalize-space(@class)," "), " autopagerize_page_element ")]',
},
]
const AUTOPAGERIZE_MICROFORMAT = {
name: 'autopagerize_microformat',
url: '.*',
reg: /.*/,
nextLink: '//a[@rel="next"] | //link[@rel="next"]',
insertBefore: '//*[contains(@class, "autopagerize_insert_before")]',
pageElement: '//*[contains(@class, "autopagerize_page_element")]',
}
// == [Cache Phase] =================================================
const PHASE = [
{type:'SBM' },
{type:'INDIVIDUAL', sub:'IND' },
{type:'INDIV_MICROFORMATS' },
{type:'SUBGENERAL', sub:'SUB' },
{type:'GENERAL', sub:'GEN' },
{type:'MICROFORMATS', sub:'MIC' }
];
// == [Application] =================================================
// [FullFeed]
var FullFeed = function(info, c){
log(info, c)
this.data = c;
this.info = info;
this.type = 'FullFeed';
this.requestURL = this.data.itemURL.replace(/&amp;/g, '&');
var bodyXPath = 'id("item_body_' + this.data.id + '")/div[@class="body"]';
this.data.item_body = $X(bodyXPath, document)[0];
this.state = 'wait';
this.mime = 'text/html; charset=' + (this.info.enc || document.characterSet);
this.entry = [];
this.request();
};
FullFeed.prototype.request = function(){
if (!this.requestURL) return;
this.state = 'request';
var self = this;
var opt = {
method: 'get',
url: this.requestURL,
overrideMimeType: this.mime,
headers: {
'User-Agent': navigator.userAgent + ' Greasemonkey (LDR Full Feed ' + VERSION + ')',
},
onerror: function(){
self.error.call(self, 'FullFeed Request Error');
},
onload: function(res){
self.load.call(self, res)
},
};
message('Loading '+this.type+' ...');
if (this.data.container.classList.contains('gm_fullfeed_loaded')) {
this.data.container.classList.toggle('gm_fullfeed_loaded');
}
this.data.container.classList.toggle('gm_fullfeed_loading');
window.setTimeout(GM_xmlhttpRequest, 0, opt);
}
function urlX(url) {
if (/^(?:https?:\/\/|\.|\/)/.test(url)) {
return url;
}
}
function idX(id) {
return id;
}
FullFeed.prototype.load = function(res){
this.state = 'loading';
var text = res.responseText;
var self = this;
try {
var htmldoc = createDocumentFromString(text);
removeXSSRisk(htmldoc);
if(res.finalUrl){
this.requestURL = res.finalUrl;
relativeToAbsolutePath(htmldoc, this.requestURL);
} else {
relativeToAbsolutePath(htmldoc, this.requestURL);
}
} catch(e) {
return this.error('HTML Parse Error');
}
//time('FULLFEED: DocumentFilterTime: ');
FullFeed.documentFilters.forEach(function(f) {
f(htmldoc, this.requestURL, this.info);
},this);
//timeEnd('FULLFEED: DocumentFilterTime: ');
this['get'+this.type](htmldoc);
}
FullFeed.prototype.getFullFeed = function(htmldoc){
this.entry = [];
if(this.info.microformats){
log('FULLFEED: Microformats')
this.entry = getElementsByMicroformats(htmldoc)
}
if(this.entry.length === 0){
try{
this.entry = $X(this.info.xpath, htmldoc);
} catch(e) {
return this.error('Something is wrong with this XPath');
}
}
if(USE_AUTOPAGERIZE_SITEINFO || AUTOPAGER)
this.apList = Manager.info.autopagerize
.filter(function(obj){
var url = obj.url;
return new RegExp(url).test(this.requestURL);
}, this)
.sort(function(a, b){ return (b.url.length - a.url.length) });
if(USE_AUTOPAGERIZE_SITEINFO && this.entry.length === 0){
log(this.apList)
this.apList.some(function(i){
if(i.name==='hAtom' || i.name==='autopagerize_microformat') return false;
try {
var entry = $X(i.pageElement, htmldoc);
} catch(e) { return false }
if(entry.length>0){
this.entry = entry;
log('FULLFEED: AutoPagerize Siteinfo');
return true;
}
else return false;
},this);
}
if(AUTO_SEARCH && this.entry.length === 0){
log('FULLFEED: Auto Search');
this.entry = searchEntry(htmldoc);
}
if(EXTRACT_TEXT && this.entry.length === 0){
log('FULLFEED: Extract Text');
this.entry = extractText(htmldoc);
}
this.requestEnd(htmldoc);
}
FullFeed.prototype.getAutoPager = function(htmldoc){
try {
this.entry = $X(this.info.xpath, htmldoc);
(this.entry.length === 0) && (this.entry = $X(this.ap.pageElement, htmldoc));
this.nextLink = $X(this.ap.nextLink, htmldoc);
} catch(e) {
this.enable = false;
}
this.requestEnd(htmldoc);
}
FullFeed.prototype.requestEnd = function(htmldoc){
var that = this;
if (this.entry.length > 0) {
if(AUTOPAGER) this.searchAutoPagerData(htmldoc);
log(this.entry);
FullFeed.filters.forEach(function(f) { f(that.entry, that.requestURL) });
this.addEntry();
this.state = 'loaded';
message('Loading '+this.type+' ...Done');
if(AUTOPAGER && !FullFeed.fullfeed['_'+this.data.id]) FullFeed.fullfeed['_'+this.data.id] = this;
this.data.container.classList.add('gm_fullfeed_loaded');
this.data.container.classList.toggle('gm_fullfeed_loading');
this.data.container.classList.toggle(this.requestURL);
}
else return this.error('This SITE_INFO is unmatched to this entry');
}
FullFeed.prototype.error = function(e){
this.state = 'error';
message('Error: ' + e);
this.data.container.classList.add('gm_fullfeed_error');
this.data.container.classList.toggle('gm_fullfeed_loading');
}
FullFeed.prototype.createSpaceFullFeed = function(){
var range = document.createRange();
range.selectNodeContents(this.data.item_body);
range.deleteContents();
range.detach();
return document.createDocumentFragment();
}
FullFeed.prototype.createSpaceAutoPager = function(){
var p = $CF('<hr/><p class="gm_fullfeed_pager">page <a class="gm_fullfeed_link" href="'+this.requestURL+'">'+(++this.pageNum || (this.pageNum=2))+'</a></p>');
return p;
}
FullFeed.prototype.addEntry = function(){
var that = this;
var df = this['createSpace'+this.type]();
this.entry.forEach(function(e) {
df.appendChild($CF(sanitize(e)));
});
this.entry = null;
this.data.item_body.appendChild(df);
}
FullFeed.prototype.AutoPager = function (){
if (!this.enable){
if(this.pageNum>0) return message("cannot AutoPage");
else return message('This entry has been already loaded.');
}
var nextLink = this.nextLink.getAttribute('href') ||
this.nextLink.getAttribute('action') ||
this.nextLink.getAttribute('value');
var base = this.requestURL;
var resolver = path_resolver(base);
nextLink = resolver(nextLink);
this.requestURL = nextLink;
this.type = 'AutoPager';
this.request();
}
FullFeed.prototype.searchAutoPagerData = function (htmldoc){
this.enable = false;
if(this.apList.length>0){
var nextLink;
if(!this.ap){
if( this.apList.some(function(i){
if((nextLink = $X(i.nextLink, htmldoc)[0]) &&
($X(i.pageElement, htmldoc).length>0)){
this.ap = i;
this.enable = true;
return true;
}
return false;
},this)){
this.nextLink = nextLink;
}
} else {
if(nextLink = $X(this.ap.nextLink, htmldoc)[0]){
this.enable = true;
this.nextLink = nextLink;
}
}
}
}
FullFeed.register = function(){
if(AUTOPAGER){
FullFeed.fullfeed = {};
w.register_hook('BEFORE_PRINTFEED',function(){
FullFeed.fullfeed = {};
});
}
if(!WIDGET) return;
var icon_data = GM_getResourceURL(ICON);
var description = "\u5168\u6587\u53d6\u5f97\u3067\u304d\u308b\u3088\uff01";
w.entry_widgets.add('gm_fullfeed_widget', function(feed, item){
if((Manager.matchPattern(item.link) || Manager.matchPattern(feed.channel.link)) && !ADCHECKER.test(item.title)) {
if(CLICKABLE) return [
'<img class="gm_fullfeed_icon_disable" id="gm_fullfeed_widget_'+item.id+'" src="'+icon_data+'">'
].join('');
else return [
'<img src="'+icon_data+'">'
].join('');
}
}, description);
if(!CLICKABLE) return;
var tmp = [];
w.register_hook("AFTER_PRINTFEED", function(feed){
addListener();
var state = w.State;
if(!state.writer || state.writer.complete) return;
watchWriter(feed);
});
w.register_hook("BEFORE_PRINTFEED", function(feed){
removeListener();
});
function watchWriter(feed){
var state = w.State;
state.writer.watch("complete", function(key, oldVal, newVal){
state.writer2.watch("complete", function(key, oldVal, newVal){
if(! state.writer.complete){
watchWriter(feed);
return newVal;
}
addListener();
return newVal;
});
return newVal;
})
}
function addListener(){
$X('id("right_body")//img[contains(concat(" ",@class," ")," gm_fullfeed_icon_disable ")]', document)
.forEach(function(element){
element.classList.remove('gm_fullfeed_icon_disable');
element.classList.add('gm_fullfeed_icon');
element.addEventListener('click', getEntryByPressButton, true);
tmp.push(element);
});
}
function removeListener(){
while(tmp.length){
try{
tmp.pop().removeEventListener('click', getEntryByPressButton, true);
}catch(e){}
}
}
var reg = /gm_fullfeed_widget_(.+)/;
function getEntryByPressButton(e){
var id = this.id.match(reg)[1];
(this && this.classList.contains('gm_fullfeed_icon')) && Manager.check(id);
}
}
// API
FullFeed.documentFilters = [];
FullFeed.filters= [];
FullFeed.itemFilters= [];
window.FullFeed = {
VERSION: VERSION,
addItemFilter: function(f){ FullFeed.itemFilters.push(f) },
addFilter: function(f){ FullFeed.filters.push(f) },
addDocumentFilter: function(f){ FullFeed.documentFilters.push(f) },
};
// [Filters]
(function(){
// Filter: Remove Script and H2 tags
// iframeはどうも要素を作成した時点で読みにいくようなので、textから正規表現で削除
// なので、SITEINFOはIFRAMEを基準に作成しないでいただけるとありがたい。
(function(){
var h2_span = document.createElement('span');
h2_span.className = 'gm_fullfeed_h2';
window.FullFeed.addFilter(function(nodes, url){
filter(nodes, function(e){
var n = e.nodeName.toLowerCase();
if(n === 'script' || n === 'h2') return false;
return true;
});
nodes.forEach(function(e){
$X('descendant-or-self::*[self::script or self::h2]', e)
.forEach(function(i){
var n = i.nodeName.toLowerCase();
var r = h2_span.cloneNode(false);
if(n === 'script') i.parentNode.removeChild(i);
if(n === 'h2'){
$A(i.childNodes).forEach(function(child){ r.appendChild(child.cloneNode(true)) });
i.parentNode.replaceChild(r, i);
}
});
});
});
})();
// Filter: Remove Particular Class
// LDR 自体が使っているclassを取り除く。とりあえずmoreだけ。
// ほかにもあれば追加する。
(function(){
window.FullFeed.addFilter(function(nodes, url){
nodes.forEach(function(e){
$X('descendant-or-self::*[contains(concat(" ",@class," ")," more ")]', e)
.forEach(function(i){
i.classList.remove('more');
});
});
});
})();
// Filter: Disable Hatena Keyword
(function(){
if(DISABLE_HATENAKEYWORD){
var reg = /(^http:\/\/d\.hatena\.ne\.jp|^http:\/\/.+?.g\.hatena\.ne\.jp\/bbs|^http:\/\/(.)*?\.g\.hatena.ne\.jp\/|^http:\/\/anond\.hatelabo\.jp\/)/;
var span = document.createElement('span');
span.className = 'keyword';
window.FullFeed.addFilter(function(nodes, url){
if(!reg.test(url)) return;
nodes.forEach(function(e){
var keywords = $X('descendant-or-self::a[(@class="keyword") or (@class="okeyword")]', e);
if(keywords){
keywords.forEach(function(key){
var r = span.cloneNode(false);
$A(key.childNodes).forEach(function(child){ r.appendChild(child.cloneNode(true)) });
key.parentNode.replaceChild(r, key);
});
}
});
});
}
})();
})();
// [Cache]
var Cache = function(manager){
var self = this;
this.manager = manager;
manager.state = 'loading';
this.ldrfullfeed = {};
this.autopagerize = [AUTOPAGERIZE_MICROFORMAT];
this.success = 0;
this.error_flag = false;
this.length = SITEINFO_IMPORT_URLS.length;
this.error_flag || message('Resetting cache. Please wait...');
PHASE.forEach(function(i){
this.ldrfullfeed[i.type] = [];
}, this);
SITEINFO_IMPORT_URLS.forEach(function(obj, index) {
new Agent(obj, this, index);
}, this);
}
Cache.prototype.finalize = function(){
var manager = this.manager;
PHASE.forEach(function(p){
this.ldrfullfeed[p.type].sort(function(a,b){
return a.urlIndex - b.urlIndex;
});
}, this);
manager.info = {
VERSION : VERSION,
ldrfullfeed : this.ldrfullfeed,
autopagerize : this.autopagerize
};
var serialized = JSON.stringify(manager.info);
try {
w.localStorage.setItem("cache", serialized);
} catch (e) {
console.log("save error", e);
}
this.error_flag || message('Resetting cache. Please wait... Done');
manager.state = 'normal';
if(WIDGET) manager.createPattern();
PHASE.forEach(function(i){
var fullfeed_list = manager.info.ldrfullfeed[i.type];
fullfeed_list.forEach(function(data){
data.reg = new RegExp(data.url);
});
});
};
Cache.prototype.error = function(e){
this.error_flag || message('Cache Error: '+e);
this.error_flag = true;
this.manager.state = 'normal';
};
var Agent = function(opt, Cache, index){
for(var i in opt) opt.hasOwnProperty(i) && (this[i] = opt[i]);
['format', 'type'].forEach(function(prop){
this[prop] = this[prop].toUpperCase();
}, this);
this.Cache = Cache;
this._flag = false;
this.index = index;
Agent.request(this);
}
Agent.prototype = {
method: 'GET',
headers: {
'User-Agent': navigator.userAgent + ' Greasemonkey (LDR Full Feed ' + VERSION + ')'
},
onload: function(res){
this['onload_'+this.type](res);
},
onerror: function(code){
if(!this._flag && this.alternative){
this.Cache.error_flag || message(code+' Regain SITEINFO from alternative url');
this._flag = true;
this.url = this.alternative;
Agent.request(this);
} else {
return this.Cache.error(code || 'Cache Request Error '+this.name);
}
},
onload_AUTOPAGERIZE: function(res){
var info = Agent[this.format].AUTOPAGERIZE(res.responseText, this.index);
if(!info) return this.onerror(this.format+' Parse Error: '+this.name);
var ap_list = this.Cache.autopagerize;
info.forEach(function(i){
ap_list.push(i);
});
log('REQUEST END');
if(++this.Cache.success === this.Cache.length) this.Cache.finalize();
},
onload_LDRFULLFEED: function(res){
var info = Agent[this.format].LDRFULLFEED(res.responseText, this.index);
if(!info) return this.onerror(this.format+' Parse Error: '+this.name);
PHASE.forEach(function(i){
var fullfeed_list = this.Cache.ldrfullfeed[i.type];
info.filter(function(d){
var type = d.type.toUpperCase();
return (type === i.type || (i.sub && type === i.sub));
})
.forEach(function(d){
fullfeed_list.push(d);
});
log('CACHE: ' + i.type + ':ok');
}, this);
log('REQUEST END');
if(++this.Cache.success === this.Cache.length) this.Cache.finalize();
},
ontimeout: function(){
log('TIMEOUT');
this.onerror('Cache Error: TIMEOUT');
}
};
Agent.JSON = {
LDRFULLFEED: function(data, index){
try {
return JSON.parse(data)
.reduce(function(memo, i){
var d = i.data;
d.name = i.name;
d.microformats = (d.microformats === 'true');
d.urlIndex = index;
if(['url', 'xpath', 'type'].some(function(prop){
if(!d[prop] && (prop != 'xpath' || !d.microformats)) return true;
try{
var reg = new RegExp(d.url);
} catch(e) {
return true;
}
return false;
})){
return memo;
} else {
memo.push(d);
return memo;
}
}, []);
} catch(e) {
return null;
}
},
AUTOPAGERIZE: function(data, index){
var info = [];
var ap_list = this.autopagerize;
try {
return JSON.parse(data)
.reduce(function(memo, i){
var d = i.data;
d.name = i.name;
try{
var reg = new RegExp(d.url);
memo.push(d);
return memo;
} catch(e) {
return memo;
}
}, []);
} catch(e) {
return null;
}
}
};
Agent.HTML = {
LDRFULLFEED: function(data, index){
var info = [];
try {
var doc = createDocumentFromString(data);
} catch(e) {
return null;
}
$X('//textarea[contains(concat(" ",normalize-space(@class)," "), " ldrfullfeed_data ")]', doc)
.forEach(function(siteinfo_list){
var data = Agent.parseSiteinfo(siteinfo_list.value, index);
if(data) info.push(data);
});
['utf-8','euc-jp','shift_jis'].forEach(function(charset){
$X('//ul[contains(concat(" ",normalize-space(@class)," "), " microformats_list ' + charset + ' ")]/li', doc)
.forEach(function(microformats_data){
var data = Agent.parseMicroformats(charset, microformats_data, index);
if(data) info.push(data);
});
});
return info;
}
};
Agent.parseMicroformats = function(c, li, index){
if(!li) return;
var info = {
name : "MicroformatsURLList:"+li.textContent,
url : li.textContent,
urlIndex : index,
enc : c,
microformats : true,
type : 'INDIV_MICROFORMATS'
}
try {
var reg = new RegExp(info.url);
return info;
} catch(e) {
return null;
}
};
Agent.parseSiteinfo = function(text, index){
var lines = text.split(/[\r\n]+/);
var reg = /(^[^:]*?):(.*)$/;
var trim = Agent.trim;
var info = {};
var result = null;
lines.forEach(function(line) {
if(result = line.match(reg)){
info[result[1]] = trim(result[2]);
}
});
info.microformats = (info.microformats && info.microformats === 'true');
if(['url', 'xpath', 'type'].some(function(prop){
if(!info[prop] && (prop != 'xpath' || !info.microformats)) return true;
try{
var reg = new RegExp(info.url);
} catch(e) {
return true;
}
return false;
})){
return null;
} else {
return info;
}
};
Agent.trim = function(str){
return str.replace(/^\s*/, '').replace(/\s*$/, '');
};
Agent.request = function(opt){
var original = opt.onload;
var new_opt = {
_timeout_flag: false,
onload: function(){
if(new_opt._timeout_flag) return false;
new_opt._timeout_flag = true;
if(opt.onload) return opt.onload.apply(opt, arguments);
},
onerror: function(){
if(opt.onerror) return opt.onerror.apply(opt, arguments);
},
onreadystatechange: function(){
if(opt.onreadystatechange) return opt.onreadystatechange.apply(opt, arguments);
}
};
['headers', 'method', 'url', 'overrideMimeType', 'data']
.forEach(function(prop){
new_opt[prop] = opt[prop];
});
window.setTimeout(function(){
if(new_opt._timeout_flag) return;
new_opt._timeout_flag = true;
opt.ontimeout.apply(opt, arguments);
}, opt.time || XHR_TIMEOUT, opt);
window.setTimeout(GM_xmlhttpRequest, 0, new_opt);
};
// [Manager]
var Manager = {
info: null,
patterns: [],
state: 'normal',
init: function(){
var self = this;
this.getSiteinfo();
this.rebuildLocalSiteinfo();
log(this.info);
if(WIDGET) this.createPattern();
if(LOADING_MOTION) addStyle(CSS, 'gm_fullfeed_style');
GM_registerMenuCommand('LDR Full Feed - reset cache', function(){ self.resetSiteinfo.call(self)});
var id = setTimeout(function(){
if (id) clearTimeout(id);
if (typeof w.Keybind != 'undefined' && typeof w.entry_widgets != 'undefined') {
w.Keybind.add(KEY, function(){
self.loadCurrentEntry();
});
if(GET_ALL)
w.Keybind.add(GET_ALL_KEY, function(){
self.loadAllEntries();
});
w.Keybind.add(GET_SITEINFO_KEY, function() {
self.resetSiteinfo();
});
if(WIDGET) FullFeed.register();
} else {
id = setTimeout(arguments.callee, 100);
}
}, 0);
},
getSiteinfo: function(){
var str = w.localStorage.getItem("cache");
if(str){
try{
this.info = JSON.parse(str);
} catch(e){
this.info = null;
}
}
if(!this.info || !this.info.VERSION || (this.info.VERSION < VERSION)){
var t = {};
PHASE.forEach(function(i){t[i.type] = []});
this.info = {
ldrfullfeed : t,
autopagerize : [AUTOPAGERIZE_MICROFORMAT],
VERSION : VERSION
};
this.resetSiteinfo();
} else {
PHASE.forEach(function(i){
var fullfeed_list = this.info.ldrfullfeed[i.type];
fullfeed_list.forEach(function(data){
data.reg = new RegExp(data.url);
});
}, this);
}
},
resetSiteinfo: function(){
if(this.state === 'loading') return message('Now loading. Please wait!');
var cacheAgent = new Cache(this);
},
rebuildLocalSiteinfo: function(){
this.siteinfo = SITE_INFO
.map(function(data){
data.urlIndex = -1;
data.reg = new RegExp(data.url);
return data;
});
},
createPattern: function(){
var exps = [];
this.siteinfo && this.siteinfo.forEach(function(i){
exps.push(i.url);
});
if(this.info && this.info.ldrfullfeed){
for each (var i in this.info.ldrfullfeed) {
i.forEach(function(info) {
exps.push(info.url);
});
}
}
var len = exps.length;
if (len > 100) {
var item = len / 3 | 0;
this.patterns[0] = new RegExp(exps.slice(0, item).join('|'));
this.patterns[1] = new RegExp(exps.slice(item, item+item).join('|'));
this.patterns[2] = new RegExp(exps.slice(item+item).join('|'));
} else if (len) {
this.patterns[0] = new RegExp(exps.join('|'));
}
},
matchPattern: function(text){
return this.patterns.some(function(pattern){
return pattern && pattern.test(text);
});
},
loadCurrentEntry: function(){
this.check();
},
loadAllEntries: function(){
var items = w.get_active_feed().items;
if (items && items.length > 0)
items.forEach(function(item){ this.check(item.id) }, this);
},
check: function(id){
var c = (id) ? new this.getData(id) : new this.getData();
if(!c) return;
if(ITEMFILTER){
FullFeed.itemFilters.forEach(function(f) {
f(c);
});
}
if(ADCHECKER.test(c.title))
return message('This entry is advertisement');
if(c.container.classList.contains('gm_fullfeed_loaded')){
if(AUTOPAGER && FullFeed.fullfeed['_'+c.id]){
FullFeed.fullfeed['_'+c.id].AutoPager();
return;
}
else return message('This entry has been already loaded.');
}
if(c.container.classList.contains('gm_fullfeed_loading'))
return message('Now loadig...');
if(!c.item.fullfeed){
var fullfeed_list = this.info.ldrfullfeed;
this.launchFullFeed(this.siteinfo, c);
log('PHASE: LOCAL SITEINFO');
if(!c.found && !PHASE.some(function(i){
log('PHASE: ' + i.type);
this.launchFullFeed(fullfeed_list[i.type], c);
return c.found;
}, this)){
message('This entry is not listed on SITE_INFO');
if (OPEN) setTimeout(GM_openInTab, 0, c.itemURL) || message('Cannot popup');
}
}
},
// data format
//
// itemURL
// feedURL
// id
// title
// container
// title
// item <-- unsafe item
// found
//
// create safe item
getData: function(id){
if(!id) var id = w.get_active_item(true).id;
if(!id) return;
var feed = w.get_active_feed();
this.item = w.get_item_info(id);
this.itemURL = this.item.link;
this.feedURL = feed.channel.link;
this.id = this.item.id;
this.container = w.$('item_' + this.id);
this.title = this.item.title;
this.found = false;
},
launchFullFeed: function(list, c){
if (typeof list.some != "function") return;
var item_url = c.itemURL;
var feed_url = c.feedURL;
list.some(function(data) {
if (data.reg.test(item_url) || data.reg.test(feed_url)) {
c.found = true;
new FullFeed(data, c);
return true;
} else {
return false;
}
});
}
}
// main
Manager.init();
// == [Utility Functions] ===========================================
function message (mes){
w.message(mes);
}
function $CF(text){
return $CF.range.createContextualFragment(text);
}
$CF.range = document.createRange();
$CF.range.selectNode(document.body);
function $A(a){
return Array.prototype.slice.call(a);
}
function getElementsByMicroformats (htmldoc) {
var t;
MICROFORMATS.some(function(i){
t = $X(i.xpath, htmldoc)
if(t.length>0){
log('FULLFEED: Microformats :' + i.name);
return true;
}
else return false;
});
return t;
}
function removeXSSRisk (htmldoc){
var attr = "allowscriptaccess";
$X("descendant-or-self::embed", htmldoc)
.forEach(function(elm){
if(!elm.hasAttribute(attr)) return;
elm.setAttribute(attr, "never");
});
$X("descendant-or-self::param", htmldoc)
.forEach(function(elm){
if(!elm.getAttribute("name") || elm.getAttribute("name").toLowerCase().indexOf(attr) < 0) return;
elm.setAttribute("value", "never");
});
}
function extractText (htmldoc) {
var div = document.createElement('div');
$X('(descendant-or-self::text()[../self::*[self::div or self::table or self::td or self::th or self::tr or self::dt or self::dd or self::font or self::strong or self::ul or self::li]]|descendant-or-self::img|descendant-or-self::a)', htmldoc)
.map(function(i){
if(i.nodeName === 'IMG')
return i;
else if(i.nodeName === 'A')
return i;
else{
i.nodeValue = i.nodeValue+'\n'
return i;
}
})
.forEach(function(i){
div.appendChild(i);
});
div.innerHTML = div.innerHTML
.replace(/(?:(\r\n|\r|\n)\s*)+/g,'<br>$1');
return [div];
}
function searchEntry(htmldoc) {
var max = 0;
var entry = [];
var data;
var xpath = [
'(//div|//td|//table|//tbody)',
'[(..//h2) or (.//h3) or (.//h4) or (.//h5) or (.//h6) or (..//*[contains(concat(@id,@class,""),"title")])]',
// '[(.|.//*|ancestor-or-self::*)contains(concat(@id,@class,""),"entry")) or (contains(concat(@id,@class,""),"section")) or (contains(concat(@id,@class,""),"content")) or (contains(concat(@id,@class,""),"main")) or (contains(concat(@id,@class,""),"day")) or (contains(concat(@id,@class,""),"article"))]]',
'[not(.//form|ancestor-or-self::form)]',
'[not((.|.//*|ancestor-or-self::*)contains(concat(" ",@class," ")," robots-nocontent ")])]',
'[not((.|.//*|ancestor-or-self::*)starts-with(concat(@id,@class,""),"side")])]',
'[not((.|.//*|ancestor-or-self::*)starts-with(concat(@id,@class,""),"navi")])]',
'[not((.|.//*|ancestor-or-self::*)starts-with(concat(@id,@class,""),"footer")])]',
'[not((.|.//*|ancestor-or-self::*)starts-with(concat(@id,@class,""),"header")])]',
'[not(.//script|ancestor-or-self::script)]',
].join('');
try {
var elms = $X(xpath, htmldoc);
if(elms.length === 0) return entry;
elms.forEach(function(e){
// var n = e.getElementsByTagName('br').length;
var n = e.textContent.length;
if(max < n){
max = n;
data = e;
}
});
entry.push(data);
return entry;
}catch (e){
return [];
}
}
function relativeToAbsolutePath(htmldoc, base){
var resolver = path_resolver(base);
$X("descendant-or-self::a", htmldoc)
.forEach(function(elm) {
if(elm.getAttribute("href")) elm.href = resolver(elm.getAttribute("href"));
});
$X("descendant-or-self::img", htmldoc)
.forEach(function(elm) {
if(elm.getAttribute("src")) elm.src = resolver(elm.getAttribute("src"));
});
$X("descendant-or-self::embed", htmldoc)
.forEach(function(elm) {
if(elm.getAttribute("src")) elm.src = resolver(elm.getAttribute("src"));
});
$X("descendant-or-self::object", htmldoc)
.forEach(function(elm) {
if(elm.getAttribute("data")) elm.data = resolver(elm.getAttribute("data"));
});
}
function path_resolver(base){
var XHTML_NS = "http://www.w3.org/1999/xhtml"
var XML_NS = "http://www.w3.org/XML/1998/namespace"
var a = document.createElementNS(XHTML_NS, 'a')
a.setAttributeNS(XML_NS, 'xml:base', base)
return function(url){
a.href = url;
return a.href;
}
}
function filter(a, f) {
for (var i = a.length; i --> 0; f(a[i]) || a.splice(i, 1));
}
function addStyle(css,id){ // GM_addStyle is slow
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'data:text/css,' + escape(css);
document.documentElement.childNodes[0].appendChild(link);
}
// via http://github.com/hatena/hatena-bookmark-xul/blob/master/chrome/content/common/05-HTMLDocumentCreator.js
// a little modified
function createDocumentFromString(source){
var doc = document.cloneNode(false);
doc.appendChild(doc.importNode(document.documentElement, false));
var range = doc.createRange();
range.selectNodeContents(doc.documentElement);
var fragment = range.createContextualFragment(source);
var headChildNames = {title: true, meta: true, link: true, script: true, style: true, /*object: true,*/ base: true/*, isindex: true,*/};
var child, head = doc.getElementsByTagName('head')[0] || doc.createElement('head'),
body = doc.getElementsByTagName('body')[0] || doc.createElement('body');
while ((child = fragment.firstChild)) {
if (
(child.nodeType === doc.ELEMENT_NODE && !(child.nodeName.toLowerCase() in headChildNames)) ||
(child.nodeType === doc.TEXT_NODE &&/\S/.test(child.nodeValue))
)
break;
head.appendChild(child);
}
body.appendChild(fragment);
doc.documentElement.appendChild(head);
doc.documentElement.appendChild(body);
return doc;
}
// %o %s %i
function log() {if(unsafeWindow.console && DEBUG) unsafeWindow.console.log.apply(unsafeWindow.console, Array.slice(arguments));}
function group() {if(unsafeWindow.console && DEBUG) unsafeWindow.console.group.apply(unsafeWindow.console, Array.slice(arguments))}
function groupEnd() {if(unsafeWindow.console &&DEBUG) unsafeWindow.console.groupEnd.apply(unsafeWindow.console, arguments);}
function time(name) {if(unsafeWindow.console.time && DEBUG) unsafeWindow.console.time.apply(unsafeWindow.console, arguments)}
function timeEnd(name) {if(unsafeWindow.console.timeEnd && DEBUG) unsafeWindow.console.timeEnd.apply(console, arguments)}
// http://d.hatena.ne.jp/os0x/20080228/1204210085
// a little modified
function escapeHTML(str) {
return str.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
function sanitize(node) {
if (node.nodeType !== 1 && node.nodeType !== 3) {
return;
}
var contents = Array.prototype.slice.call(node.childNodes).reduce(function(memo, node) {
var content = sanitize(node);
if (content) {
memo.push(content);
}
return memo;
}, []);
if (node.nodeType === 1) {
// white list
var tag = node.tagName;
var attr = (function attrCollector() {
var res = [''];
switch (tag.toUpperCase()) {
case 'H2':
tag = 'H3';
break;
case 'IMG':
if (/^(?:https?:\/\/|\.|\/)/.test(node.src)) {
res.push('src=' + JSON.stringify(node.src));
}
if (node.alt || node.title) {
res.push('alt=' + JSON.stringify(node.alt || node.title));
}
break;
case 'A':
if (/^(?:https?:\/\/|\.|\/)/.test(node.href)) {
res.push('href='+ JSON.stringify(node.href));
}
if (node.alt || node.title) {
res.push('alt=' + JSON.stringify(node.alt || node.title));
}
res.push('target="_blank"');
break;
};
return res.join(' ');
})();
tag = escapeHTML(tag);
return '<' + tag + ' ' + attr + '>' + contents.join('') + '</' + tag + '>';
} else if (node.nodeType === 3) {
return escapeHTML(node.nodeValue);
}
}
})(this.unsafeWindow || this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment