root/lang/javascript/userscripts/googlereaderfullfeed.user.js @ 7141

Revision 7141, 14.0 kB (checked in by drry, 2 years ago)

lang/javascript/userscripts/googlereaderfullfeed.user.js:

  • bumped up the version number to 0.0.6.
  • @namespace を変更しました。
  • 大文字の定数と思われる変数を定数にしました。
Line 
1// ==UserScript==
2// @name        Google Reader Full Feed
3// @namespace   tag:mattn.jp@gmail.com,2008-02-25:/coderepos.org
4// @include     http://www.google.com/reader/*
5// @include     https://www.google.com/reader/*
6// @include     http://www.google.co.jp/reader/*
7// @include     https://www.google.co.jp/reader/*
8// @description loading full entry on Google Reader
9// @privilege   false
10// @version     0.0.6
11// based on LDR Full Feed(http://d.hatena.ne.jp/toshi123)
12//
13// author: mattn (mattn.jp@gmail.com)
14// ==/UserScript==
15setTimeout(function(){
16
17var w = (typeof unsafeWindow == 'undefined') ? window : unsafeWindow;
18
19// == [CSS] =========================================================
20const CSS = [
21'.gm_fullfeed_loading, .gm_fullfeed_loading a{color : green !important;}',
22'.gm_fullfeed_loading .item_body a{color : palegreen !important;}',
23'.gm_fullfeed_loading{background-color : Honeydew !important;}',
24].join('');
25
26// == [Icon] ========================================================
27const ICON = 'data:image/gif;base64,'+
28'R0lGODdhEwATAPMAMf+MAP+lAP+lOv+0AP+0kP/EAP/Etv/TOv/hZv/x2//x////tv//2////wAA'+
29'AAAAACwAAAAAEwATAAAETBDISWsNOOuNJf+aB4LiyJUZ0awNsqGB0RSYkAwhoAnKYaIEBm4EXJgC'+
30'QGFA1VBmUDwfJjjs6DQy2tJp5TBX0uf1mCO/xmYk2mxptyMAOw==';
31
32// == [Config] ======================================================
33
34const KEY = 'g';
35const ADCHECKER = /^(?:AD|PR):/;
36const LOADING_MOTION = true;
37const REMOVE_SCRIPT = true;
38const REMOVE_H2TAG = false;
39const OPEN = false; //SITEINFOになかった場合にそのエントリを開くかどうか。
40const WIDGET = true;
41const SITEINFO_IMPORT_URLS = [
42    'http://constellation.jottit.com/siteinfo',
43];
44
45// == [SITE_INFO] ===================================================
46
47const SITE_INFO = [
48];
49
50// == [Application] =================================================
51
52var FullFeed = function(info, c){
53  this.itemInfo = c;
54  this.info = info;
55  this.requestURL = this.itemInfo.itemURL;
56  var bodyXPath = 'id("current-entry")//ins/div';
57  this.itemInfo.item_body = getFirstElementByXPath(bodyXPath);
58  this.state = 'wait';
59
60  if (this.info.enc) {
61    this.mime = 'text/html; charset=' + this.info.enc;
62  } else {
63    this.mime = 'text/html; charset=' + document.characterSet;
64  }
65
66
67  this.request();
68};
69
70FullFeed.prototype.request = function(){
71  if (!this.requestURL) {
72    return
73  }
74  this.state = 'request';
75  var self = this;
76  var opt = {
77        method: 'get',
78        url: this.requestURL,
79        overrideMimeType: this.mime,
80        onerror: function(){
81          self.requestError.apply(self, ['Request Error'])
82        },
83        onload: function(res){
84          self.requestLoad.apply(self, [res])
85        },
86  };
87  message('Loading');
88  w.toggleClass(this.itemInfo.item_container, 'gm_fullfeed_loading');
89  if (opt.url.indexOf("http:") != 0) {
90    opt.url = pathToURL(this.info.base, opt.url);
91  }
92  window.setTimeout(function(){ GM_xmlhttpRequest(opt);}, 0);
93}
94
95FullFeed.prototype.requestLoad = function(res) {
96  this.state = 'loading';
97  var text = res.responseText;
98
99  if(this.info.base){
100    text = relativeToAbsolutePath(text, this.info.base);
101  } else {
102    text = relativeToAbsolutePath(text, this.itemInfo.itemURL);
103  }
104  if(REMOVE_SCRIPT) text = text.replace(/<script[^>]*>[\S\s]*?<\/script[^>]*>/g, "");
105  if(REMOVE_H2TAG) text = text.replace(/<h2[^>]*>[\S\s]*?<\/h2[^>]*>/g, "");
106  var htmldoc = parseHTML(text);
107  for (var i = 0;i < FullFeed.documentFilters.length; i++) {
108    FullFeed.documentFilters[i](htmldoc, this.itemInfo.itemURL, this.info);
109  }
110  try{
111    var entry = getElementsByXPath(this.info.xpath, htmldoc);
112  }
113  catch(e) {
114    message(e);
115    return;
116  }
117
118  if (entry) {
119    this.removeEntry();
120    entry = this.addEntry(entry);
121    for (var j = 0;j < FullFeed.filters.length; j++) {
122      FullFeed.filters[j](entry);
123    }
124    this.requestEnd();
125  } else {
126    this.requestError('This SITE_INFO is unmatched to this entry');
127  }
128
129  if (this.info.base) {
130    w.addClass(this.itemInfo.item_container, this.info.base);
131  } else {
132    w.addClass(this.itemInfo.item_container, this.itemInfo.itemURL);
133  }
134}
135
136FullFeed.prototype.requestEnd = function(){
137  this.state = 'loaded';
138  message('Done');
139  w.toggleClass(this.itemInfo.item_container, 'gm_fullfeed_loading');
140  w.addClass(this.itemInfo.item_container, 'gm_fullfeed_loaded');
141}
142
143FullFeed.prototype.requestError = function(e){
144  this.state = 'error';
145  message('Error : ' + e);
146  w.toggleClass(this.itemInfo.item_container, 'gm_fullfeed_loading');
147  w.addClass(this.itemInfo.item_container, 'gm_fullfeed_error');
148}
149
150FullFeed.prototype.removeEntry = function(){
151  while (this.itemInfo.item_body.firstChild) {
152    this.itemInfo.item_body.removeChild(this.itemInfo.item_body.firstChild);
153  }
154}
155
156FullFeed.prototype.addEntry = function(entry){
157  var self = this;
158  return entry.map(function(i) {
159      var pe = document.importNode(i,true);
160      self.itemInfo.item_body.appendChild(pe);
161      return pe;
162  });
163}
164
165FullFeed.parser = function(text){
166  var lines = text.split(/\r?\n|\r/);
167  var reg = /(^[^:]*):(.*)$/;
168  var trimspace = function(str){
169    return str.replace(/^\s+|\s+$/g, '');
170  };
171  var info = {};
172  for(var i = 0; i < lines.length; i++) {
173    if (reg.test(lines[i])) {
174      info[RegExp.$1] = trimspace(RegExp.$2);
175    }
176  }
177  var isValid = function(info) {
178    var infoProp = ['url', 'xpath'];
179    for (var i = 0; i <infoProp.length; i++) {
180      if (!info[infoProp[i]]) {
181        return false;
182      }
183    }
184    try{
185      new RegExp(info.url);
186    } catch(e) {
187      return false;
188    }
189    return true;
190  };
191  return isValid(info) ? info : null;
192}
193
194FullFeed.resetCache = function(){
195  message('Resetting cache. Please wait.');
196  SITEINFO_IMPORT_URLS.forEach(function(l){
197    var opt = {
198      method: 'get',
199      url: l,
200      onload: function(res){
201        FullFeed.setCache(res, l);
202      },
203      onerror: function(res){
204        message('Cache Request Error');
205      },
206    }
207    window.setTimeout(GM_xmlhttpRequest, 0, opt);
208  });
209}
210
211FullFeed.setCache = function(res, url){
212  var info = [];
213  var doc = parseHTML(res.responseText);
214  var lists = getElementsByXPath(
215      '//textarea[@class="ldrfullfeed_data"]', doc);
216  lists.forEach(function(list){
217      var data = FullFeed.parser(list.value);
218      if (data) {
219        info.push(data);
220      }
221  });
222  if (info.length > 0) {
223    cacheInfo[url] = {
224      url: url,
225      info: info
226    }
227    GM_setValue('cache', cacheInfo.toSource());
228    if(WIDGET) FullFeed.createPattern();
229    message('Done');
230  }
231}
232
233
234FullFeed.getCache = function(){
235  return eval(GM_getValue('cache')) || {};
236}
237
238FullFeed.createPattern = function(){
239  if(!WIDGET) return;
240  var exps = [];
241  for (var i = 0; i < SITE_INFO.length; i++){
242    exps.push(SITE_INFO[i].url);
243  }
244  for (var url in cacheInfo) {
245    var site = cacheInfo[url];
246    for (var j = 0; j < site.info.length; j++){
247      exps.push(site.info[j].url);
248    }
249  }
250  pattern = exps.join('|');
251}
252
253FullFeed.registerWidgets = function() {
254  if(!WIDGET) return;
255  var container = getFirstElementByXPath('id("current-entry")//a[contains(concat(" ",normalize-space(@class)," ")," entry-title-link ")]').parentNode;
256  if (!container) return;
257
258  FullFeed.createPattern();
259  var description = "\u5168\u6587\u53d6\u5f97\u3067\u304d\u308b\u3088\uff01";
260
261  var c = new getCurrentItem();
262  var item = c.item;
263  var feed = c.feed;
264  if (item.link.match(pattern) || feed.channel.link.match(pattern)) {
265    icon = document.createElement('span');
266    icon.title = description;
267    icon.innerHTML = '<img src="'+ICON+'">'
268    w.addClass(icon, 'gm_fullfeed_checked');
269    container.appendChild(document.createTextNode(' '));
270    container.appendChild(icon);
271  }
272}
273
274FullFeed.documentFilters = [
275// addTargetAttr
276(function (doc){
277  var anchors = getElementsByXPath('descendant-or-self::a', doc);
278  anchors.forEach(function(i){
279    i.target = '_blank';
280  });
281}),
282];
283
284FullFeed.filters= [];
285
286window.FullFeed = {};
287
288window.FullFeed.addDocumentFilter = function(f){
289  FullFeed.documentFilters.push(f);
290};
291
292window.FullFeed.addFilter = function(f){
293  FullFeed.filters.push(f);
294};
295
296w.get_active_item = function(flag) {
297  var item = {}
298  try {
299    item.link = getFirstElementByXPath('id("current-entry")//a[contains(concat(" ",normalize-space(@class)," ")," entry-title-link ")]').href;
300    item.title = getFirstElementByXPath('id("current-entry")//a[contains(concat(" ",normalize-space(@class)," ")," entry-title-link ")]').textContent;
301  } catch(e) {}
302  return item;
303}
304
305w.get_active_feed = function() {
306  var feed = {};
307  feed.channel = {}
308  try {
309    feed.channel.link = decodeURIComponent(getFirstElementByXPath('id("current-entry")//a[contains(concat(" ",normalize-space(@class)," ")," entry-source-title ")]').href.replace(/^.*\/(?=http)/, ''));
310  } catch(e) {}
311  return feed;
312}
313
314var hasClass = w.hasClass = function(element, classname) {
315  var cl = element.className;
316  var cls = cl.split(/\s+/);
317  return cls.indexOf(classname) != -1;
318}
319
320var toggleClass = w.toggleClass = function(element, classname) {
321  hasClass(element, classname) ? removeClass(element, classname) : addClass(element, classname);
322}
323
324var removeClass = w.removeClass = function(element, classname) {
325  var cl = element.className;
326  var cls = cl.split(/\s+/);
327  element.className = cls.remove(classname).join(" ");
328}
329
330var addClass = w.addClass = function(element, classname) {
331  var cl = element.className;
332  if (!contain(cl, classname)) {
333    element.className += " " + classname;
334  }
335}
336
337var contain = w.contain = function(self, other) {
338  if (isString(self) && isString(other)) {
339    return self.indexOf(other) != -1;
340  }
341  if (isRegExp(other)) {
342    return other.test(self);
343  }
344}
345
346var isString = w.isString = function(obj) {
347  return typeof obj == "string" || obj instanceof String;
348}
349
350var isRegExp = w.isRegExp = function(obj) {
351  return obj instanceof RegExp;
352}
353
354Array.prototype.remove = function(to_remove) {
355  return this.filter(function (val) {return val != to_remove});
356}
357
358// itemの情報を格納するobjectのconstructor
359var getCurrentItem = function(){
360  this.item = w.get_active_item(true);
361  this.feed = w.get_active_feed();
362  this.itemURL = this.item.link;
363  this.feedURL = this.feed.channel.link;
364  this.id = this.item.id;
365  this.item_container = getFirstElementByXPath('id("current-entry")//ins/div');
366  this.title = this.item.title;
367  this.find = false;
368};
369
370var launchFullFeed = function (list, c){
371  if(list){
372    for (var i = 0; i < list.length; i++){
373      var reg = new RegExp(list[i].url);
374      if (reg.test(c.itemURL) || reg.test(c.feedURL)){
375        c.find = true;
376        var ff = new FullFeed(list[i], c);
377        break;
378      }
379    }
380  }
381};
382
383var init = function(){
384  var c = new getCurrentItem();
385  if (!c.title) return;
386  if(ADCHECKER.test(c.title)){
387    message('This entry is advertisement');
388    return;
389  }
390  if(w.hasClass(c.item_container, 'gm_fullfeed_loaded')){
391    message('This entry has been already loaded.');
392    return;
393  }
394
395  launchFullFeed(SITE_INFO, c);
396
397  if (!c.find) {
398    for ( i = 0; i < SITEINFO_IMPORT_URLS.length && !c.find; i++){
399      launchFullFeed(cacheInfo[SITEINFO_IMPORT_URLS[i]].info, c);
400    }
401  }
402
403  if (!c.find){
404    message('This entry is not listed on SITE_INFO');
405    if (OPEN) window.open(c.itemURL) || message('Cannot popup');
406  }
407
408};
409
410var cacheInfo = FullFeed.getCache();
411var pattern;
412
413GM_registerMenuCommand('Google Reader Full Feed - reset cache', FullFeed.resetCache)
414
415if(LOADING_MOTION) addStyle(CSS, 'gm_fullfeed');
416
417document.addEventListener('keyup', function(e) {
418  if (e.keyCode == 71) {
419    init();
420    //FullFeed.registerWidgets();
421  }
422}, true);
423
424var timer = setTimeout(function() {
425  if (timer) clearTimeout(timer);
426  try {
427    var container = getFirstElementByXPath('id("current-entry")//a[contains(concat(" ",normalize-space(@class)," ")," entry-title-link ")]');
428    var icon = getFirstElementByXPath('id("current-entry")//span[contains(concat(" ",normalize-space(@class)," ")," gm_fullfeed_checked ")]');
429    if (container && !icon) {
430      FullFeed.registerWidgets();
431    }
432  } catch(e) {}
433  timer = setTimeout(arguments.callee, 200);
434});
435
436// == [Utility] =====================================================
437
438function relativeToAbsolutePath (text, link){
439  text = text.replace(/(<[^>]*img\s+[^>]*src\s*=\s*")([^"]+)(?="[^>]*>)/gi, function(match, l, src){
440    if (/^https?:\/\//.test(src)) {
441      return l+src;
442    } else if (src[0] == "/") {
443      var link_top = link.replace(/^(https?:\/\/[^\/]+)\/.*$/, '$1');
444      return l+link_top+src;
445    } else {
446      var link_base = link.replace(/\/[^\/]+$/, '/');
447      return l+link_base+src;
448    }
449    });
450  return text;
451}
452
453function message (mes){
454  //w.message(mes);
455  console.log(mes);
456}
457
458// copied from AutoPagerize(c) id:swdyh
459function getElementsByXPath(xpath, node) {
460    var node = node || document
461    var doc = node.ownerDocument ? node.ownerDocument : node
462    var nodesSnapshot = doc.evaluate(xpath, node, null,
463        XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
464    var data = []
465    for (var i = 0; i < nodesSnapshot.snapshotLength; i++) {
466        data.push(nodesSnapshot.snapshotItem(i))
467    }
468    return (data.length >= 1) ? data : null
469}
470
471function getFirstElementByXPath(xpath, node) {
472    var node = node || document
473    var doc = node.ownerDocument ? node.ownerDocument : node
474    var result = doc.evaluate(xpath, node, null,
475        XPathResult.FIRST_ORDERED_NODE_TYPE, null)
476    return result.singleNodeValue ? result.singleNodeValue : null
477}
478
479// copied from Pagerization (c) id:ofk
480function parseHTML(str) {
481  str = str.replace(/^[\s\S]*?<html[^>]*>|<\/html\s*>[\s\S]*$/ig, '')
482  var res = document.implementation.createDocument(null, 'html', null)
483  var range = document.createRange();
484  range.setStartAfter(document.body);
485  res.documentElement.appendChild(
486  res.importNode(range.createContextualFragment(str), true)
487  );
488  return res;
489}
490
491function pathToURL(url, path) {
492    var re = path[0] == "/"
493           ? /^([a-zA-Z]+:\/\/[^\/]+)\/.*$/
494           : /^(.*\/).*$/;
495    return url.replace(re, "$1" + path)
496}
497
498// copied from LDRize (c) id:snj14
499function addStyle(css, id){ // GM_addStyle is slow
500    var link = document.createElement('link');
501    link.rel = 'stylesheet';
502    link.href = 'data:text/css,' + escape(css);
503    document.documentElement.appendChild(link);
504}
505
506}, 0)
Note: See TracBrowser for help on using the browser.