// Vimperator plugin: "Update Wassr" // Last Change: 19-Jan-2009. Jan 2008 // License: Creative Commons // Maintainer: mattn - http://mattn.kaoriya.net/ // Based On: twitter.js by Trapezoid // // The script allows you to update Wassr status from Vimperator 0.6.*. // // Commands: // :wassr some thing text // post "some thing text" to wassr. // :wassr! someone // show someone's statuses. // :wassr!? someword // show search result of 'someword' from "http://labs.ceek.jp/wassr/". // :wassr!@ // show replies. // :wassr!+ someone // fav someone's last status.. mean put iine. // :wassr!- someone // un-fav someone's last status.. mean remove iine. // :wassr -footmark // show footmarks. // :wassr -todo // show your todos. // :wassr -todo+ some thing text // add 'some thing text' to your todo. // :wassr -todo- todo-id // remove todo which id is todo-id. // :wassr -todo* todo-id // start todo which id is todo-id. // :wassr -todo/ todo-id // stop todo which id is todo-id. // :wassr -todo! todo-id // done todo which id is todo-id. (function(){ var passwordManager = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager); var evalFunc = window.eval; try { var sandbox = new Components.utils.Sandbox(window); if (Components.utils.evalInSandbox("true", sandbox) === true) { evalFunc = function(text) { return Components.utils.evalInSandbox(text, sandbox); } } } catch(e) { liberator.log('warning: wassr.js is working with unsafe sandbox.'); } function sprintf(format){ var i = 1, re = /%s/, result = "" + format; while (re.test(result) && i < arguments.length) result = result.replace(re, arguments[i++]); return result; } function emojiConv(str){ return str.replace(/[^*+.-9A-Z_a-z-]/g,function(s){ var c = s.charCodeAt(0); return (0xE001 <= c && c <= 0xF0FC) ? '' : s; }) } function sayWassr(username, password, stat){ var xhr = new XMLHttpRequest(); xhr.open("POST", "http://api.wassr.jp/statuses/update.json", false, username, password); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.send("status=" + encodeURIComponent(stat) + "&source=" + encodeURIComponent("vimperator/wassr.js")); } function showFollowersStatus(username, password, target){ var xhr = new XMLHttpRequest(); var endPoint = target ? "http://api.wassr.jp/user_timeline.json?id=" + target : "http://api.wassr.jp/statuses/friends_timeline.json?id=" + username; xhr.open("GET", endPoint, false, username, password); xhr.setRequestHeader("User-Agent", "XMLHttpRequest"); // for debug //xhr.open("GET", "http://api.wassr.jp/statuses/user_timeline/otsune.json", false, username, password); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.send(null); var statuses = evalFunc(xhr.responseText) || []; var html = .toSource() .replace(/(?:\r?\n|\r)[ \t]*/g, " ") + statuses.map(function(status) <> {status.user.screen_name} {status.user_login_id}‬ .toSource() .replace(/(?:\r?\n|\r)[ \t]*/g, " ") + sprintf(': %s‬', status.html)) .join("
"); liberator.echo(html, true); } function favWassr(username, password, user){ var xhr = new XMLHttpRequest(); xhr.open("POST", "http://api.wassr.jp/user_timeline.json?id=" + user, false, username, password); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.send(null); xhr.open("POST", "http://api.wassr.jp/favorites/create/" + evalFunc(xhr.responseText)[0].rid + ".json", false, username, password); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.send(null); } function unfavWassr(username, password, user){ var xhr = new XMLHttpRequest(); xhr.open("POST", "http://api.wassr.jp/user_timeline.json?id=" + user, false, username, password); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.send(null); xhr.open("POST", "http://api.wassr.jp/favorites/destroy/" + evalFunc(xhr.responseText)[0].rid + ".json", false, username, password); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.send(null); } function showWassrReply(username, password){ var xhr = new XMLHttpRequest(); xhr.open("POST", "http://api.wassr.jp/statuses/replies.json", false, username, password); xhr.setRequestHeader("User-Agent", "XMLHttpRequest"); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.send(null); var statuses = evalFunc(xhr.responseText); var html = .toSource() .replace(/(?:\r?\n|\r)[ \t]*/g, " ") + statuses.map(function(status) <> {status.user.screen_name} {status.user_login_id}‬ .toSource() .replace(/(?:\r?\n|\r)[ \t]*/g, " ") + sprintf(': %s‬', status.html)) .join("
"); //liberator.log(html); liberator.echo(html, true); } function showWassrSearchResult(word){ var xhr = new XMLHttpRequest(); xhr.open("GET", "http://labs.ceek.jp/wassr/rss?k=" + encodeURIComponent(word), false); xhr.send(null); var items = xhr.responseXML.getElementsByTagName('item'); var html = .toSource() .replace(/(?:\r?\n|\r)[ \t]*/g, " "); for (var n = 0; n < items.length; n++) html += <> {items[n].getElementsByTagName('title')[0].textContent.replace(/>/g, '>').replace(/</g, '<').replace(/^%/, '')}‬ : {items[n].getElementsByTagName('description')[0].textContent.replace(/>/g, '>').replace(/</g, '<')}‬
.toSource() .replace(/(?:\r?\n|\r)[ \t]*/g, " "); liberator.echo(html, true); } function todoAction(username, password, arg){ var xhr = new XMLHttpRequest(); if (arg.match(/\+ (.*)/)) { xhr.open("POST", "http://api.wassr.jp/todo/add.json", false, username, password); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.send("body=" + encodeURIComponent(RegExp.$1)); } else if (arg.match(/- (.*)/)) { xhr.open("POST", "http://api.wassr.jp/todo/delete.json", false, username, password); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.send("todo_rid=" + encodeURIComponent(RegExp.$1)); } else if (arg.match(/\* (.*)/)) { xhr.open("POST", "http://api.wassr.jp/todo/start.json", false, username, password); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.send("todo_rid=" + encodeURIComponent(RegExp.$1)); } else if (arg.match(/\/ (.*)/)) { xhr.open("POST", "http://api.wassr.jp/todo/stop.json", false, username, password); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.send("todo_rid=" + encodeURIComponent(RegExp.$1)); } else if (arg.match(/! (.*)/)) { xhr.open("POST", "http://api.wassr.jp/todo/done.json", false, username, password); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.send("todo_rid=" + encodeURIComponent(RegExp.$1)); } xhr.open("GET", "http://api.wassr.jp/todo/list.json", false, username, password); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.send(null); var todos = evalFunc(xhr.responseText); var html = .toSource() .replace(/(?:\r?\n|\r)[ \t]*/g, " ") + todos.map(function(todo) <> todo {todo.todo_rid} .toSource() .replace(/(?:\r?\n|\r)[ \t]*/g, " ") + sprintf(': %s', todo.body)) .join("
"); liberator.echo(html, true); } function footmarkAction(username, password){ var xhr = new XMLHttpRequest(); xhr.open("GET", "http://api.wassr.jp/footmark/recent.json", false, username, password); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.send(null); var footmarks = evalFunc(xhr.responseText); var html = .toSource() .replace(/(?:\r?\n|\r)[ \t]*/g, " ") + footmarks.map(function(footmark) <> {footmark.nick} {footmark.login_id}‬ .toSource() .replace(/(?:\r?\n|\r)[ \t]*/g, " ")).join("
"); liberator.echo(html, true); } function footmarkAction(username, password){ var xhr = new XMLHttpRequest();
let PLUGIN_INFO =
<VimperatorPlugin>
<name>{NAME}</name>
<description>Show/Open related blog</description>
<description lang="ja">現在ページへリンクしているブログを表示または開くプラグイン</description>
<author email="teramako@gmail.com" homepage="http://vimperator.g.hatena.ne.jp/teramako/">teramako</author>
<version>1.0</version>
<license>MPL 1.1/GPL 2.0/LGPL 2.1</license>
<minVersion>2.0pre</minVersion>
<maxVersion>2.0</maxVersion>
<updateURL>http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/relatedBlogSearch.js</updateURL>
<detail lang="ja"><![CDATA[
現在開いているページへリンクしているブログを探しだし表示または開くことを目的とするプラグイン

== Command ==

:relatedblog:
  現在ページにリンクしているブログをリストします
:relatedblog -q[uery] {searchTerm}:
  {searchTerm} で検索してブログをりすとします
:relatedblog {URL}:
  {URL} を開きます
  補完から取ってくると良いと思います

>||
tab relatedblog {URL}
||<
とすることで新規タブに開けます
またリストを表示した後";o" などのヒントからも開けるでしょう(historyコマンドに似ている)

== 変数 ==
let コマンドで設定してください(しない場合はデフォルト値が使用されます)

:g:blogSearchHeaderTemplate:
  リストを表示する時のヘッダとなるXML文字列
  省略時は以下が使用されます
  >||
  '<table><tr><th>Title</th><th>Author</th><th>Content</th></tr></table>';
  ||<
:g:blogSearchBodyTemplate:
  リストを表示する時のコンテンツとなるXML文字列
  省略時は以下が使用されます
  >||
  '<tr><td><a highlight="URL" href="%link%">%title%</a></td><td>%author%</td><td>%content%</td></tr>';
  ||<

また%で囲まれた特定の文字列が変数として使用されます
%link%:
  対象ページのURL
%title%:
  対象ページのタイトル
%author%:
  対象ページの著者
%content%:
  対象ページの内容(一部)
%rootURI%:
  対象ページのトップページのURL
%id%:
  不明ドメイン日付パスが含まれる
%published%:
  投稿された日時(%Y-%m-%dT%H:%M:%SZ)
%updated%:
  更新された日時(%Y-%m-%dT%H:%M:%SZ)
]]></detail>
</VimperatorPlugin>;

liberator.plugins.relatedBlogSearch = (function(){
const LANG = window.navigator.language;
const BLOGSEARCH_URL = 'http://blogsearch.google.com/blogsearch_feeds?output=atom&hl=' + LANG + '&q=';

/**
 * Serach with Google blogsearch and get entries
 * @param {String} name query string
 */
function BlogSearch() { this._init.apply(this, arguments); }
BlogSearch.prototype = {
  _init: function(name){
    if (!name)
      throw Components.results.NS_ERROR_XPC_NOT_ENOUGH_ARGS;

    this.date = new Date();
    this.name = name;
    this.query = BLOGSEARCH_URL + this.name;
    let self = this;
    this.items = Array.map(
      util.httpGet(this.query).responseXML.getElementsByTagName("entry"),
      function(entry) self._parse(entry)
    );
  },
  /**
   * @param {Element} entryElm Atom entry element
   * @return {Object}
   */
  _parse: function(entryElm){
    let entry = {};
    Array.forEach(entryElm.childNodes, function(elm){
      let tagName = elm.localName;
      switch(tagName){
        case 'link':
          entry.link = elm.getAttribute('href'); break;
        case 'author':
          entry.author = elm.childNodes[0].textContent;
          entry.rootURI = elm.childNodes[1].textContent;
          break;
        default:
          entry[tagName] = elm.textContent;
      }
    });
    return entry;
  },
  /**
   * @param {RegExp} reg
   * @param {String[]} itemNames item's property names
   * @return {Object[]} item list
   */
  search: function(reg, itemNames){
    if (!itemNames) itemNames = [name for (name in this.items[0])];
    return this.items.filter(function(item) itemNames.some(function(name) reg.test(item[name])));
  },
  /**
   * @param {String} template
   * @param {XMLList} xml header XML
   * @param {Object[]} items if omitted, used all items
   * template e.g.)
   *  '<tr><td><a href="link">%title%</a></td><td>%author%</td><td>%content%</td></tr>'
   */
  toXMLByTemplate: function(template, xml, items){
    if (!items) items = this.items;

    function entryToXML(item){
      function replacer(all, name) name in item ? item[name] : all;
      return new XMLList(template.replace(/%(\w+?)%/g, replacer).replace(/&/g,"&amp;"));
    }
    items.forEach(function(item){xml.* += entryToXML(item);});
    return xml;
  },
};

/**
 * -query option completion list
 * e.g.)
 * return [
 *  ["link:https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects", "-"],
 *  ["link:https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference", "-"],
 *  ["link:https://developer.mozilla.org/en", "-"],
 *  ["link:https://developer.mozilla.org", "-"],
 * ]
 * when current url is "https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects"
 * @return {String[][]}
 */
function getQueryList(){
  return buffer.URL.split(/\/(?!\/)/).reduce(function(p,c){
    p.length == 0 ? p.push(c) : p.push(p[p.length-1]+"/"+c);
    return p;
  },[]).splice(1).map(function(url) ["link:" + url, "-"]).reverse();
}

// ----------------------------------------------
// command
// ----------------------------------------------
commands.addUserCommand(["relatedblog"], "search linked blog from google blogsearch",
function(args){
  let query = args["-query"] ? args["-query"] : "link:" + buffer.URL;
  let entry = manager.getCache(query);
  let header = new XMLList(manager.headerTemplate);
  if (args.length == 0){
    let xml  = entry.toXMLByTemplate(manager.bodyTemplate, header);
    liberator.echo(xml, true);
  } else {
    let reg = new RegExp(args[0], "i");
    let items = entry.search(reg);
    if (items.length > 1){
      liberator.echo(entry.toXMLByTemplate(manager.bodyTemplate, header, items),true);
    } else if (items.length == 1){
      let url = items[0].link;
      liberator.open(url);
    } else {
      liberator.echomsg("related blog is none",5);
    }
  }
},{
  options: [
    [["-query", "-q"], commands.OPTION_STRING, null, getQueryList]
  ],
  argCount: "?",
  completer: function(context, args){
    manager.completer(context, args);
  },
},true);

// ----------------------------------------------
// public
// ----------------------------------------------
var manager = {
  get bodyTemplate(){
    return liberator.globalVariables.blogSearchBodyTemplate ?
      liberator.globalVariables.blogSearchBodyTemplate :
      '<tr><td><a highlight="URL" href="%link%">%title%</a></td><td>%author%</td><td>%content%</td></tr>';
  },
  get headerTemplate(){
    return liberator.globalVariables.blogSearchHeaderTemplate ?
      liberator.globalVariables.blogSearchHeaderTemplate :
      '<table><tr><th>Title</th><th>Author</th><th>Content</th></tr></table>';
  },
  /**
   * @type {BlogSerach[]}
   */
  cache: [],
  /**
   * キャッシュからエントリを取得
   * キャッシュがないまたは期限切れの場合は新たに取得
   * キャッシュのリストは10個まで XXX: 外だししたほうが良いかもしれない
   * XXX: 有効期限を変数に持たせた方が良いかもしれない
   * @param {String} name query string
   * @return {BlogSearch}
   */
  getCache: function(name){
    let cache = this.cache.filter(function(entry) entry.name == name);
    if (cache.length == 0){
      let entry = new BlogSearch(name);
      if(this.cache.push(entry) > 10)
        this.cache.shift();

      return entry;
    } else if (cache[0].date < Date.now() - 5*60*1000){
      cache[0]._init(name);
    }
    return cache[0];
  },
  /**
   * relatedblog コマンド用の補完関数
   * @param {CompletionContext} context
   * @param {String[]} args
   */
  completer: function(context, args){
    let query = args["-query"] ? args["-query"] : "link:" + buffer.URL;
    let entry = this.getCache(query)
    context.title = ["URL", "Title"];
    if (args.length == 0){
      context.completions = entry.items.map(function(item) [item.link, item.title]);
    } else {
      let reg = new RegExp(context.filter, "i");
      context.completions = entry.search(reg).map(function(item) [item.link, item.title]);
    }
  },
};
return manager;
})();
// vim: sw=2 ts=2 et: