Source for "Google Reader Full Feed + autoload"

By tomy_kaira
Has 1 other script.


// ==UserScript==
// @name        Google Reader Full Feed + autoload
// @namespace   tag:mattn.jp@gmail.com,2008-02-25:/coderepos.org
// @include     http://www.google.co.jp/reader/*
// @include     https://www.google.co.jp/reader/*
// @include     http://www.google.com/reader/*
// @include     https://www.google.com/reader/*
// @description loading full entry on Google Reader Tomykaira changed.
// @privilege   false
// @version     0.1.1
// ==/UserScript==
// based on LDR Full Feed <http://d.hatena.ne.jp/toshi123>
//
// author: mattn <mattn.jp@gmail.com>

(function(w) {
// == [CSS] =========================================================
const CSS = <><![CDATA[
  .gm_fullfeed_loading, .gm_fullfeed_loading a { color : green !important; }
  .gm_fullfeed_loading .item_body a { color : palegreen !important; }
  .gm_fullfeed_loading { background-color : Honeydew !important; }
]]></>.toString();

// == [Icon] ========================================================
const ICON = <><!-- http://api.feed-media.com/img/getcontentsmark.gif
--><![CDATA[data:image/gif;base64,
R0lGODdhEwATAPMAMf+MAP+lAP+lOv+0AP+0kP/EAP/Etv/TOv/hZv/x2//x////tv//2////wAA
AAAAACwAAAAAEwATAAAETBDISWsNOOuNJf+aB4LiyJUZ0awNsqGB0RSYkAwhoAnKYaIEBm4EXJgC
QGFA1VBmUDwfJjjs6DQy2tJp5TBX0uf1mCO/xmYk2mxptyMAOw==
]]></>.toString().replace(/\s+/g, "");
const ICON2 = <><!-- (c) id:Constellation --><![CDATA[data:image/gif;base64,
R0lGODdhEwATAPQAMUFp4YfO64fO7ofW9Yfe+LHO68XW68X3/MX3/9ne6+zn7uz/+Oz//Oz////v
8v///P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAA
EwATAAAFUCAgjmRZBmiqrqjIvqoLw/LM1unQQLyz4gECJIESHAwxgEqAUNhwAwZyBl0UnsqcNKCD
PKatbLGpBQeAQiJ3mwJydzxn0vZy0+1Y+s3EN4UAADs=
]]></>.toString().replace(/\s+/g, "");

// == [Config] ======================================================

const KEY = 'g';//<-これなんにつかってんだろ 実は"z"
const ADCHECKER = /^(?:AD|PR):/;
const LOADING_MOTION = true;
const REMOVE_SCRIPT = true;
const REMOVE_H2TAG = false;
const OPEN = false; //SITEINFOになかった場合にそのエントリを開くかどうか。
const WIDGET = true;
const SITEINFO_IMPORT_URLS = [
  'http://constellation.jottit.com/siteinfo',
];

// == [SITE_INFO] ===================================================

const SITE_INFO = [
{url: "^http://gigazine\.net/",
xpath: 'id("maincol")/div[@class="content"]/child::node()[not(@class="title") and not(@class="posted") and not(@class="center")]',
base: "http://gigazine.net"
},
];

// == [Application] =================================================
var interval;
var AUTO_FETCH = true;
var switchAutoFetch = function() {
  if(AUTO_FETCH == true){
    AUTO_FETCH=false;
    message('する→しない');
    clearInterval(interval);
  } else {
    AUTO_FETCH=true;
    message('しない→する');
    interval = window.setInterval(
       function() {
			var c = new getCurrentItem();
			if(!w.hasClass(c.item_container, 'gm_fullfeed_loaded')){
                init();
            }
        },
        500
    );
  }
}

var FullFeed = function(info, c) {
  this.itemInfo = c;
  this.info = info;
  this.requestURL = this.itemInfo.itemURL;
  var bodyXPath = 'id("current-entry")//ins/div';
  this.itemInfo.item_body = getFirstElementByXPath(bodyXPath);
  this.state = 'wait';

  if (this.info.enc) {
    this.mime = 'text/html; charset=' + this.info.enc;
  } else {
    this.mime = 'text/html; charset=' + document.characterSet;
  }


  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 (Google Reader Full Feed)"
    },
    onerror: function() {
      self.requestError.apply(self, ['Request Error'])
    },
    onload: function(res) {
      self.requestLoad.apply(self, [res])
    }
  };
  message('Loading Full Feed...');
  w.toggleClass(this.itemInfo.item_container, 'gm_fullfeed_loading');
  if (opt.url.indexOf("http:") != 0) {
    opt.url = pathToURL(this.info.base, opt.url);
  }
  window.setTimeout(GM_xmlhttpRequest, 0, opt);
};

FullFeed.prototype.requestLoad = function(res) {
  this.state = 'loading';
  var text = res.responseText;

  text = text.replace(/(<[^>]+?[\s"'])on(?:(?:un)?load|(?:dbl)?click|mouse(?:down|up|over|move|out)|key(?:press|down|up)|focus|blur|submit|reset|select|change)\s*=\s*(?:"(?:\\"|[^"])*"?|'(\\'|[^'])*'?|[^\s>]+(?=[\s>]|<\w))(?=[^>]*?>|<\w|\s*$)/gi,
    "$1");
  if (REMOVE_SCRIPT) text = text.replace(/<script[^>]*>[\S\s]*?<\/script\s*>/gi, "");
  if (REMOVE_H2TAG)  text = text.replace(/<h2[^>]*>[\S\s]*?<\/h2\s*>/gi, "");
  var htmldoc = parseHTML(text);
  removeXSSRisks(htmldoc);
  if (this.info.base && !this.itemInfo.itemURL.match(this.info.base)) {
    relativeToAbsolutePath(htmldoc, this.info.base);
  } else {
    relativeToAbsolutePath(htmldoc, this.itemInfo.itemURL);
  }
  for (var i = 0, l = FullFeed.documentFilters.length; i < l;
    FullFeed.documentFilters[i++](htmldoc, this.itemInfo.itemURL, this.info));
  try {
    var entry = getElementsByXPath(this.info.xpath, htmldoc);
  } catch (e) {
    message(e);
    return;
  }

  if (entry) {
    this.removeEntry();
    entry = this.addEntry(entry);
    for (var i = 0, l = FullFeed.filters.length; i < l;
      FullFeed.filters[i++](entry));
    this.requestEnd();
  } else {
    this.requestError('This SITE_INFO is unmatched to this entry');
  }

  if (this.info.base) {
    w.addClass(this.itemInfo.item_container, this.info.base);
  } else {
    w.addClass(this.itemInfo.item_container, this.itemInfo.itemURL);
  }
};

FullFeed.prototype.requestEnd = function() {
  this.state = 'loaded';
  message('Loading Full Feed... Done');
  w.toggleClass(this.itemInfo.item_container, 'gm_fullfeed_loading');
  w.addClass(this.itemInfo.item_container, 'gm_fullfeed_loaded');
};

FullFeed.prototype.requestError = function(e) {
  this.state = 'error';
  message('Error : ' + e);
  w.toggleClass(this.itemInfo.item_container, 'gm_fullfeed_loading');
  w.addClass(this.itemInfo.item_container, 'gm_fullfeed_error');
};

FullFeed.prototype.removeEntry = function() {
  while (this.itemInfo.item_body.firstChild) {
    this.itemInfo.item_body.removeChild(this.itemInfo.item_body.firstChild);
  }
};

FullFeed.prototype.addEntry = function(entry) {
  var self = this;
  return entry.map(function(i) {
    var pe = document.importNode(i, true);
    self.itemInfo.item_body.appendChild(pe);
    return pe;
  });
};

FullFeed.parser = function(text) {
  var lines = text.split(/\r?\n|\r/);
  var reg = /^([^:]+):(.*)$/;
  var trimspace = function(str) {
    return str.replace(/^\s+|\s+$/g, '');
  };
  var info = {};
  for (var i = lines.length; i > 0; ) {
    if (reg.test(lines[--i])) {
      info[RegExp.$1] = trimspace(RegExp.$2);
    }
  }
  var isValid = function(info) {
    var infoProp = ['url', 'xpath'];
    for (var i = infoProp.length; i > 0; ) {
      if (!info[infoProp[--i]]) {
        return false;
      }
    }
    try {
      new RegExp(info.url);
    } catch (e) {
      return false;
    }
    return true;
  };
  return isValid(info) ? info : null;
};

FullFeed.resetCache = function() {
  message('Resetting cache. Please wait...');
  SITEINFO_IMPORT_URLS.forEach(function(l) {
    var opt = {
      method: 'get',
      url: l,
      headers: {
        "User-Agent": navigator.userAgent + " Greasemonkey (Google Reader Full Feed)"
      },
      onload: function(res) {
        FullFeed.setCache(res, l);
      },
      onerror: function(res) {
        message('Cache Request Error');
      }
    };
    window.setTimeout(GM_xmlhttpRequest, 0, opt);
  });
};

FullFeed.setCache = function(res, url) {
  var info = [];
  var doc = parseHTML(res.responseText);
  var lists = getElementsByXPath(
      '//textarea[@class="ldrfullfeed_data"]', doc);
  lists.forEach(function(list) {
    var data = FullFeed.parser(list.value);
    if (data) {
      info.push(data);
    }
  });
  if (info.length > 0) {
    cacheInfo[url] = {
      url: url,
      info: info
    };
    GM_setValue('cache', cacheInfo.toSource());
    if (WIDGET) FullFeed.createPattern();
    message('Resetting cache. Please wait... Done');
  }
};


FullFeed.getCache = function(key, oDefault) {
  return eval(GM_getValue(key || 'cache', oDefault || "({})"));
};

FullFeed.createPattern = function() {
  if (!WIDGET) return;
  var exps = [];
  for (var i = 0, l = SITE_INFO.length; i < l; exps.push(SITE_INFO[i++].url));
  for (var url in cacheInfo) {
    var site = cacheInfo[url];
    for (var i = 0, l = site.info.length; i < l; exps.push(site.info[i++].url));
  }
  pattern = exps.join('|');
};

FullFeed.registerWidgets = function() {
  if (!WIDGET) return;
  var container = getFirstElementByXPath('id("current-entry")//a[contains(concat(" ",normalize-space(@class)," ")," entry-title-link ")]').parentNode;
  if (!container) return;

  FullFeed.createPattern();
  var description = "\u5168\u6587\u53d6\u5f97\u3067\u304d\u308b\u3088\uff01";

  var c = new getCurrentItem();
  var item = c.item;
  var feed = c.feed;
  if (item.link.match(pattern) || feed.channel.link.match(pattern)) {
    icon = document.createElement('span');
    icon.title = description;
    icon.innerHTML = '<img src="'+ICON+'">';
    w.addClass(icon, 'gm_fullfeed_checked');
    container.appendChild(document.createTextNode(' '));
    container.appendChild(icon);
  }
};

FullFeed.documentFilters = [
// addTargetAttr
(function(doc) {
  var anchors = getElementsByXPath('descendant-or-self::a', doc);
  if (anchors) {
    anchors.forEach(function(i) {
      i.target = '_blank';
    });
  }
}),
];

FullFeed.filters = [];

window.FullFeed = {};

window.FullFeed.addDocumentFilter = function(f) {
  FullFeed.documentFilters.push(f);
};

window.FullFeed.addFilter = function(f) {
  FullFeed.filters.push(f);
};

w.get_active_item = function(flag) {
  var item = {};
  var exp = 'id("current-entry")//a[contains(concat(" ",normalize-space(@class)," ")," entry-title-link ")]';
  try {
    item.link =  getFirstElementByXPath(exp).href;
    item.title = getFirstElementByXPath(exp).textContent;
  } catch (e) {}
  return item;
};

w.get_active_feed = function() {
  var feed = {};
  feed.channel = {};
  try {
    feed.channel.link = decodeURIComponent(getFirstElementByXPath('id("current-entry")//a[contains(concat(" ",normalize-space(@class)," ")," entry-source-title ")]').href.replace(/^.*\/(?=http)/, ''));
  } catch (e) {}
  return feed;
};

var hasClass = w.hasClass = function(element, classname) {
  var cl = element.className;
  var cls = cl.split(/\s+/);
  return cls.indexOf(classname) != -1;
};

var toggleClass = w.toggleClass = function(element, classname) {
  hasClass(element, classname) ? removeClass(element, classname) : addClass(element, classname);
};

var removeClass = w.removeClass = function(element, classname) {
  var cl = element.className;
  var cls = cl.split(/\s+/);
  element.className = cls.remove(classname).join(" ");
};

var addClass = w.addClass = function(element, classname) {
  var cl = element.className;
  if (!contain(cl, classname)) {
    element.className += " " + classname;
  }
};

var contain = w.contain = function(self, other) {
  if (isString(self) && isString(other)) {
    return self.indexOf(other) != -1;
  }
  if (isRegExp(other)) {
    return other.test(self);
  }
};

var isString = w.isString = function(obj) {
  return typeof obj == "string" || obj instanceof String;
};

var isRegExp = w.isRegExp = function(obj) {
  return obj instanceof RegExp;
};

Array.prototype.remove = function(to_remove) {
  return this.filter(function(val) { return val != to_remove; });
};

// itemの情報を格納するobjectのconstructor
var getCurrentItem = function() {
  this.item = w.get_active_item(true);
  this.feed = w.get_active_feed();
  this.itemURL = this.item.link;
  this.feedURL = this.feed.channel.link;
  this.id = this.item.id;
  this.item_container = getFirstElementByXPath('id("current-entry")//ins/div');
  this.title = this.item.title;
  this.find = false;
};

var launchFullFeed = function(list, c) {
  if (!list) return;
  for (var i = 0, l = list.length; i < l; i++) {
    var reg = new RegExp(list[i].url);
    if (reg.test(c.itemURL) || reg.test(c.feedURL)) {
      c.find = true;
      var ff = new FullFeed(list[i], c);
      break;
    }
  }
};

var init = function() {
  var c = new getCurrentItem();
  if (!c.title) return;
  if (ADCHECKER.test(c.title)) {
    return message('This entry is advertisement');
  }
  if (w.hasClass(c.item_container, 'gm_fullfeed_loaded')) {
    return message('This entry has been already loaded.');
  }

  launchFullFeed(SITE_INFO, c);

  if (!c.find) {
    for (var i = 0, l = SITEINFO_IMPORT_URLS.length; i < l && !c.find;
      launchFullFeed(cacheInfo[SITEINFO_IMPORT_URLS[i++]].info, c));
  }

  if (!c.find) {
    message('This entry is not listed on SITE_INFO');
    if (OPEN) window.open(c.itemURL) || message('Cannot popup');
  }
};

var cacheInfo = FullFeed.getCache();
var pattern;

GM_registerMenuCommand('Google Reader Full Feed - reset cache', FullFeed.resetCache);
GM_registerMenuCommand('フィード自動読み切り替え - feed autoread switch',switchAutoFetch);

if (LOADING_MOTION) addStyle(CSS, 'gm_fullfeed');

document.addEventListener('keyup', function(e) {
  if (e.keyCode == w.KeyEvent.DOM_VK_Z) {
    if (e.shiftKey)
      FullFeed.resetCache();
    else if(e.ctrlKey)
      switchAutoFetch();
    else
      init();
  }
}, false);

var timer = setTimeout(function() {
  if (timer) clearTimeout(timer);
  try {
    var container = getFirstElementByXPath('id("current-entry")//a[contains(concat(" ",normalize-space(@class)," ")," entry-title-link ")]');
    var icon = getFirstElementByXPath('id("current-entry")//span[contains(concat(" ",normalize-space(@class)," ")," gm_fullfeed_checked ")]');
    if (container && !icon) {
      FullFeed.registerWidgets();
    }
  } catch (e) {}
  timer = setTimeout(arguments.callee, 200);
});

// == [Utility] =====================================================
function removeXSSRisks(htmldoc) {
  var attr = "allowScriptAccess";

  Array.forEach(htmldoc.getElementsByTagName("embed"), function(embed) {
    embed.hasAttribute(attr) && embed.setAttribute(attr, "never");
  });
  Array.forEach(htmldoc.getElementsByTagName("param"), function(param) {
    param.getAttribute("name") == attr && param.setAttribute("value", "never");
  });
}

function relativeToAbsolutePath(htmldoc, base) {
  var top = base.match("^https?://[^/]+")[0];
  var current = base.replace(/\/[^\/]+$/, '/');

  Array.forEach(htmldoc.getElementsByTagName("a"), function(a) {
    if (a.getAttribute("href"))
      a.href = _rel2abs(a.getAttribute("href"), top, current);
  });
  Array.forEach(htmldoc.getElementsByTagName("img"), function(img) {
    if (img.getAttribute("src"))
      img.src = _rel2abs(img.getAttribute("src"), top, current);
  });
}

function _rel2abs(url, top, current) {
  if (url.match("^https?://")) {
    return url;
  } else if (url.indexOf("/") == 0) {
    return top + url;
  } else {
    return current + url;
  }
}

// copied from FLASH KEY (c) id:brazil
// http://userscripts.org/scripts/show/11996
// slightly modified.
var FlashMessage = new function() {
  addStyle(<><![CDATA[
    #FLASH_MESSAGE {
      position : fixed;
      font-size : 200%;
      z-index : 10000;

      padding : 20px 50px 20px 50px;
      left : 1em;
      top : 1em;

      background-color : #444;
      color : #FFF;
      -moz-border-radius: 0.3em;
      min-width : 1em;
      text-align : center;
    }
  ]]></>.toString()
        .replace(/^\ {4}/gm, ""));
  var opacity = 0.9;
  var flash = document.createElement('div');
  flash.id = 'FLASH_MESSAGE';
  hide(flash);
  document.documentElement.appendChild(flash);
  var canceler;
  this.showFlashMessageWindow = function(string, duration) {
    duration = duration || 400;
    canceler && canceler();
    flash.innerHTML = string;
    flash.style.MozOpacity = opacity;
    show(flash);

    canceler = callLater(function() {
      canceler = tween(function(value) {
        flash.style.MozOpacity = opacity * (1 - value);
      }, 100, 5);
    }, duration);
  };

  // ----[Utility]-------------------------------------------------
  function callLater(callback, interval) {
    var timeoutId = setTimeout(callback, interval);
    return function() {
      clearTimeout(timeoutId);
    };
  }
  function tween(callback, span, count) {
    count = count || 20;
    var interval = span / count;
    var value = 0;
    var calls = 0;
    var intervalId = setInterval(function() {
      callback(calls / count);

      if (count == calls) {
        canceler();
        return;
      }
      calls++;
    }, interval);
    var canceler = function() {
      clearInterval(intervalId);
      hide(flash);
    };
    return canceler;
  }
  function hide(target) {
    target.style.display = 'none';
  }
  function show(target, style) {
    target.style.display = style || '';
  }
};

function message(mes) {
  //w.message(""); w.message(mes);
  //console.log(mes);
  FlashMessage.showFlashMessageWindow("");
  FlashMessage.showFlashMessageWindow(mes, 1000);
}

// copied from AutoPagerize (c) id:swdyh
function getElementsByXPath(xpath, node) {
  var node = node || document;
  var nodesSnapshot = (node.ownerDocument || node).
    evaluate(xpath, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  var data = []
  for (var i = 0, l = nodesSnapshot.snapshotLength; i < l;
    data.push(nodesSnapshot.snapshotItem(i++)));
  return data.length > 0 ? data : null;
}

function getFirstElementByXPath(xpath, node) {
  var node = node || document;
  var result = (node.ownerDocument || node).
    evaluate(xpath, node, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
  return result.singleNodeValue ? result.singleNodeValue : null;
}

if (AUTO_FETCH) {
	message('Auto_Fetch:ON');
    interval = window.setInterval(
       function() {
			var c = new getCurrentItem();
			if(!w.hasClass(c.item_container, 'gm_fullfeed_loaded')){
                init();
            }
        },
        500
    );
}

// copied from Pagerization (c) id:ofk
function parseHTML(str) {
  str = str.replace(/^[\s\S]*?<html[^>]*>|<\/html\s*>[\s\S]*$/ig, '');
  var res = document.implementation.createDocument(null, 'html', null);
  var range = document.createRange();
  range.setStartAfter(document.body);
  res.documentElement.appendChild(
    res.importNode(range.createContextualFragment(str), true)
  );
  return res;
}

function pathToURL(url, path) {
  var re = path.indexOf("/") == 0
         ? /^([a-zA-Z]+:\/\/[^\/]+)\/.*$/
         : /^(.*\/).*$/;
  return url.replace(re, "$1" + path);
}

// copied from LDRize (c) id:snj14
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.appendChild(link);
}

})(this.unsafeWindow || this);