297// Vimperator plugin: "Update Haiku"
// Last Change: 12-Jan-2009. Jan 2008
// License: Creative Commons
// Maintainer: mattn <mattn.jp@gmail.com> - http://mattn.kaoriya.net/
//
// The script allows you to update Haiku status from Vimperator.
//
// Commands:
// :haiku some thing text
// post "some thing text" to keyword 'id:username' on Hatena Haiku.
// :haiku #keyword some thing text
// post "some thing text" to keyword 'id:keyword' on Hatena Haiku.
// :haiku!/
// show public timeline.
// :haiku! someone
// show someone's statuses.
// :haiku! album
// show album timeline.
// :haiku!+ someone
// fav someone's last status.. mean put Hatena Star.
// :haiku!- someone
// un-fav someone's last status.. mean remove Hatena Star.
// :haiku! #keyword
// show the keyword timeline.
var PLUGIN_INFO =
<VimperatorPlugin>
<name>{NAME}</name>
<description>Hatena Haiku Client</description>
<author mail="mattn.jp@gmail.com" homepage="http://mattn.kaoriya.net">mattn</author>
<license>Creative Commons</license>
<minVersion>2.0a1</minVersion>
<maxVersion>2.0</maxVersion>
<updateURL>https://github.com/vimpr/vimperator-plugins/raw/master/haiku.js</updateURL>
<detail><![CDATA[
The script allows you to update Haiku status from Vimperator.
== Commands ==
:haiku some thing text:
post "some thing text" to keyword 'id:username' on Hatena Haiku.
:haiku #keyword some thing text:
post "some thing text" to keyword 'id:keyword' on Hatena Haiku.
:haiku!/:
show public timeline.
:haiku! someone:
show someone's statuses.
:haiku! album:
show album timeline.
:haiku!+ someone:
fav someone's last status.. mean put Hatena Star.
:haiku!- someone:
un-fav someone's last status.. mean remove Hatena Star.
:haiku! #keyword:
show the keyword timeline.
]]></detail>
</VimperatorPlugin>;
(function(){
liberator.plugins.haiku = {
get cache() statuses
};
var passwordManager = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
var CLIENT_NAME = encodeURIComponent(config.name + "::plugin::haiku.js");
var evalFunc = window.eval;
var statuses = null;
try {
var sandbox = new Components.utils.Sandbox("about// ==VimperatorPlugin==
// @name Auto Detect Link
// @description-ja (次|前)っぽいページへのリンクを探してジャンプ
// @license Creative Commons 2.1 (Attribution + Share Alike)
// @version 1.4
// @author anekos (anekos@snca.net)
// ==/VimperatorPlugin==
//
// Usage:
// デフォルトの設定では、"]]" "[[" を上書きします。
// ]] 次っぽいページへ
// [[ 前っぽいページへ
//
// Setting:
// liberator.globalVariables.autoDetectLink
// nextPatterns:
// backPatterns:
// (次|前)のパターンの配列。
// 要素は、
// ・リンク文字列に対する(正規表現|文字列)
// ・リンクに対する関数のリスト
// nextMappings:
// backPatterns:
// (次|前)移動のマッピング(Array)
// useNextHistory:
// useBackHistory:
// 履歴を併用。
// 履歴がある場合はそっちを優先します。
// useSuccPattern:
// doc_01.html のときは、 doc_02.html を次と見なす…ようなパターン。
// ファイル名に当たる部分の、数字列あるいは一文字のアルファベットが対象です。
// (つながっているアルファベットは無視されます。)
// doc_02.html => doc_03.html
// doc_a.html => doc_b.html
// force:
// (次|前)っぽいURIを捏造してそこに移動します。
// useAutoPagerize:
// AutoPagerize のキャッシュを利用します。
// (ただし、"次" へのリンクにしか使われません)
//
// example:
// :js liberator.globalVariables.autoDetectLink = {nextPatterns: [/next/, /次/]}
//
// Function:
// (次|前)へのリンクを検出する。
// liberator.plugins.autoDetectLink.detect(next, setting)
// next: 次のリンクを探すときは、true。
// setting: 設定を一時的に上書きする。省略可。
// return: リンクのURIなどを含んだオブジェクト
// uri: アドレス。
// text: リンクテキストなど
// frame: リンクの存在するフレームの Window オブジェクト
// element: リンクの要素
//
// (次|前)へのリンクに移動。
// liberator.plugins.autoDetectLink.go(next, setting)
// 引数は detect と同じ。
//
// example:
// 履歴を使用しないで、前のリンクを探す。
// liberator.plugins.autoDetectLink.detect(false, {useBackHistory: false});
//
// Note:
// 単純なリンクと、フォームのボタンを検出できます。
//
// License:
// http://creativecommons.org/licenses/by-sa/2.1/jp/
// http://creativecommons.org/licenses/by-sa/2.1/jp/deed
//
// TODO:
// input / form
// history
(function () {
liberator.log('auto_detect_link.js loading');
////////////////////////////////////////////////////////////////
// default setting
////////////////////////////////////////////////////////////////
let defaultSetting = {
nextPatterns: [
//[NnNn][EeEe][XxXx][TtTt]/,
/[Nn\uff2e\uff4e][Ee\uff25\uff45][Xx\uff38\uff58][Tt\uff34\uff54]/,
//[FfFf](?:[OoOo][RrRr])?[WwWw](?:[AaAa][RrRr])?[DdDd]/,
/[Ff\uff26\uff46](?:[Oo\uff2f\uff4f][Rr\uff32\uff52])?[Ww\uff37\uff57](?:[Aa\uff21\uff41][Rr\uff32\uff52])?[Dd\uff24\uff44]/,
//^\s*(?:次|つぎ)[への]/, /つづく|続/, /次|つぎ/, /進む/,
/^\s*(?:\u6b21|\u3064\u304e)[\u3078\u306e]/, /\u3064\u3065\u304f|\u7d9a/, /\u6b21|\u3064\u304e/, /\u9032\u3080/,
//^\s*>\s*$/, />+|≫/
/^\s*>\s*$/, />+|\u226b/
],
backPatterns: [
//[BbBb][AaAa][CcCc][KkKk]/, /[PpPp][RrRr][EeEe][VvVv]/,
/[Bb\uff22\uff42][Aa\uff21\uff41][Cc\uff23\uff43][Kk\uff2b\uff4b]/, /[Pp\uff30\uff50][Rr\uff32\uff52][Ee\uff25\uff45][Vv\uff36\uff56]/,
//^\s*前[への]/, /前/, /戻る/,
/^\s*\u524d[\u3078\u306e]/, /\u524d/, /\u623b\u308b/,
//^\s*<\s*$/, /<+|≪/
/^\s*<\s*$/, /<+|\u226a/
],
nextMappings: [']]'],
backMappings: ['[['],
useSuccPattern: true,
useNextHistory: false,
useBackHistory: false,
//clickButton: true,
force: false,
useAutoPagerize: true,
displayDelay: 500,
};
////////////////////////////////////////////////////////////////
// setting
////////////////////////////////////////////////////////////////
let _gv;
// 評価を遅延するために関数にしておく
function gv () {
if (_gv)
return _gv;
if (liberator.globalVariables) {
if (!liberator.globalVariables.autoDetectLink)
liberator.globalVariables.autoDetectLink = {};
_gv = liberator.globalVariables.autoDetectLink;
}
for (let key in defaultSetting) {
if (_gv[key] == undefined)
_gv[key] = defaultSetting[key];
}
return _gv;
}
const APPREF = 'greasemonkey.scriptvals.http://swdyh.yu.to//AutoPagerize.cacheInfo';
let ap_cache = eval(Application.prefs.getValue(APPREF, null));
for each (let cache in ap_cache) {
cache.info = cache.info.filter(function (i) 'url' in i);
cache.info.sort(function (a, b) b.url.length - a.url.length);
}
////////////////////////////////////////////////////////////////
// functions
////////////////////////////////////////////////////////////////
// 空白を
function removeSpace (str)
str.replace(/^\s+|\s+$/g, '').replace(/\s+/g, ' ');
// Array#find
function find (ary, f) {
var func = (typeof f == 'function') ? f : function (v) v == f;
for (let i = 0, l = ary.length; i < l; i++) {
if (func(ary[i])) {
return ary[i];
}
}
return null;
}
// 要素をクリックする
function clickElement (elem) {
var e = content.document.createEvent('MouseEvents');
e.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
elem.dispatchEvent(e);
}
// 開いたURIなどの表示
function displayOpened (link) {
var msg = 'open: ' + link.type + ' <' + removeSpace(link.text) + '> ' + link.uri;
setTimeout(function () liberator.echo(msg, commandline.FORCE_SINGLELINE), gv().displayDelay);
}
// リンクを開く
function open (link) {
if (link.element) {
clickElement(link.element);
} else if (link.uri) {
link.frame.location.href = link.uri;
}
displayOpened(link);
}
// 元の文字列、詰め込む文字、長さ
function padChar (s, c, n)
s.replace(new RegExp('^(.{0,'+(n-1)+'})$'), function (s) padChar(c+s, c, n));
// (次|前)の数字文字列リストを取得
function succNumber (n, next) {
var m = (parseInt(n || 0, 10) + (next ? 1 : -1)).toString();
var result = [m];
if (m.length < n.length)
result.unshift(padChar(m.toString(), '0', n.length));
return result;
}
// (次|前)の文字列リストを取得
function succString (s, next) {
var result = [], d = next ? 1 : -1;
var c = String.fromCharCode(s.charCodeAt(0) + d);
if (('a' <= c && c <= 'z') || 'A' <= c && c <= 'Z')
result.push(c);
return result;
}
// (次|前)のURIリストを取得
function succURI (uri, next) {
var urim = uri.match(/^(.+\/)([^\/]+)$/);
if (!urim)
return [];
var [_, dir, file] = urim, result = [];
// succ number
let (dm, file = file, left = '', temp = []) {
while (file && (dm = file.match(/\d+/))) {
let [rcontext, lcontext, lmatch] = [RegExp.rightContext, RegExp.leftContext, RegExp.lastMatch];
left += lcontext;
succNumber(lmatch, next).forEach(function (succ) {
temp.push(dir + left + succ + rcontext);
});
left += lmatch;
file = rcontext;
}
result = result.concat(temp.reverse());
}
// succ string
let (dm, file = file, left = '', temp = []) {
while (file && (dm = file.match(/(^|[^a-zA-Z])([a-zA-Z])([^a-zA-Z]|$)/))) {
let [rcontext, lcontext] = [RegExp.rightContext, RegExp.leftContext];
left += lcontext + dm[1];
succString(dm[2], next).forEach(function (succ) {
temp.push(dir + left + succ + dm[3] + rcontext);
});
left += dm[1];
file = dm[3] + rcontext;
}
result = result.concat(temp.reverse());
}
return result;
}
// パターンマッチング
function match (pattern, link)
pattern instanceof Function ? pattern(link) :
!link.text ? null :
pattern instanceof RegExp ? pattern.test(link.text) :
link.text.toLowerCase().indexOf(pattern.toString().toLowerCase()) >= 0;
// 要素が表示されているか?
function isVisible (element) {
var st;
try {
st = content.document.defaultView.getComputedStyle(element, null);
return !(st.display && st.display.indexOf('none') >= 0) && (!element.parentNode || isVisible(element.parentNode))
} catch (e) {
return true;
}
}
// リンクのフィルタ
function linkElementFilter (elem)
isVisible(elem) && elem.href && elem.href.indexOf('@') < 0 && /^(?:(?:https?|f(?:ile|tp)):\/\/|javascript:)/.test(elem.href) && elem.textContent;
// 全てのリンクを取得
// 再帰的にフレーム内のも取得する
function getAllLinks (content) {
var result = [];
// Anchor
var elements = content.document.links;
for (let i = 0, l = elements.length; i < l; i++) {
let it = elements[i];
if (linkElementFilter(it))
result.push({
type: 'link',
frame: content,
uri: it.href,
rel: it.rel,
text: it.textContent,
element: it
});
}
// Form
elements = content.document.getElementsByTagName('input');
for (let i = 0, l = elements.length; i < l; i++) {
(function (input) {
result.push({
type: 'input',
frame: content,
uri: input.form && input.form.action,
text: input.value,
click: input.click,
element: input,
});
})(elements[i]);
}
// Frame
if (content.frames) {
for (let i = 0, l = content.frames.length; i < l; i++) {
result = result.concat(getAllLinks(content.frames[i]));
}
}
return result;
}
// 全フレームの URL を得る
function getAllLocations (content) {
let result = [content.location.href];
if (content.frames) {
for (let i = 0, l = content.frames.length; i < l; i++) {
result = result.concat(getAllLocations(content.frames[i]));
}
}
return result;
}
// 上書きした設定を返す。
function getCurrentSetting (setting) {
if (!setting)
setting = {};
for (let n in gv()) {
if (setting[n] == undefined)
setting[n] = gv()[n];
}
return setting;
}
// 相対アドレスから絶対アドレスに変換するんじゃないの?
function toAbsPath (path) {
with (content.document.createElement('a'))
return (href = path) && href;
}
// AutoPagerize のデータからマッチする物を取得
function getAutopagerizeNext () {
if (!ap_cache)
return;
var info = (function () {
var uri = buffer.URL;
for each (let cache in ap_cache) {
for (let i = 0, l = cache.info.length; i < l; i++) {
let info = cache.info[i];
if (uri.match(info.url))
return info;
}
}
})();
if (!info)
return;
var doc = content.document;
var result = doc.evaluate(info.nextLink, doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
if (result.singleNodeValue)
return result.singleNodeValue;
}
////////////////////////////////////////////////////////////////
// main
////////////////////////////////////////////////////////////////
// リンクを探す
function detect (next, setting) {
try {
setting = getCurrentSetting(setting);
// TODO
if (setting.useAutoPagerize && next) {
let apnext = getAutopagerizeNext();
if (apnext) {
return {
type: 'aplink',
frame: content,
uri: apnext.href || apnext.action || apnext.value,
text: apnext.textContent || apnext.title || apnext,
element: apnext
};
}
}
patterns = next ? setting.nextPatterns : setting.backPatterns;
let uri = window.content.location.href;
let links = getAllLinks(window.content);
// rel="prev|next"
{
let relValue = next ? /(?:^|[ \t\r\n])next(?:[ \t\n\r]|$)/
: /(?:^|[ \t\r\n])prev(?:[ \t\n\r]|$)/;
let link = find(links, function (link) ((typeof link.rel == 'string') && relValue.test(link.rel.toLowerCase())));
if (link)
return link;
}
// keywords
{
let link;
if (patterns.some(function (pattern) {
link = find(links, function (link) match(pattern, link));
return link ? true : false;
}))
return link;
}
// succ
let succs = [];
getAllLocations(window.content).forEach(function (uri) {
succs = succs.concat(succURI(uri, next));
});
if (setting.useSuccPattern) {
let link;
if (succs.some(function (succ) {
link = find(links, function (link) link.uri && (link.uri.indexOf(succ) >= 0));
return link ? true : false;
}))
return link;
}
// force
if (setting.force && succs.length) {
return {
type: 'force',
uri: succs[0],
text: '-force-',
frame: window.content,
};
}
} catch (e) {
liberator.log(e);
liberator.echoerr(e);
}
}
// 猫又
function go (next, setting) {
setting = getCurrentSetting(setting);
if ((next && setting.useNextHistory) || (!next && setting.useBackHistory)) {
next ? BrowserForward() : BrowserBack();
displayOpened({uri: 'history', text: next ? 'next' : 'back'});
return;
}
var link = detect(next, setting);
if (link)
open(link);
}
// 外部から使用可能にする。
if (liberator.plugins)
liberator.plugins.autoDetectLink = {detect: detect, go: go};
////////////////////////////////////////////////////////////////
// Mappings
////////////////////////////////////////////////////////////////
if (gv().nextMappings.length) {
mappings.remove([modes.NORMAL], gv().nextMappings);
mappings.addUserMap(
[modes.NORMAL],
gv().nextMappings,
'Go next',
function () go(true)
);
}
if (gv().backMappings.length) {
mappings.remove([modes.NORMAL], gv().backMappings);
mappings.addUserMap(
[modes.NORMAL],
gv().backMappings,
'Go back',
function () go(false)
);
}
liberator.log('auto_detect_link.js loaded');
})();