From 56cb882fc6f122c75608f8eb6142a607d3dc855f Mon Sep 17 00:00:00 2001 From: janus_wel Date: Tue, 4 Nov 2008 21:02:16 +0000 Subject: for vimperator 1.2 git-svn-id: http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/branches/1.2@22732 d0d07461-0603-4401-acd4-de1884942a52 --- ldrize_cooperation.js | 414 +++++++++++++++++++++++ ldrize_cooperation_fetch_flv.js | 78 +++++ matanico.js | 350 ++++++++++++++++++++ nicontroller.js | 707 ++++++++++++++++++++++++++++++++++++++++ nnp_cooperation.js | 265 +++++++++++++++ reading.js | 227 +++++++++++++ youtubeamp.js | 328 +++++++++++++++++++ 7 files changed, 2369 insertions(+) create mode 100644 ldrize_cooperation.js create mode 100644 ldrize_cooperation_fetch_flv.js create mode 100644 matanico.js create mode 100644 nicontroller.js create mode 100644 nnp_cooperation.js create mode 100644 reading.js create mode 100644 youtubeamp.js diff --git a/ldrize_cooperation.js b/ldrize_cooperation.js new file mode 100644 index 0000000..2fbc6a6 --- /dev/null +++ b/ldrize_cooperation.js @@ -0,0 +1,414 @@ +// Vimperator plugin: 'Cooperation LDRize Mappings' +// Version: 0.21 +// Last Change: 05-Nov-2008. Jan 2008 +// License: Creative Commons +// Maintainer: Trapezoid - http://unsigned.g.hatena.ne.jp/Trapezoid +// +// Cooperation LDRize Mappings for vimperator0.6.* +// +// Variable: +// g:ldrc_captureMapping +// Specifies keys that capture by LDRize +// usage: let g:ldrc_captureMappings = "['j','k','p','o','?']" +// g:ldrc_enable +// LDRize Cooperation be Enable by default or not +// usage: let g:ldrc_enable = "false" +// default: true +// g:ldrc_hints +// Narrows "hinttags" based on Siteinfo. +// usage: let g:ldrc_hints = "true" +// default: false +// g:ldrc_intelligence_bind +// More inteligence cooperation bind +// usage: let g:ldrc_intelligence_bind = "true" +// default: false +// g:ldrc_skip +// length in which paragraph is skipped (use by inteligence bind mode) +// usage: let g:ldrc_hints = "true" +// default: 0.5 +// Mappings: +// Mappings for LDRize +// default: 'j','k','p','o' +// ',f' +// Show hints that specified by LDRize's siteinfo +// Commands: +// 'm' or 'mb' or 'minibuffer': +// Execute args as Minibuffer Command +// usage: :minibuffer pinned-link | open | clear-pin +// 'pin': +// View pinned link list +// usage: :pin +// 'pindownload': +// Download View pinned link by handler function or outer promgram. please see 'handlerInfo' also +// usage: :pindownload +// 'ldrc' or 'toggleldrizecooperation': +// Toggle LDRize Cooperation +// usage: :toggleldrizecooperation +// Options: +// 'ldrc' +// Enable LDRize Cooperation +// usage: :set ldrc +// 'noldrc' +// Disable LDRize Cooperation +// usage: :set noldrc +// +// 'ldrchints' +// Enable "Narrows Hinttags based on Siteinfo" +// usage: :set ldrchints +// 'noldrchints' +// Disable "Narrows Hinttags based on Siteinfo" +// usage: :set noldrchints + +(function(){ + //pattern: wildcard + //include: [regexp, option] or regexp + //handler: [programPath, [args]] or programPath or function(url,title) + var handlerInfo = [ + //{ + // pattern: 'http://www.nicovideo.jp/*', + // handler: ['c:\\usr\\SmileDownloader\\SmileDownloader.exe',['%URL%']], + // wait: 5000 + //}, + //{ + // handler: ['C:\\usr\\irvine\\irvine.exe',['%URL%']], + //}, + ]; + const DISABLE_ICON = 'data:image/png;base64,' + +'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAA7E' + +'AAAOxAGVKw4bAAACL0lEQVR4nF2Sy0tUYRjGf9+Z4/HMjJfjBUZEMM2MSDII' + +'REjSVtVecBFZi6Bdi4RW/SFBq2oR0R8gSaUJhVJIBkEEMZOWl5kuM+fqnPN9' + +'52sxQ4kPv837Pu+zel4xMjkz/3h5p87pbhyDw4o1mzUOkubYbvLo2kVx+4Pe' + +'rAKMdTGQ5YgiWK/8z+QT3yyVUTFAzaBXHQ0IONPKOxepAH65dUOGSB/pM9LC' + +'whjyy/sg4DB3TjGZbjVuVIihQhKfxGdzmzhhNBvGXhr7NDiRY+fr573ibmtC' + +'4pN4GNJDukiXusvbIuMnh9K9YujSYKKPl6vrZu+EI5EuyheG9JEe0qPusfSR' + +'4cGBbPA98og8LMlAPlor2ZEvVIT0kD6G9EhcEpfY58c+xbKYHBaRl4Ye432s' + +'rqyo7pnQo/qTxEW62gy2CKoAbheu4mGGm5eHgsViOTh+5Sp37+2X4gJQC0gU' + +'Otb0j2hhaCG06NfC0K22/radzs6uTM3ojY1SobDcdHNaCC2Mimn2YZmQggEd' + +'kPJ0UczfyOzVWHr1xnVmrS5I0R6pgTC1mXdoUwB2Jj5QFvDsBc8fTCkpL82l' + +'uW6rWWEPQBoL07JwCgAaywbgd8ynIrultTB3wWk73LtWdS3OXtd/fBwH2+Yg' + +'xM4R14kqrzMZzM5pO9dcNlQrl832wTSoGiEok84eOrK0ZGB0+shTJYpyFUv7' + +'In/s/LlbTyq+/ufZFlkTK4MhAJKUMCGs6x473rg/9xe9wS0xVA1n/AAAAABJ' + +'RU5ErkJggg=='; + const ENABLE_ICON = 'data:image/png;base64,' + +'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAAsT' + +'AAALEwEAmpwYAAACI0lEQVR4nGWSzU7yQBSGp84UKalDY0MkLsSdYWtCIok3' + +'4YKV7tx7MWy9A6/ABZDgHbhghdFqU9M0FpH57cyUcdFA8Pue3fl5T07Oe5zz' + +'8/PhcEgpbbfbtVoN7LBer9M01VpX4f7+/t3dnfP4+JimKQDg6OgIYwz+UpZl' + +'HMdbjbUWZVkmpQQAEEJc1wX/EYZhHMdlWQIAKKV7cgPG+PLy8uPjg/+l3+/7' + +'vl/1KKVQURRCCABAFEVa6yAIOOeO41Tjj4+PoyiK49h1XSkl53xPbOCcz+fz' + +'bre7WCzYhpOTk+l0GoYhhFAIIaXck1JuNc/Pz51OpyiKahkAAMb49fVVCKGU' + +'qgTw4uKCUqq1RggZY05PT8uyTJJEa312dvby8rJcLq21y+WSUiqlhN1uN89z' + +'xpgxJs9zQkiv1xuNRlmWXV9f39/ff39/53meZRmllBCCZrNZkiTWWowxIWQ6' + +'nV5dXRFCGGOfn59PT0+MMWut67pa6/V6jZrNpjHGWus4TqPRsNaORqPBYCCE' + +'GI/Hvu/7vm+trc4KAEC+71dGQggrdyaTyXA4NMbc3NxsvW82mwCAoihQrVY7' + +'PDzctVYIEUXR29tbo9GAEO6WpJTO7e0tIQRjXK/XhRCe5ymlsiyDEAZB4Hle' + +'lawEX19fqNVqVS/kOE6r1fI8DyHU6XT++ShjzM/Pz8HBAXx/f+/3+9X2WmvO' + +'uVKq3GCMUUoxxlarVb1ef3h4+AWNW50eXTIBjgAAAABJRU5ErkJggg=='; + + var Class = function(){return function(){this.initialize.apply(this,arguments)}} + + var _isEnable; + + var LDRizeCooperation = new Class(); + LDRizeCooperation.prototype = { + initialize: function(){ + var self = this; + this.LDRize = {getSiteinfo: function(){return undefined;}}; + this.Minibuffer = null; + this.handlerInfo = handlerInfo; + + this.LDRizeCooperationPanel = this.setupStatusbarPanel(); + + this.isEnable = liberator.globalVariables.ldrc_enable != undefined ? + window.eval(liberator.globalVariables.ldrc_enable) : true ; + this.isIntelligenceBind = liberator.globalVariables.ldrc_intelligence_bind != undefined ? + window.eval(liberator.globalVariables.ldrc_intelligence_bind) : false ; + this.isModHints = liberator.globalVariables.ldrc_hints != undefined ? + window.eval(liberator.globalVariables.ldrc_hints) : false ; + this.captureMappings = window.eval(liberator.globalVariables.ldrc_captureMappings) || ['j','k','p','o']; + this.skipHeight = liberator.globalVariables.ldrc_skip != undefined ? + window.eval(liberator.globalVariables.ldrc_skip) : 0.5 ; + + this.convertHandlerInfo(this.handlerInfo); + this.hookGreasemonkey(); + this.initLDRizeCaptureKeys(this.captureMappings); + this.initLDRizeCooperationFuture(); + + + if(liberator.plugins.LDRizeCooperationPlugins != undefined){ + liberator.plugins.LDRizeCooperationPlugins.forEach(function(func){ + func.apply(self,arguments); + }); + delete liberator.plugins.LDRizeCooperationPlugins; + } + }, + setupStatusbarPanel: function(){ + var self = this; + var LDRizeCooperationPanel = document.createElement('statusbarpanel'); + LDRizeCooperationPanel.setAttribute('id','ldrizecopperation-status'); + LDRizeCooperationPanel.setAttribute('class','statusbarpanel-iconic'); + LDRizeCooperationPanel.setAttribute('src',this.isEnable ? ENABLE_ICON : DISABLE_ICON); + LDRizeCooperationPanel.addEventListener("click",function(e){ + self.isEnable = !self.isEnable; + },false); + document.getElementById('status-bar').insertBefore(LDRizeCooperationPanel,document.getElementById('security-button').nextSibling); + + return LDRizeCooperationPanel; + }, + hookGreasemonkey: function(){ + var self = this; + var GreasemonkeyService = Cc["@greasemonkey.mozdev.org/greasemonkey-service;1"].getService().wrappedJSObject; + this.addAfter(GreasemonkeyService,'evalInSandbox',function(code,codebase,sandbox){ + if(sandbox.window.LDRize != undefined && sandbox.window.Minibuffer != undefined){ + sandbox.window.addEventListener("focus",function(){ + self.LDRize = sandbox.LDRize; + self.Minibuffer = sandbox.Minibuffer.command; + },false); + if(window.content.wrappedJSObject == sandbox.unsafeWindow){ + self.LDRize = sandbox.LDRize; + self.Minibuffer = sandbox.Minibuffer.command; + } + } + }); + }, + initLDRizeCaptureKeys: function(keys){ + var self = this; + keys.forEach(function(x){ + var map = liberator.modules.mappings.getDefault(null,x) || liberator.modules.mappings.get(null,x); + var oldAction = map.action; + var getter = "getPrev"; + switch(x){ + case 'j': getter = "getNext"; + case 'k': map.action = function(){ + self.isEnableLDRizeCooperation() ? + self.isIntelligenceBind && self.isScrollOrBind(getter) ? + oldAction.apply(this,arguments) // scroll + : self.sendRawKeyEvent(0,x.charCodeAt(0)) // bind + : oldAction.apply(this,arguments); + }; + break; + default: map.action = function(){ + self.isEnableLDRizeCooperation() ? self.sendRawKeyEvent(0,x.charCodeAt(0)):oldAction.apply(this,arguments); + }; + break; + } + }); + }, + initLDRizeCooperationFuture: function(){ + var self = this; + + var originalHinttags = liberator.modules.options.hinttags; + var originalExtendedHinttags = liberator.modules.options.hinttags; + + function setHinttags(enable){ + if(enable){ + var siteinfo = self.LDRize.getSiteinfo(); + if(siteinfo.link && siteinfo.paragraph){ + liberator.modules.options.hinttags = siteinfo.paragraph + "/" + siteinfo.link; + liberator.modules.options.extendedhinttags = siteinfo.paragraph + "/" + siteinfo.link; + }else{ + liberator.modules.options.hinttags = originalHinttags; + liberator.modules.options.extendedhinttags = originalExtendedHinttags; + } + }else{ + liberator.modules.options.hinttags = originalHinttags; + liberator.modules.options.extendedhinttags = originalExtendedHinttags; + } + } + + + //Mappings + liberator.modules.mappings.addUserMap([liberator.modules.modes.NORMAL], [",f"], + "Start QuickHint mode with LDRize", + function(){ + setHinttags(true); + liberator.modules.hints.show("o"); + setHinttags(self.isEnableLDRizeCooperation() && self.isModHints); + } ,{}); + + liberator.modules.mappings.addUserMap([liberator.modules.modes.NORMAL], ["f"], + "Start QuickHint mode", + function(){ + setHinttags(self.isEnableLDRizeCooperation() && self.isModHints); + liberator.modules.hints.show("o"); + },{}); + + liberator.modules.mappings.addUserMap([liberator.modules.modes.NORMAL], ["F"], + "Start QuickHint mode, but open link in a new tab", + function(){ + setHinttags(self.isEnableLDRizeCooperation() && self.isModHints); + liberator.modules.hints.show("t"); + },{}); + + liberator.modules.mappings.addUserMap([liberator.modules.modes.NORMAL], [";"], + "Start an extended hint mode", + function(arg){ + setHinttags(self.isEnableLDRizeCooperation() && self.isModHints); + liberator.modules.hints.show(arg); + }, + { flags: liberator.modules.Mappings.flags.ARGUMENT }); + //Commands + liberator.modules.commands.addUserCommand(["pin"], "LDRize Pinned Links", + function(){ + var links = self.getPinnedItems(); + var showString = links.length + " Items
"; + links.forEach(function(link){ + showString += link + "
"; + }); + liberator.modules.commandline.echo(showString, liberator.modules.commandline.HL_NORMAL, liberator.modules.commandline.FORCE_MULTILINE); + } ,{}); + liberator.modules.commands.addUserCommand(["mb","m","minibuffer"], "Execute Minibuffer", + function(arg){self.Minibuffer.execute(arg)}, + { + completer: function(filter){ + var completionList = []; + var command = self.Minibuffer.command; + var alias = self.Minibuffer.alias_getter(); + var tokens = filter.split("|").map(function(str){return str.replace(/\s+/g,"")}); + var exp = new RegExp("^" + tokens.pop()); + for(let i in command) if(exp.test(i))completionList.push([tokens.concat(i).join(" | "),"MinibufferCommand"]); + for(let i in alias) if(exp.test(i))completionList.push([i,"MinibufferAlias"]); + return [0,completionList]; + } + }); + liberator.modules.commands.addUserCommand(["pindownload"], "Download pinned links by any software", + function(arg){ self.downloadLinksByProgram(self.getPinnedItems());} ,{}); + liberator.modules.commands.addUserCommand(["toggleldrizecooperation","toggleldrc"], "Toggle LDRize Cooperation", + function(arg){ self.isEnable = !self.isEnable}, {}); + //Options + liberator.modules.options.add(['ldrc','ldrizecooperation'],'LDRize cooperation','boolean',this.isEnable, + { + setter: function(value){ self.isEnable = value; }, + getter: function(){ return self.isEnable; } + } + ); + liberator.modules.options.add(['ldrchints'],'mod hinttags for LDRize','boolean',this.isModHints, + { + setter: function(value){ self.isModHints = value; }, + getter: function(){ return self.isModHints; } + } + ); + }, + convertHandlerInfo: function(handlerInfoArray){ + handlerInfoArray.forEach(function(x){ + x.include = typeof x.include != "undefined" + ? typeof x.include == "string" ? new RegExp(x.include) : new RegExp(x.include[0],x.include[1]) + : typeof x.pattern != "undefined" + ? new RegExp("^"+String(x.pattern).replace(/\s+/g,"").replace(/[\\^$.+?|(){}\[\]]/g,"\\$&") + .replace(/(?=\*)/g,".")+"$","i") + : /(?:)/; + delete x.pattern; + }); + }, + + get isEnable(){ + return _isEnable; + }, + set isEnable(value){ + this.LDRizeCooperationPanel.setAttribute("src",value ? DISABLE_ICON : ENABLE_ICON); + _isEnable = value; + }, + isEnableLDRize: function(){ return this.LDRize.getSiteinfo() != undefined; }, + isEnableLDRizeCooperation: function(){ return this.isEnable && this.isEnableLDRize() }, + + //Pin + getPinnedItems: function(){ + var linkXpath = this.LDRize.getSiteinfo()['link']; + var viewXpath = this.LDRize.getSiteinfo()['view'] || linkXpath + "/text()"; + return this.LDRize.getPinnedItems().map(function(i){ + let linkResult = i.XPath(linkXpath); let viewResult = i.XPath(viewXpath); + return [linkResult, viewResult ? viewResult.textContent : null]} + ); + }, + downloadLinksByProgram: function(links){ + var self = this; + var count = 0; + links.forEach(function([url,title]){ + for each(let x in self.handlerInfo){ + if(x.include.test(url)){ + setTimeout(function(){ + if(typeof x.handler == "object"){ + var args = x.handler[1].map(function(s){ return s.replace(/%URL%/g,url).replace(/%TITLE%/g,title); }); + liberator.modules.io.run(x.handler[0],args,false); + }else if(typeof x.handler == "string"){ + liberator.modules.io.run(x.handler,[url],false); + }else if(typeof x.handler == "function"){ + x.handler(url.toString(),title); + } + },x.wait != undefined ? x.wait * count++ : 0); + return; + } + } + liberator.echoerr("LDRize Cooperation: download pattern not found!!"); + }); + }, + isScrollOrBind: function(getter){ + try{ + var self = this; + var paragraphes = this.LDRize.getParagraphes(); + var paragraph = paragraphes[getter](); + var current = paragraphes.current; + var next = paragraphes.getNext(); + + var innerHeight = window.content.innerHeight; + var scrollY = window.content.scrollY; + + var limit = window.content.innerHeight * (self.skipHeight + 0.5); + + if(paragraph.paragraph == undefined) return true; // scroll + if(current.paragraph == undefined) return false; // bind + if(current.paragraph.y - window.content.scrollY == this.LDRize.getScrollHeight() + && getter == "getPrev") return false; // bind + + var p = this.getClientPosition(paragraph.paragraph.node); + var np = next && next.paragraph.node != undefined ? + this.getClientPosition(next.paragraph.node) : + {top: window.content.scrollMaxY + window.content.innerHeight,left: 0}; + var cp = this.getClientPosition(current.paragraph.node); + + /* + *log(p); + *log(np); + *log(cp); + */ + + //check current paragraph + if(!(scrollY < np.top && cp.top < scrollY + innerHeight)) return false; // bind + //check next/prev paragraph + if(Math.abs(p.top - (scrollY + innerHeight/2)) < innerHeight * 0.5) return false; // bind + if(Math.abs(p.top - (scrollY + innerHeight/2)) > limit) return true; // scroll + else return false; // bind + }catch(e){ + log(e); + } + }, + + //Utils + addAfter: function(target,name,after){ + var original = target[name]; + target[name] = function() { + var tmp = original.apply(target,arguments); + after.apply(target,arguments); + return tmp; + }; + }, + getClientPosition: function(elem){ + try{ + var position = elem.getBoundingClientRect(); + }catch(e){ + position = elem.parentNode.getBoundingClientRect(); + } + return { + left:Math.round(window.content.scrollX+position.left), + top:Math.round(window.content.scrollY+position.top) + } + }, + sendRawKeyEvent: function(keyCode,charCode){ + var evt = window.content.wrappedJSObject.document.createEvent("KeyEvents"); + evt.initKeyEvent("keypress",true,true,window.content.wrappedJSObject,false,false,false,false,keyCode,charCode); + window.content.wrappedJSObject.document.dispatchEvent(evt); + }, + } + + liberator.plugins.LDRizeCooperation = new LDRizeCooperation(); +})(); diff --git a/ldrize_cooperation_fetch_flv.js b/ldrize_cooperation_fetch_flv.js new file mode 100644 index 0000000..d8412b2 --- /dev/null +++ b/ldrize_cooperation_fetch_flv.js @@ -0,0 +1,78 @@ +// Vimperator plugin: 'Cooperation LDRize Mappings - Niconico Flv Fetchearg || liberator.buffer.URLr' +// Version: 0.4 +// Last Change: 05-Nov-2008. Jan 2008 +// License: Creative Commons +// Maintainer: Trapezoid - http://unsigned.g.hatena.ne.jp/Trapezoid +// +// Cooperation LDRize Mappings - Niconico Flv Fetcher for vimperator0.6.* +// Require LDRize Cooperation ver 0.14 +(function(){ + function NiconicoFlvHandler(url,title){ + const nicoApiEndPoint = "http://www.nicovideo.jp/api/getflv?v="; + const nicoWatchEndPoint = "http://www.nicovideo.jp/watch/"; + var videoId = url.match(/\wm\d+/)[0]; + var fileName = title.replace(/[?\\\*\/:<>\|\"]/g,'_') + ".flv"; + httpGET(nicoApiEndPoint + videoId,function(apiResult){ + var flvUrl = decodeURIComponent(apiResult.match(/url=(.*?)&/)[1]); + + httpGET(nicoWatchEndPoint + videoId,function(watchPage){ + try{ + var DownloadManager = Cc["@mozilla.org/download-manager;1"] + .getService(Ci.nsIDownloadManager); + var WebBrowserPersist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] + .createInstance(Ci.nsIWebBrowserPersist); + + var sourceUri = makeURI(flvUrl,null,null); + var file = DownloadManager.userDownloadsDirectory; + file.appendRelativePath(fileName); + var fileUri = makeFileURI(file); + + var download = DownloadManager.addDownload(0, sourceUri, fileUri, fileName, + null, null, null, null, WebBrowserPersist); + WebBrowserPersist.progressListener = download; + WebBrowserPersist.saveURI(sourceUri, null, null, null, null, file); + }catch(e){log(e);liberator.echoerr(e)} + }); + }); + } + + function setupLDRizeCooperationNiconicoFlvFetcher(){ + var NiconicoFlvFetcher = { + pattern: 'http://www.nicovideo.jp/watch/*', + handler: NiconicoFlvHandler, + wait: 5000 + } + this.convertHandlerInfo([NiconicoFlvFetcher]); + this.handlerInfo.unshift(NiconicoFlvFetcher); + } + + if(liberator.plugins.LDRizeCooperation == undefined){ + liberator.plugins.LDRizeCooperationPlugins = liberator.plugins.LDRizeCooperationPlugins || []; + liberator.plugins.LDRizeCooperationPlugins.push(setupLDRizeCooperationNiconicoFlvFetcher); + }else{ + setupLDRizeCooperationNiconicoFlvFetcher.apply(liberator.plugins.LDRizeCooperation); + } + + function httpGET(uri,callback){ + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function(){ + if(xhr.readyState == 4){ + if(xhr.status == 200) + callback.call(this,xhr.responseText); + else + throw new Error(xhr.statusText) + } + }; + xhr.open("GET",uri,true); + xhr.send(null); + } + liberator.commands.addUserCommand(['fetchflv'],'Download flv file from Nicovideo', + function(arg){ + httpGET(arg || liberator.buffer.URL,function(responseText){ + var [,title] = responseText.match(/(.*?)<\/title>/i); + liberator.log(title); + NiconicoFlvHandler(arg || liberator.buffer.URL,title); + }); + },{} + ); +})(); diff --git a/matanico.js b/matanico.js new file mode 100644 index 0000000..c0c05e5 --- /dev/null +++ b/matanico.js @@ -0,0 +1,350 @@ +/* + * ==VimperatorPlugin== + * @name matanico.js + * @description update Twitter's status to current video name and comment + * @description-ja 今見てる動画のタイトルとコメントを Twitter に投稿する + * @author janus_wel <janus_wel@fb3.so-net.ne.jp> + * @version 0.62 + * @minversion 2.0pre 2008/10/16 + * ==/VimperatorPlugin== + * + * LICENSE + * New BSD License + * + * USAGE + * :matanico [comment] + * Twitter に今見ている動画の情報をポストする。 comment はなくてもかまわない。 + * 動画ページではみている動画の情報を、タグ検索ページでは検索結果をポストする。 + * :matanico! [comment] + * Twitter に送られる文字列をクリップボードにコピーする。 twitter には送られない。 + * 動画ページではみている動画の情報を、タグ検索ページでは検索結果をコピーする。 + * + * VALIABLE + * g:matanico_status_format + * 動画閲覧ページで投稿する文章の書式設定。動画ページで適用される。以下の変数指定が可能。 + * $SERVICENAME : このプラグインが付加する文字列。 g:matanico_status_servicename で指定する。 + * $SUBJECT : 動画の名前。 + * $PLAYTIME : 再生時間。 + * $URL : 動画の URL。 + * $COMMENT : コメント。これがないとコメントを書いても反映されない。 + * default + * let g:matanico_status_format='$SERVICENAME : $SUBJECT($PLAYTIME) - $URL $COMMENT' + * + * g:matanico_status_servicename + * このプラグインが固定で付加する文字列。動画ページで適用される。 + * default + * let g:matanico_status_servicename='またニコニコ動画見てる' + * + * g:matanico_tag_format + * タグ検索ページで投稿する文章の書式設定。以下の変数指定が可能。 + * $SERVICENAME : このプラグインが付加する文字列。 g:matanico_tag_servicename で指定する。 + * $TAG : 検索したタグ。複数の場合は半角スペースで区切られる。 + * $NUMOFVIDEOS : 検索結果の件数。 + * $URL : 検索結果の URL。 + * $COMMENT : コメント。これがないとコメントを書いても反映されない。 + * default + * let g:matanico_tag_format='$SERVICENAME : $TAG($NUMOFVIDEOS件) - $URL $COMMENT' + * + * g:matanico_tag_servicename + * このプラグインが固定で付加する文字列。タグ検索ページで適用される。 + * default + * let g:matanico_tag_servicename='またニコニコタグ検索してる' + * + * g:matanico_related_tag_format + * キーワードによるタグ検索ページで投稿する文章の書式設定。以下の変数指定が可能。 + * $SERVICENAME : このプラグインが付加する文字列。 g:matanico_tag_servicename で指定する。 + * $KEYWORD : 検索したタグ。複数の場合は半角スペースで区切られる。 + * $NUMOFTAGS : 検索結果の件数。 + * $URL : 検索結果の URL。 + * $COMMENT : コメント。これがないとコメントを書いても反映されない。 + * default + * let g:matanico_related_tag_format='$SERVICENAME : $KEYWORD($NUMOFTAGS件) - $URL $COMMENT' + * + * g:matanico_tag_servicename + * このプラグインが固定で付加する文字列。キーワードによるタグ検索ページで適用される。 + * default + * let g:matanico_related_tag_servicename='またキーワードでニコニコタグ検索してる' + * + * HISTORY + * 2008/06/14 ver. 0.10 - initial written. + * 2008/06/27 ver. 0.20 - change replace argument to regexp with 'g' option. + * - add matanico! command. + * - refactoring + * - display sended status if succeed. + * 2008/06/28 ver. 0.21 - change display strings, 'Yanked ' and 'Posted '. + * 2008/07/13 ver. 0.30 - change xpath function and xpath query. + * 2008/07/14 ver. 0.40 - change url checking. + * 2008/07/15 ver. 0.50 - make NicoScraper class. + * - add function to post tag page. + * refer : http://nicovideo.g.hatena.ne.jp/koizuka/20080322/matanico_tag + * 2008/09/04 ver. 0.60 - add function to post related tag page. + * 2008/10/08 ver. 0.61 - correspond vimperator specification + * "bang" in extra object on addUserCommand. + * */ + +(function() { + +// information functions +// change XPath query when html changed. +function NicoScraper() {} +NicoScraper.prototype = { + constants: { + VERSION: '0.62', + WATCH_PAGE: 1, + WATCH_URL: '^http://www\\.nicovideo\\.jp/watch/[a-z]{2}\\d+', + TAG_PAGE: 2, + TAG_URL: '^http://www\\.nicovideo\\.jp/tag/', + RELATED_TAG_PAGE: 3, + RELATED_TAG_URL: '^http://www\\.nicovideo\\.jp/related_tag/', + }, + + version: function() { return this.constants.VERSION; }, + + pagecheck: function() { + if(this.getURL().match(this.constants.WATCH_URL)) return this.constants.WATCH_PAGE; + if(this.getURL().match(this.constants.TAG_URL)) return this.constants.TAG_PAGE; + if(this.getURL().match(this.constants.RELATED_TAG_URL)) return this.constants.RELATED_TAG_PAGE; + throw 'current tab is not nicovideo.jp'; + }, + + _flvplayer: function() { + if(this.pagecheck() === this.constants.WATCH_PAGE) { + let flvplayer = window.content.document.getElementById('flvplayer'); + if(! flvplayer) throw 'flvplayer is not found'; + + return flvplayer.wrappedJSObject ? flvplayer.wrappedJSObject : flvplayer ? flvplayer : null; + } + return null; + }, + + getURL: function() { return liberator.buffer.URL; }, + + getSubject: function() { + if(this.pagecheck() === this.constants.WATCH_PAGE) { + let subject = $f('//h1/a[contains(concat(" ",@class," "), " video ")]'); + return subject ? subject.text : null; + } + return null; + }, + + getPlaytime: function() { + var p = this._flvplayer(); + var playtime = p ? Math.round(p.ext_getTotalTime()) : null; + if(playtime) { + let min = Math.floor(playtime / 60); + let sec = playtime % 60; + if(sec < 10) sec = '0' + sec; + return playtime ? [min, sec].join(':') : null; + } + else return null; + }, + + getTagName: function() { + if(this.pagecheck() === this.constants.TAG_PAGE) { + let word_nodes = $s('id("search_words")/span[contains(concat(" ",@class," "), " search_word ")]'); + let words = []; + word_nodes.forEach(function(node) { words.push(node.textContent); }); + return words.length ? words.join(' ') : null; + } + return null; + }, + + getNumofVideos: function() { + if(this.pagecheck() === this.constants.TAG_PAGE) { + let numofVideos = $f('//strong[contains(concat(" ",@class," "), " result_total ")]'); + return numofVideos.textContent ? numofVideos.textContent : null; + } + return null; + }, + + getKeyword: function() { + if(this.pagecheck() === this.constants.RELATED_TAG_PAGE) { + let keyword = $f('//strong[contains(concat(" ",@class," "), " search_word ")]'); + return keyword.textContent ? keyword.textContent : null; + } + return null; + }, + + getNumofTags: function() { + if(this.pagecheck() === this.constants.RELATED_TAG_PAGE) { + let numofTags = $f('//strong[contains(concat(" ",@class," "), " result_total ")]'); + return numofTags.textContent ? numofTags.textContent : null; + } + return null; + }, +}; + +var scraper = new NicoScraper; + +liberator.commands.addUserCommand(['matanico'], "update Twitter's status to current video name and comment", + function(arg, special) { + try { + // build post string ----- + let post_string; + // domain check + switch(scraper.pagecheck()) { + // video page + case scraper.constants.WATCH_PAGE: + { + // get value from global variable or set default + let format = liberator.globalVariables.matanico_status_format || '$SERVICENAME : $SUBJECT($PLAYTIME) - $URL $COMMENT'; + let serviceName = liberator.globalVariables.matanico_status_servicename || 'またニコニコ動画見てる'; + + // expand variable ( evaluate variable ? ) + post_string = format.replace(/\$SERVICENAME/g, serviceName) + .replace(/\$SUBJECT/g, scraper.getSubject()) + .replace(/\$PLAYTIME/g, scraper.getPlaytime()) + .replace(/\$URL/g, scraper.getURL()) + .replace(/\$COMMENT/g, arg); + } + break; + + // tag search page + case scraper.constants.TAG_PAGE: + { + // get value from global variable or set default + let format = liberator.globalVariables.matanico_tag_format || '$SERVICENAME : $TAG($NUMOFVIDEOS件) - $URL $COMMENT'; + let serviceName = liberator.globalVariables.matanico_tag_servicename || 'またニコニコタグ検索してる'; + + // expand variable ( evaluate variable ? ) + post_string = format.replace(/\$SERVICENAME/g, serviceName) + .replace(/\$TAG/g, scraper.getTagName()) + .replace(/\$NUMOFVIDEOS/g, scraper.getNumofVideos()) + .replace(/\$URL/g, scraper.getURL()) + .replace(/\$COMMENT/g, arg); + } + break; + + // related_tag search page + case scraper.constants.RELATED_TAG_PAGE: + { + // get value from global variable or set default + let format = liberator.globalVariables.matanico_related_tag_format || '$SERVICENAME : $KEYWORD($NUMOFTAGS件) - $URL $COMMENT'; + let serviceName = liberator.globalVariables.matanico_related_tag_servicename || 'またキーワードでニコニコタグ検索してる'; + + // expand variable ( evaluate variable ? ) + post_string = format.replace(/\$SERVICENAME/g, serviceName) + .replace(/\$KEYWORD/g, scraper.getKeyword()) + .replace(/\$NUMOFTAGS/g, scraper.getNumofTags()) + .replace(/\$URL/g, scraper.getURL()) + .replace(/\$COMMENT/g, arg); + } + break; + + default: + throw 'current tab is not nicovideo.jp'; + break; + } + + // ':matanico!' display the evaluated format. + if(special) { + liberator.util.copyToClipboard(post_string, true); + return; + } + + // ready posting ----- + // URI encode + let parameter = 'status=' + encodeURIComponent(post_string); + + // twitter's URL to post + let domain = 'http://twitter.com/'; + let postURL = 'https://twitter.com/statuses/update.json'; + + // get user account for twitter + let [user, pass] = getUserAccount(domain, postURL, null); + + // send status + let req = new XMLHttpRequest(); + if(req) { + req.open('POST', postURL, true, user, pass); + req.onreadystatechange = function() { + if(req.readyState == 4) { + if(req.status == 200) liberator.echo('Posted ' + post_string) + else throw 'failure in posting status to Twitter. HTTP status code : ' + req.status; + } + } + req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + req.send(parameter); + } + } + catch(e) { + liberator.echoerr(e); + liberator.log(e); + } + }, + // complete logic is none. + { + bang: true, + } +); + +// stuff functions +function $f(query, node) { + node = node || window.content.document; + var result = (node.ownerDocument || node).evaluate( + query, + node, + null, + XPathResult.FIRST_ORDERED_NODE_TYPE, + null + ); + return result.singleNodeValue ? result.singleNodeValue : null; +} + +function $s(query, node) { + node = node || window.content.document; + var result = (node.ownerDocument || node).evaluate( + query, + node, + null, + XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, + null + ); + var nodes = []; + for(let i=0 ; i<result.snapshotLength ; ++i) nodes.push(result.snapshotItem(i)); + return nodes; +} + +// user account manager +// from direct_bookmark.js +// thanks to Trapezoid +function getUserAccount(form,post,arg) { + var user, password; + try { + let passwordManager = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager); + let logins = passwordManager.findLogins({}, form, post, arg); + if(logins.length > 0) { + [user, password] = [logins[0].username, logins[0].password]; + } else { + let promptUser = { value : '' }, promptPass = { value : '' }; + let promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"] + .getService(Ci.nsIPromptService); + + let nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1", + Ci.nsILoginInfo, + "init"); + + let ret = promptSvc.promptUsernameAndPassword( + window, form, 'Enter e-mail address and password.', + promptUser, promptPass, null, {} + ); + if(ret) { + [user, password] = [promptUser.value, promptPass.value]; + let formLoginInfo = new nsLoginInfo(form, + post, null, + user, password, '', ''); + passwordManager.addLogin(formLoginInfo); + } else { + liberator.echoerr("account not found - " + form); + } + } + } + catch(ex) { + liberator.echoerr("handled exception during getting username and password"); + liberator.log(ex); + } + return [user, password]; +} + +})(); +// vim:sw=4 ts=4 et: diff --git a/nicontroller.js b/nicontroller.js new file mode 100644 index 0000000..ac66991 --- /dev/null +++ b/nicontroller.js @@ -0,0 +1,707 @@ +/* + * ==VimperatorPlugin== + * @name nicontroller.js + * @description this script give you keyboard opration for nicovideo.jp. + * @description-ja ニコニコ動画のプレーヤーをキーボードで操作できるようにする。 + * @author janus_wel <janus_wel@fb3.so-net.ne.jp> + * @version 0.54 + * @minversion 2.0pre 2008/10/16 + * ==/VimperatorPlugin== + * + * LICENSE + * New BSD License + * + * USAGE + * :nicoinfo + * プレーヤーに関しての情報を表示する。今のところバージョンだけ。 + * :nicopause + * 再生 / 一時停止を切り替える。 + * :nicomute + * 音声あり / なしを切り替える。 + * :nicommentvisible + * コメント表示 / 非表示を切り替える。 + * :nicorepeat + * リピート再生するかどうかを切り替える。 + * :nicosize + * 最大化 / ノーマルを切り替える。 + * :nicoseek [position] + * 指定した場所にシークする。秒数で指定が可能。 + * 指定なしの場合一番最初にシークする。 + * :nicoseek! delta + * 現在の位置から delta 分離れた所にシークする。秒数で指定が可能。 + * マイナスを指定すると戻る。指定なしの場合変化しない。 + * :nicovolume [volume] + * ボリュームを設定する。 0 ~ 100 が指定できる。 + * 指定なしの場合 100 にセットする。 + * :nicovolume! delta + * ボリュームを現在の値から変更する。 -100 ~ +100 を指定可能。 + * 指定なしの場合変化しない。 + * :nicodescription + * 説明文・メニューの表示 / 非表示を切り替える + * :nicomment comment + * コメント欄を指定した文字列で埋める。 + * 詳しい機能は http://d.hatena.ne.jp/janus_wel/20080913/1221317583 + * :nicommand command + * コマンド欄を指定した文字列で埋める。 + * プレミアムかどうかで補完可能なコマンドも変化。 + * 補完はけっこう賢くなったと思う。 + * + * HISTORY + * 2008/07/13 ver. 0.10 - initial written. + * 2008/07/14 ver. 0.20 - add nicosize, nicoseek, nicovolume. + * 2008/07/15 ver. 0.30 - add nicoinfo. + * 2008/07/19 ver. 0.31 - allow assign mm:ss format to seekTo method. + * thanks to id:nokturnalmortum + * refer: http://d.hatena.ne.jp/nokturnalmortum/20080718#1216314934 + * - fix error message. + * 2008/09/12 ver. 0.40 - completer function of :nicommand -> usefull. + * - add feature: comment input assistance. + * 2008/09/14 ver. 0.41 - fix the bug that happen by adding method to Array. + * - fix the nicopause bug associated with + * flvplayer's status('buffering' and 'end'). + * thanks to なまえ (no name ?) + * refer: http://d.hatena.ne.jp/janus_wel/20080914/1221387317 + * 2008/10/01 ver. 0.50 - add :nicodescription. + * 2008/10/02 ver. 0.51 - refactoring. + * - use Error object when throw exception. + * - extract constant variables from code + * and define in NicoPlayerController.constants. + * 2008/10/08 ver. 0.52 - correspond Vimperator specification + * "bang" in extra object on addUserCommand. + * */ + +/* +_vimperatorrc に以下のスクリプトを貼り付けると幸せになれるかも +コマンド ( [',n-'] や [',n+'] の部分 ) は適宜変えてね。 + +javascript <<EOM +// [N],n- +// N 秒前にシークする。 +// 指定なしの場合 10 秒前。 +liberator.mappings.addUserMap( + [liberator.modes.NORMAL], + [',n-'], + 'seek by count backward', + function(count) { + if(count === -1) count = 10; + liberator.execute(':nicoseek! ' + '-' + count); + }, + { flags: liberator.Mappings.flags.COUNT } +); + +// [N],n+ +// N 秒後にシークする。 +// 指定なしの場合 10 秒後。 +liberator.mappings.addUserMap( + [liberator.modes.NORMAL], + [',n+'], + 'seek by count forward', + function(count) { + if(count === -1) count = 10; + liberator.execute(':nicoseek! ' + count); + }, + { flags: liberator.Mappings.flags.COUNT } +); +EOM +*/ + +(function() { + +// class definition +// cookie manager +function CookieManager() { + this.initialize.apply(this, arguments); +} +CookieManager.prototype = { + initialize: function (uri) { + const Cc = Components.classes; + const Ci = Components.interfaces; + + const MOZILLA = '@mozilla.org/'; + const IO_SERVICE = MOZILLA + 'network/io-service;1'; + const COOKIE_SERVICE = MOZILLA + 'cookieService;1'; + + this.ioService = Cc[IO_SERVICE].getService(Ci.nsIIOService); + this.cookieService = Cc[COOKIE_SERVICE].getService(Ci.nsICookieService); + if(!this.ioService || !this.cookieService) { + throw new Error('error on CookieManager initialize.'); + } + + this.readCookie(uri); + }, + + readCookie: function (uri) { + if(uri) { + this.uri = uri; + this.uriObject = this.ioService.newURI(uri, null, null); + this.cookie = this._deserializeCookie(this._getCookieString()); + } + }, + + _getCookieString: function () { + return this.uriObject + ? this.cookieService.getCookieString(this.uriObject, null) + : null; + }, + + _setCookieString: function (cookieString) { + if(this.uriObject && cookieString) { + this.cookieService.setCookieString(this.uriObject, null, cookieString, null); + } + }, + + _deserializeCookie: function (cookieString) { + var cookies = cookieString.split('; '); + var cookie = {}; + var key, val; + for (let i=0, max=cookies.length ; i<max ; ++i) { + [key, val] = cookies[i].split('='); + cookie[key] = val; + } + return cookie; + }, + + getCookie: function (key) { + return this.cookie[key] ? this.cookie[key] : null; + }, + + setCookie: function (obj) { + this.cookie[obj.key] = obj.value; + var string = [ + obj.key + '=' + obj.value, + 'domain=' + obj.domain, + 'expires=' + new Date(new Date().getTime() + obj.expires), + ].join(';'); + this._setCookieString(string); + }, +}; + +// NicoPlayerController Class +function NicoPlayerController() { + this.initialize.apply(this, arguments); +} +NicoPlayerController.prototype = { + initialize: function () { + this.cookieManager = new CookieManager(); + }, + + constants: { + VERSION: '0.53', + + CARDINAL_NUMBER: 10, + + NICO_DOMAIN: '.nicovideo.jp', + NICO_URL: 'http://www.nicovideo.jp/', + WATCH_URL: 'http://www.nicovideo.jp/watch/', + WATCH_PAGE: 1, + + FLVPLAYER_NODE_ID: 'flvplayer', + + STATE_PLAYING: 'playing', + PLAY: true, + PAUSE: false, + + STATE_SIZE_NORMAL: 'normal', + STATE_SIZE_FIT: 'fit', + + NAME_PREMIUM_NO: 'premiumNo', + NAME_PLAYER_VERSION: 'PLAYER_VERSION', + + SEEKTO_DEFAULT: 0, + SEEKBY_DEFAULT: 0, + VOLUMETO_DEFAULT: 100, + VOLUMEBY_DEFAULT: 0, + + DESCRIPTION_HIDDEN_NODE_ID: 'des_1', + DESCRIPTION_DISPLAYED_NODE_ID: 'des_2', + + DESCRIPTION_HIDDEN_STATE: 0, + DESCRIPTION_DISPLAYED_STATE: 1, + + COOKIE_DESCRIPTION_NAME: 'desopen', + COOKIE_EXPIRES: 60 * 60 * 24 * 365 * 1000, + + COMMAND_NORMAL: [ + ['naka', 'normal comment (flow right to left)'], + ['ue', 'fix comment to vertical top and horizonal center of the screen'], + ['shita', 'fix comment to vertical bottom and horizonal center of the screen'], + ['medium', 'normal size comment'], + ['big', 'big size comment'], + ['small', 'small size comment'], + ['white', 'white color comment'], + ['red', 'red color comment'], + ['pink', 'pink color comment'], + ['orange', 'orange color comment'], + ['yellow', 'yellow color comment'], + ['green', 'green color comment'], + ['cyan', 'cyan color comment'], + ['blue', 'bule color comment'], + ['purple', 'purple color comment'], + ['184', 'anonymouse comment'], + ['sage', 'post comment on "sage" mode'], + ['invisible', 'invisible comment'], + ], + COMMAND_PREMIUM: [ + ['niconicowhite', 'nicinicowhite color comment'], + ['truered', 'truered color comment'], + ['passionorange', 'passionorange comment'], + ['madyellow', 'madyellow comment'], + ['elementalgreen', 'elementalgreen comment'], + ['marineblue', 'marineblue'], + ['nobleviolet', 'nobleviolet'], + ['black', 'black'], + ], + }, + + getControllerVersion: function () { return this.constants.VERSION; }, + getPlayerVersion: function () { return this.getValue(this.constants.NAME_PLAYER_VERSION); }, + + pagecheck: function() { + if(this.getURL().match(this.constants.WATCH_URL)) return this.constants.WATCH_PAGE; + throw new Error('current tab is not watch page on nicovideo.jp'); + }, + + getURL: function() { return liberator.buffer.URL; }, + + _flvplayer: function() { + if(this.pagecheck() === this.constants.WATCH_PAGE) { + let flvplayer = window.content.document.getElementById(this.constants.FLVPLAYER_NODE_ID); + if(! flvplayer) throw new Error('flvplayer is not found'); + + return flvplayer.wrappedJSObject + ? flvplayer.wrappedJSObject + : flvplayer ? flvplayer : null; + } + return null; + }, + + togglePlay: function() { + var p = this._flvplayer(); + (p.ext_getStatus() !== this.constants.STATE_PLAYING) + ? p.ext_play(this.constants.PLAY) + : p.ext_play(this.constants.PAUSE); + + if(p.ext_getStatus() === 'end') { + let base = p.ext_getPlayheadTime(); + let self = this; + setTimeout(function () { + base !== p.ext_getPlayheadTime() + ? p.ext_play(self.constants.PAUSE) + : p.ext_play(self.constants.PLAY); + }, 100); + } + }, + + toggleMute: function() { + var p = this._flvplayer(); + p.ext_setMute(! p.ext_isMute()); + }, + + toggleCommentVisible: function() { + var p = this._flvplayer(); + p.ext_setCommentVisible(! p.ext_isCommentVisible()); + }, + + toggleRepeat: function() { + var p = this._flvplayer(); + p.ext_setRepeat(! p.ext_isRepeat()); + }, + + toggleSize: function() { + var p = this._flvplayer(); + (p.ext_getVideoSize() === this.constants.STATE_SIZE_NORMAL) + ? p.ext_setVideoSize(this.constants.STATE_SIZE_FIT) + : p.ext_setVideoSize(this.constants.STATE_SIZE_NORMAL); + }, + + toggleDescription: function () { + if(!(this.pagecheck() === this.constants.WATCH_PAGE)) { + return; + } + + // get nodes + var hidden = window.content.document.getElementById(this.constants.DESCRIPTION_HIDDEN_NODE_ID); + var displayed = window.content.document.getElementById(this.constants.DESCRIPTION_DISPLAYED_NODE_ID); + + // get cookie + this.cookieManager.readCookie(this.constants.NICO_URL); + var val = this.cookieManager.getCookie(this.constants.COOKIE_DESCRIPTION_NAME); + + if(!(hidden && displayed && val !== undefined && val !== null)) { + return; + } + + // change 'display' property of description nodes + var escape = hidden.style.display; + hidden.style.display = displayed.style.display; + displayed.style.display = escape; + + // change cookie + var change = (val == this.constants.DESCRIPTION_HIDDEN_STATE) + ? this.constants.DESCRIPTION_DISPLAYED_STATE + : this.constants.DESCRIPTION_HIDDEN_STATE; + this.cookieManager.setCookie({ + key: this.constants.COOKIE_DESCRIPTION_NAME, + value: change, + domain: this.constants.NICO_DOMAIN, + expires: this.constants.COOKIE_EXPIRES, + }); + }, + + seekTo: function(position) { + if(position) { + if(position.match(/^(\d+):(\d+)$/)) { + position = parseInt(RegExp.$1, this.constants.CARDINAL_NUMBER) * 60 + + parseInt(RegExp.$2, this.constants.CARDINAL_NUMBER); + } + if(isNaN(position)) throw new Error('assign unsigned number : seekTo()'); + } + else position = this.constants.SEEKTO_DEFAULT; + + var p = this._flvplayer(); + p.ext_setPlayheadTime(position); + }, + + seekBy: function(delta) { + if(delta) { + if(isNaN(delta)) throw new Error('assign signed number : seekBy()'); + } + else delta = this.constants.SEEKBY_DEFAULT; + + var p = this._flvplayer(); + var position = p.ext_getPlayheadTime(); + position += parseInt(delta, this.constants.CARDINAL_NUMBER); + + p.ext_setPlayheadTime(position); + }, + + volumeTo: function(volume) { + if(volume) { + if(isNaN(volume)) throw new Error('assign unsigned number : volumeTo()'); + } + else volume = this.constants.VOLUMETO_DEFAULT; + + var p = this._flvplayer(); + p.ext_setVolume(volume); + }, + + volumeBy: function(delta) { + if(delta) { + if(isNaN(delta)) throw new Error('assign signed number : volumeBy()'); + } + else delta = this.constants.VOLUMEBY_DEFAULT; + + var p = this._flvplayer(); + var volume = p.ext_getVolume(); + volume += parseInt(delta, this.constants.CARDINAL_NUMBER); + + p.ext_setVolume(volume); + }, + + getValue: function(name) { + return this._flvplayer().GetVariable(name); + }, + + setValue: function(name, value) { + return this._flvplayer().SetVariable(name, value); + }, + + // return the clone not to damage + // Array.apply() is cloning Array + // (adding method to Array has a lot of troubles) + // refer: http://la.ma.la/blog/diary_200510062243.htm + getAvailableCommands: function() { + return this.getValue(this.constants.NAME_PREMIUM_NO) + ? this.constants.COMMAND_NORMAL.concat(this.constants.COMMAND_PREMIUM) + : Array.apply(null, this.constants.COMMAND_NORMAL) + } + +}; + +// global object +var controller = new NicoPlayerController(); + +// command register +liberator.commands.addUserCommand( + ['nicoinfo'], + 'display player information', + function() { + try { + let info = [ + 'player version : ' + controller.getPlayerVersion(), + 'controller version : ' + controller.getControllerVersion(), + ].join("\n"); + liberator.echo(info, liberator.commandline.FORCE_MULTILINE); + } + catch(e) { liberator.echoerr(e); } + }, + {} +); + +liberator.commands.addUserCommand( + ['nicopause'], + 'toggle play / pause', + function() { + try { controller.togglePlay(); } + catch(e) { liberator.echoerr(e); } + }, + {} +); + +liberator.commands.addUserCommand( + ['nicomute'], + 'toggle mute', + function() { + try { controller.toggleMute(); } + catch(e) { liberator.echoerr(e); } + }, + {} +); + +liberator.commands.addUserCommand( + ['nicommentvisible'], + 'toggle comment visible', + function() { + try { controller.toggleCommentVisible(); } + catch(e) { liberator.echoerr(e); } + }, + {} +); + +liberator.commands.addUserCommand( + ['nicorepeat'], + 'toggle repeat', + function() { + try { controller.toggleRepeat(); } + catch(e) { liberator.echoerr(e); } + }, + {} +); + +liberator.commands.addUserCommand( + ['nicoseek'], + 'controll seek bar', + function(arg, special) { + try { + special ? controller.seekBy(arg) : controller.seekTo(arg); + } + catch(e) { liberator.echoerr(e); } + }, + { + bang: true, + } +); + +liberator.commands.addUserCommand( + ['nicovolume'], + 'controll volume', + function(arg, special) { + try { + special ? controller.volumeBy(arg) : controller.volumeTo(arg); + } + catch(e) { liberator.echoerr(e); } + }, + { + bang: true, + } +); + +liberator.commands.addUserCommand( + ['nicosize'], + 'toggle video size', + function() { + try { controller.toggleSize(); } + catch(e) { liberator.echoerr(e); } + }, + {} +); + +liberator.commands.addUserCommand( + ['nicodescription'], + 'toggle display or not the description for video', + function() { + try { controller.toggleDescription(); } + catch(e) { liberator.echoerr(e); } + }, + {} +); + +liberator.commands.addUserCommand( + ['nicomment'], + 'fill comment box', + function(arg) { + try { + let command, comment; + [command, comment] = expandExCommand(arg); + + comment = comment.replace(/ /g, EMSP) + .replace(/ /g, NBSP) + .replace(/<LF>/g, LF); + + if(command) { + controller.setValue('inputArea.MailInput.text', command); + } + controller.setValue('ChatInput.text', comment); + } + catch(e) { liberator.echoerr(e); } + }, + {} +); + +liberator.commands.addUserCommand( + ['nicommand'], + 'fill command box', + function(arg) { + try { controller.setValue('inputArea.MailInput.text', arg); } + catch(e) { liberator.echoerr(e); } + }, + { + completer: function(args) { + var arg = args.string; + + // get available commands by roll + var availableCommands = controller.getAvailableCommands(); + + // for no argument + if(!arg) { return [0, availableCommands]; } + + // make array of inputted words + // and current input word shoud be last (dayone ?) + var inputted = arg.toLowerCase().split(/\s+/); + var current = inputted[inputted.length - 1]; + // complete position is the top of last word + var completePosition = arg.lastIndexOf(' ') + 1; + + // exclude inputted word from candidates + var candidates = availableCommands.filter( function(commandSet) { + for(let i=0, numofInputted=inputted.length ; i<numofInputted ; ++i) { + if(commandSet[0] === inputted[i]) { + inputted.splice(i, 1); + return false; + } + } + return true; + }); + + // display all candidates in after space ' ' + if(inputted[inputted.length - 1] !== current) { + // complete position is the next of last space + completePosition = arg.length + 1; + return [completePosition, candidates]; + } + + // return the set that start with current word + var commands = candidates.filter( function(commandSet) { + return (commandSet[0].indexOf(current) === 0); + }); + + return [completePosition, commands]; + }, + } +); + + +// for ex-command ------------------------------------------------------- +// constants +const MAX_LINE = { + big: 16, + medium: 25, + small: 38, +}; +const EMSP = '\u3000'; +const NBSP = '\u00a0'; +const LF = '\u000a'; +const PROPATIES_DEFAULT = { + fixFlag: false, + max : MAX_LINE['medium'], + line : 1, + size : '', +}; +const COMMAND_SEPARATOR = '|'; + +// functions +function expandExCommand(arg) { + var command, comment; + + // command and comment is separated by COMMAND_SEPARATOR + var temp = arg.split(COMMAND_SEPARATOR); + if(temp.length > 1) { + command = temp.shift(); + comment = temp.join(COMMAND_SEPARATOR); + } + else { + comment = arg; + } + + // ex_command is putted in braces + if(comment.match(/^\{([^{}]+)\}(.+)/)) { + let exCommand = RegExp.$1; + let text = RegExp.$2; + + let properties = analysisExCommand(exCommand); + + // fine tune command about comment size + if(properties.size) { + if(command) { + command = command.replace(/\s*big\s*/g, ' ') + .replace(/\s*medium\s*/g, ' ') + .replace(/\s*small\s*/g, ' '); + } + command += ' ' + properties.size; + } + + // expand!! + comment = buildLineBreakString(properties.line) + text; + if(properties.fixFlag) { + let post = buildLineBreakString(properties.max - properties.line + 1); + comment += post + NBSP; + } + } + + return [command, comment]; +} + +// " " and <LF> on each line +function buildLineBreakString(numof) { + // faster than string concatenate (+, +=) + var string = Array(numof * 2); + for(let i=1 ; i<numof ; ++i) { + string.push(NBSP); + string.push(LF); + } + + return string.join(''); +} + +// RegExp hell +function analysisExCommand(exCommand) { + // default set + var properties = PROPATIES_DEFAULT; + + // fix or not + if(exCommand.match(/\bfix\b/)) { + properties.fixFlag = true; + } + + // comment size and max line + if(exCommand.match(/\b(big|medium|small)\b/)) { + properties.size = RegExp.$1; + properties.max = MAX_LINE[properties.size]; + } + else if(exCommand.match(/\bmax(\d+)\b/)) { + properties.max = RegExp.$1; + } + + // line + if(exCommand.match(/\bline(-?\d+)\b/)) { + let line = parseInt(RegExp.$1, 10); + if(line < 0) line = properties.max + line + 1; + if(line > properties.max) line = properties.max; + properties.line = line; + } + + return properties; +} +})(); + +// vim: set sw=4 ts=4 et; diff --git a/nnp_cooperation.js b/nnp_cooperation.js new file mode 100644 index 0000000..71c2d1f --- /dev/null +++ b/nnp_cooperation.js @@ -0,0 +1,265 @@ +/* + * ==VimperatorPlugin== + * @name niconicoplaylist_cooperation.js + * @description this script give you keyboard opration for NicoNicoPlaylist. + * @description-ja NicoNicoPlaylist をキーボードで操作できるようにする。 + * @author janus_wel <janus_wel@fb3.so-net.ne.jp> + * @version 0.32 + * @minversion 2.0pre 2008/10/16 + * ==/VimperatorPlugin== + * + * CONSTRAINT + * need NicoNicoPlaylist version 1.12 or above + * + * LICENSE + * New BSD License + * + * USAGE + * :nnppushallvideos + * 現在のページ内のすべての動画を再生リストに送る。 + * ランキングやマイリストのほか、動画ページではオススメ動画が追加される。 + * :nnppushthisvideo + * 現在見ている動画を再生リストに送る。 + * :nnpplaynext [next] + * 再生リストの次の動画を再生する。 + * :nnpremove [index] + * index 番目の動画を再生リストから取り除く。 index は 0 から数える。 + * 指定しない場合は一番上が取り除かれる。 + * :nnpclear + * 再生リストをすべてクリアする。 + * :nnpgetlist [numof] + * 再生リストの上から numof 個を表示する。指定しない場合は g:nnp_coop_numoflist が使われる。 + * :nnprandom + * ランダムモードの on / off + * :nnploop + * ループモードの on / off + * :nnpfullscreen + * 全画面モードの on / off + * + * VARIABLES + * g:nnp_coop_numoflist + * :NNPGetList で表示するリストの個数を指定する。デフォルトは 10 。 + * + * HISTORY + * 2008/07/11 ver. 0.10 - initial written. + * 2008/07/15 ver. 0.20 - refactoring. + * 2008/09/26 ver. 0.30 - change XPath expression. + * - correspond mode toggling (fullscreen, random, loop). + * - change caption: display now-playing title and mode's statuses. + * - mode's statuses are displayed with the following word. + * R: random mode is on + * L: loop mode is on + * F: fullscreen mode is on + * 2008/09/28 ver. 0.31 - bugfix :nnpgetlist in ranking page. + * + * */ +/* +以下のコードを _vimperatorrc に貼り付けると幸せになれるかも。 +コマンド ( [',nn'] や [',nr'] の部分 ) は適宜変えてね。 + +javascript <<EOM + +// [N],nn +// N 番目の動画を再生する。 +// 指定なしの場合次の動画が再生される。 +liberator.mappings.addUserMap( + [liberator.modes.NORMAL], + [',nn'], + 'play next item in NicoNicoPlaylist', + function(count) { + if(count === -1) count = 1; + liberator.execute(':nnpplaynext ' + count); + }, + { flags: liberator.Mappings.flags.COUNT } +); + +// [N],nr +// 上から N 個の動画を削除する。 +// 指定なしの場合一番上の動画が削除される。 +liberator.mappings.addUserMap( + [liberator.modes.NORMAL], + [',nr'], + 'remove item in NicoNicoPlaylist', + function(count) { + if(count === -1) count = 1; + for(let i=0 ; i<count ; ++i) liberator.execute(':nnpremove'); + liberator.execute(':nnpgetlist'); + }, + { flags: liberator.Mappings.flags.COUNT } +); + +EOM + +*/ + +(function(){ + +// thumbnail URL +const thumbnailURL = 'http://tn-skr$HOSTNUMBER.smilevideo.jp/smile?i=$VIDEO_ID'; + +// style +const styles = [ + '<style type="text/css">', + 'table.nnp_coop .index { text-align:right; width:2em; }', + 'table.nnp_coop .thumbnail { text-align:center; }', + 'table.nnp_coop caption { color:green; }', + 'table.nnp_coop thead { text-align:center; }', + '</style>', +].join(''); + +// table +const tableTemplate = [ + '<table class="nnp_coop">', + '$CAPTION', + '$THEAD', + '<tbody>$ITEMS</tbody>', + '</table>', +].join(''); + +// table caption +const captionTemplate = '<caption>now playing: $PLAYTITLE (display $NUMOFDISPLAY / $NUMOFTOTAL$STATUSES)</caption>'; + +// table head +const thead = [ + '<thead>', + '<tr>', + '<td> </td>', + '<td>thumbnail</td>', + '<td>title</td>', + '<td>url</td>', + '</tr>', + '</thead>', +].join(''); + +// item +const itemHTML = [ + '<tr>', + '<td class="index">$INDEX:</td>', + '<td class="thumbnail"><img src="$THUMBNAILURL" width="33" height="25" /></td>', + '<td>$TITLE</td>', + '<td>$URL</td>', + '</tr>', +].join(''); + + +// scrape from div element that inserted by NicoNicoPlaylist +liberator.commands.addUserCommand(['nnpgetlist'], 'get NicoNicoPlaylist', + function (arg) { + // check existence of NicoNicoPlaylist + var playlist = $f('//div[contains(@id, "playlistcontroller_")]'); + if(!playlist) { + liberator.echoerr('NicoNicoPlaylist is not found.'); + return; + } + + var titleNode = $f('//h1') || $f('./html/head/title'); + var playTitle = titleNode.textContent; + var statuses = ''; + if($f('.//input[contains(@id, "-checkbox-random")]', playlist).checked) statuses += 'R'; + if($f('.//input[contains(@id, "-checkbox-loop")]', playlist).checked) statuses += 'L'; + if($f('.//input[contains(@id, "-checkbox-full")]', playlist).checked) statuses += 'F'; + if(statuses) statuses = ' ' + statuses; + + // check existence of items in NicoNicoPlaylist + var nodes = $s('./div[contains(concat(" ", @class, " "), " playlist-list-outer ")]/ul/li/a', playlist); + var nodesLength = nodes.length + if(nodesLength === 0) { + liberator.echoerr('no items in NicoNicoPlaylist.'); + return; + } + + // get number of displayed items + var numofList = arg.match(/^\d+$/) + ? arg + : (liberator.globalVariables.nnp_coop_numoflist || 10); + + // struct display string + // generate data + var items = new Array; + for(let i=0 ; i<nodesLength && i<numofList ; ++i ) { + // get video id + let id = nodes[i].href.match(/\d+$/); + // build thumnail's URL + // refer: http://d.hatena.ne.jp/ZIGOROu/20081014/1223991205 + let thumbnail = thumbnailURL.replace(/\$HOSTNUMBER/g, id % 2 + 1) + .replace(/\$VIDEO_ID/g, id); + // evaluate variables and push to list + items.push( + itemHTML.replace(/\$INDEX/g, i + 1) + .replace(/\$THUMBNAILURL/g, thumbnail) + .replace(/\$TITLE/g, nodes[i].textContent) + .replace(/\$URL/g, nodes[i].href) + ); + } + + // evaluate variables + var caption = captionTemplate + .replace(/\$NUMOFDISPLAY/g, (nodesLength < numofList) ? nodesLength : numofList) + .replace(/\$NUMOFTOTAL/g, nodesLength) + .replace(/\$PLAYTITLE/g, playTitle) + .replace(/\$STATUSES/g, statuses); + + // final processing + var str = styles + tableTemplate.replace(/\$CAPTION/g, caption) + .replace(/\$THEAD/g, thead) + .replace(/\$ITEMS/g, items.join('')); + + liberator.echo(str, liberator.commandline.FORCE_MULTILINE); + },{} +); + +// stuff functions +// return first node +function $f(query, node) { + node = node || window.content.document; + var result = (node.ownerDocument || node).evaluate( + query, + node, + null, + XPathResult.FIRST_ORDERED_NODE_TYPE, + null + ); + return result.singleNodeValue ? result.singleNodeValue : null; +} + +// return snapshot nodes list +function $s(query, node) { + node = node || window.content.document; + var result = (node.ownerDocument || node).evaluate( + query, + node, + null, + XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, + null + ); + var nodes = []; + for(let i=0 ; i<result.snapshotLength ; ++i) nodes.push(result.snapshotItem(i)); + return nodes; +} + +// define other commands +// only send CommandEvent to NicoNicoPlaylist script +[ + [['nnppushallvideos'], "push all videos to NicoNicoPlaylist", 'GMNNPPushAllVideos'], + [['nnppushthisvideo'], "push current video to NicoNicoPlaylist", 'GMNNPPushThisVideo'], + [['nnpplaynext'], "play next in NicoNicoPlaylist", 'GMNNPPlayNext'], + [['nnpremove'], "remove item in NicoNicoPlaylist", 'GMNNPRemove'], + [['nnpclear'], "clear all items in NicoNicoPlaylist", 'GMNNPClear'], + [['nnprandom'], "toggle random mode of NicoNicoPlaylist", 'GMNNPRandom'], + [['nnploop'], "toggle loop mode of NicoNicoPlaylist", 'GMNNPLoop'], + [['nnpfullscreen'], "toggle fullscreen mode of NicoNicoPlaylist", 'GMNNPFullScreen'], +].forEach( + function ([command, description, eventname]){ + liberator.commands.addUserCommand(command, description, + function (arg) { + var r = document.createEvent("CommandEvent"); + r.initCommandEvent(eventname, true, true, arg); + window.content.dispatchEvent(r); + },{} + ); + } +); + +})(); + +// vim:sw=4 ts=4 et: diff --git a/reading.js b/reading.js new file mode 100644 index 0000000..a6d69f0 --- /dev/null +++ b/reading.js @@ -0,0 +1,227 @@ +/* + * ==VimperatorPlugin== + * @name reading.js + * @description update Twitter's status to current URL and comment + * @description-ja 今見てるページの URL とタイトルをコメントといっしょに Twitter に投稿する + * @author janus_wel <janus_wel@fb3.so-net.ne.jp> + * @version 0.22 + * @minversion 2.0pre 2008/10/16 + * ==/VimperatorPlugin== + * + * LICENSE + * New BSD License + * + * USAGE + * :reading [comment] + * Twitter に今見ているページの情報をポストする。 comment はなくてもかまわない。 + * :reading! [comment] + * Twitter に送られる文字列をクリップボードにコピーする。実際には送られない。 + * + * VALIABLE + * g:reading_format + * 投稿する文章の書式設定。以下の変数指定が可能。 + * $SERVICENAME : このプラグインが付加する文字列。 g:reading_servicename で指定する。 + * $TITLE : ページのタイトル。 + * $URL : ページの URL。 + * $SELECTED : visual mode やマウスで選択した文字列。 + * $COMMENT : コメント。これがないとコメントを書いても反映されない。 + * default + * let g:reading_format='$SERVICENAME : $COMMENT "$TITLE" $URL $SELECTED' + * + * g:reading_servicename + * このプラグインが固定で付加する文字列。 + * default + * let g:reading_servicename='I'm reading now' + * + * g:reading_title_default + * タイトルがない場合に付加される文字列。 + * default + * let g:reading_title_default='no title' + * + * HISTORY + * 2008/09/05 ver. 0.10 - initial written. + * 2008/09/24 ver. 0.20 - add URL canonicalization. + * 2008/10/02 ver. 0.21 - fix the bug not apply encodeURI + * to querystring for Pathtraq API. + * */ + +(function() { + +// Twitter's URL to post +const DOMAIN = 'http://twitter.com/'; +const POST_URL = 'https://twitter.com/statuses/update.json'; + +// information functions +// change XPath query when HTML changed. +function Scraper() {} +Scraper.prototype = { + constants: { + VERSION: '0.22', + }, + + version: function() { return this.constants.VERSION; }, + + getURL: function() { + return liberator.buffer.URL; + }, + + getTitle: function() { + var title = $f('//title'); + return title + ? title.text.replace(/^\s+/, '') + .replace(/\s+$/, '') + .replace(/\n/g, ' ') + : null; + }, + + getSelected: function() { + var selected = window.content.getSelection().toString(); + return selected ? selected : ''; + } +}; + +liberator.commands.addUserCommand(['reading'], "update Twitter's status to current page title, URL and comment", + function(arg, special) { + try { + // build post string ----- + let post_string; + + // get value from global variable or set default + let format = liberator.globalVariables.reading_format || '$SERVICENAME : $COMMENT "$TITLE" $URL $SELECTED'; + let serviceName = liberator.globalVariables.reading_servicename || 'I\'m reading now'; + let title_default = liberator.globalVariables.reading_title_default || 'no title'; + + let scraper = new Scraper; + let title = scraper.getTitle() || title_default; + let canonicalizedURL = canonicalizeURL(scraper.getURL()); + + // expand variable ( evaluate variable ? ) + post_string = format.replace(/\$SERVICENAME/g, serviceName) + .replace(/\$TITLE/g, title) + .replace(/\$URL/g, canonicalizedURL) + .replace(/\$SELECTED/g, scraper.getSelected()) + .replace(/\$COMMENT/g, arg); + + // ':matanico!' display the evaluated format. + if(special) { + liberator.util.copyToClipboard(post_string, true); + return; + } + + // ready posting ----- + // URI encode + let parameter = 'status=' + encodeURIComponent(post_string); + + // get user account for Twitter + let [user, pass] = getUserAccount(DOMAIN, POST_URL, null); + + // send status + let req = new XMLHttpRequest(); + if(req) { + req.open('POST', POST_URL, true, user, pass); + req.onreadystatechange = function() { + if(req.readyState == 4) { + if(req.status == 200) liberator.echo('Posted ' + post_string); + else throw new Error('failure in posting status to Twitter. HTTP status code : ' + req.status); + } + }; + req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + req.send(parameter); + } + } + catch(e) { + liberator.echoerr(e.message); + liberator.log(e.message); + } + }, + // complete logic is none. + { + bang: true, + } +); + +// stuff functions +function $f(query, node) { + node = node || window.content.document; + var result = (node.ownerDocument || node).evaluate( + query, + node, + null, + XPathResult.FIRST_ORDERED_NODE_TYPE, + null + ); + return result.singleNodeValue ? result.singleNodeValue : null; +} + +function $s(query, node) { + node = node || window.content.document; + var result = (node.ownerDocument || node).evaluate( + query, + node, + null, + XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, + null + ); + var nodes = []; + for(let i=0 ; i<result.snapshotLength ; ++i) nodes.push(result.snapshotItem(i)); + return nodes; +} + +function canonicalizeURL(url) { + const PATHTRAQ_CANONICALIZE_URL_API = 'http://api.pathtraq.com/normalize_url2?api=json&url='; + + var req = new XMLHttpRequest(); + req.open('GET', PATHTRAQ_CANONICALIZE_URL_API + encodeURI(url), false); + req.send(null); + if(req.status === 200) { + let canonicalized = req.responseText.replace(/^"/, '').replace(/"$/, ''); + return canonicalized ? canonicalized : url; + } + else { + throw new Error(req.status + ' ' + req.statusText + "\n" + req.responseHeaders); + } +} + +// user account manager +// from direct_bookmark.js +// thanks to Trapezoid +function getUserAccount(form, post, arg) { + var user, password; + try { + let passwordManager = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager); + let logins = passwordManager.findLogins({}, form, post, arg); + if(logins.length > 0) { + [user, password] = [logins[0].username, logins[0].password]; + } else { + let promptUser = { value : '' }, promptPass = { value : '' }; + let promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"] + .getService(Ci.nsIPromptService); + + let nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1", + Ci.nsILoginInfo, + "init"); + + let ret = promptSvc.promptUsernameAndPassword( + window, form, 'Enter e-mail address and password.', + promptUser, promptPass, null, {} + ); + if(ret) { + [user, password] = [promptUser.value, promptPass.value]; + let formLoginInfo = new nsLoginInfo(form, + post, null, + user, password, '', ''); + passwordManager.addLogin(formLoginInfo); + } else { + liberator.echoerr("account not found - " + form); + } + } + } + catch(ex) { + liberator.echoerr("handled exception during getting username and password"); + liberator.log(ex); + } + return [user, password]; +} + +})(); +// vim:sw=4 ts=4 et: diff --git a/youtubeamp.js b/youtubeamp.js new file mode 100644 index 0000000..0699a76 --- /dev/null +++ b/youtubeamp.js @@ -0,0 +1,328 @@ +/* + * ==VimperatorPlugin== + * @name youtubeamp.js + * @description this script gives you keyboard oprations for YouTube.com. + * @description-ja YouTube のプレーヤーをキーボードで操作できるようにする。 + * @author janus_wel <janus_wel@fb3.so-net.ne.jp> + * @version 0.12 + * @minversion 2.0pre 2008/10/16 + * ==/VimperatorPlugin== + * + * LICENSE + * New BSD License + * + * USAGE + * :ytinfo + * プレーヤーに関しての情報を表示する。今のところバージョンだけ。 + * :ytpause + * 再生 / 一時停止を切り替える。 + * :ytmute + * 音声あり / なしを切り替える。 + * :ytsize + * 最大化 / ノーマルを切り替える。動いてない。 + * :ytseek [position] + * 指定した場所にシークする。秒数で指定が可能。 + * 指定なしの場合一番最初にシークする。 + * :ytseek! delta + * 現在の位置から delta 分離れた所にシークする。秒数で指定が可能。 + * マイナスを指定すると戻る。指定なしの場合変化しない。 + * :ytvolume [volume] + * ボリュームを設定する。 0 ~ 100 が指定できる。 + * 指定なしの場合 100 にセットする。 + * :ytvolume! delta + * ボリュームを現在の値から変更する。 -100 ~ +100 を指定可能。 + * 指定なしの場合変化しない。 + * + * SEE ALSO + * http://code.google.com/apis/youtube/js_api_reference.html + * + * HISTORY + * 2008/10/07 ver. 0.10 - initial written. + * */ + +(function() { + +Function.prototype.bind = function(object) { + var __method = this; + return function() { + return __method.apply(object, arguments); + }; +}; + +// class definition +// YouTubePlayerController Class +function YouTubePlayerController() { + this.initialize.apply(this, arguments); +} +YouTubePlayerController.prototype = { + initialize: function() { + this.fuller = this._changeToFull.bind(this); + }, + + constants: { + VERSION: '0.12', + + CARDINAL_NUMBER: 10, + + YOUTUBE_DOMAIN: '.youtube.jp', + YOUTUBE_URL: '^http://[^.]+\\.youtube\\.com/', + WATCH_URL: 'http://[^.]+\\.youtube\\.com/watch', + WATCH_PAGE: 1, + + PLAYER_NODE_ID: 'movie_player', + + STATE_PLAYING: 1, + + SIZE_WIDTH_DEFAULT: 480, + SIZE_HEIGHT_DEFAULT: 385, + + NAME_PLAYER_VERSION: 'PLAYER_VERSION', + + SEEKTO_DEFAULT: 0, + SEEKBY_DEFAULT: 0, + VOLUMETO_DEFAULT: 100, + VOLUMEBY_DEFAULT: 0, + + HIDE_NODES: [ + 'old-masthead', + 'watch-vid-title', + 'watch-other-vids', + 'old-footer', + 'copyright', + 'watch-main-area', + 'watch-comments-stats', + 'watch-video-response', + ], + }, + + getControllerVersion: function() { return this.constants.VERSION; }, + + pagecheck: function() { + if(this.getURL().match(this.constants.WATCH_URL)) return this.constants.WATCH_PAGE; + throw new Error('current tab is not watch page on youtube.com'); + }, + + getURL: function() { return liberator.buffer.URL; }, + + _player: function() { + if(this.pagecheck() === this.constants.WATCH_PAGE) { + let player = this._getElementById(this.constants.PLAYER_NODE_ID); + if(! player) throw new Error('player is not found'); + + return player; + } + return null; + }, + + togglePlay: function() { + var p = this._player(); + (p.getPlayerState() !== this.constants.STATE_PLAYING) + ? p.playVideo() + : p.pauseVideo(); + }, + + toggleMute: function() { + var p = this._player(); + p.isMuted() ? p.unMute() : p.mute(); + }, + + toggleSize: function() { + var p = this._player(); + (p.width == this.constants.SIZE_WIDTH_DEFAULT && p.height == this.constants.SIZE_HEIGHT_DEFAULT) + ? this._fullSize() + : this._normalSize(); + }, + + _changeToFull: function() { + var p = this._player(); + setTimeout(function() { + p.width = content.innerWidth; + p.height = content.innerHeight; + }, 0); + }, + + _getElementById: function(id) { + var e = window.content.document.getElementById(id); + if(!e) return null; + + return e.wrappedJSObject + ? e.wrappedJSObject + : e; + }, + + _fullSize: function() { + var b = this._getElementById('baseDiv'); + this.defMargin = b.style.margin; + this.defPadding = b.style.padding; + this.defWidth = b.style.width; + b.style.margin = 0; + b.style.padding = 0; + b.style.width = '100%'; + + for(let i=0, max=this.constants.HIDE_NODES.length ; i<max ; ++i) { + let h = this._getElementById(this.constants.HIDE_NODES[i]); + if(h) { h.style.display = 'none'; } + } + + this._changeToFull(); + + window.addEventListener( + 'resize', + this.fuller, + false + ); + }, + + _normalSize: function() { + var b = this._getElementById('baseDiv'); + b.style.margin = this.defMargin; + b.style.padding = this.defPadding; + b.style.width = this.defWidth; + + for(let i=0, max=this.constants.HIDE_NODES.length ; i<max ; ++i) { + let h = this._getElementById(this.constants.HIDE_NODES[i]); + if(h) { h.style.display = 'block'; } + } + + var p = this._player(); + p.width = this.constants.SIZE_WIDTH_DEFAULT; + p.height = this.constants.SIZE_HEIGHT_DEFAULT; + + window.removeEventListener( + 'resize', + this.fuller, + false + ); + }, + + seekTo: function(position) { + if(position) { + if(position.match(/^(\d+):(\d+)$/)) { + position = parseInt(RegExp.$1, this.constants.CARDINAL_NUMBER) * 60 + + parseInt(RegExp.$2, this.constants.CARDINAL_NUMBER); + } + if(isNaN(position)) throw new Error('assign unsigned number : seekTo()'); + } + else position = this.constants.SEEKTO_DEFAULT; + + var p = this._player(); + p.seekTo(position); + }, + + seekBy: function(delta) { + if(delta) { + if(isNaN(delta)) throw new Error('assign signed number : seekBy()'); + } + else delta = this.constants.SEEKBY_DEFAULT; + + var p = this._player(); + var position = p.getCurrentTime(); + position += parseInt(delta, this.constants.CARDINAL_NUMBER); + + p.seekTo(position); + }, + + volumeTo: function(volume) { + if(volume) { + if(isNaN(volume)) throw new Error('assign unsigned number : volumeTo()'); + } + else volume = this.constants.VOLUMETO_DEFAULT; + + var p = this._player(); + p.setVolume(volume); + }, + + volumeBy: function(delta) { + if(delta) { + if(isNaN(delta)) throw new Error('assign signed number : volumeBy()'); + } + else delta = this.constants.VOLUMEBY_DEFAULT; + + var p = this._player(); + var volume = p.getVolume(); + volume += parseInt(delta, this.constants.CARDINAL_NUMBER); + + p.setVolume(volume); + }, +}; + +// global object +var controller = new YouTubePlayerController(); + +// command register +liberator.commands.addUserCommand( + ['ytinfo'], + 'display player information', + function() { + try { + let info = [ + 'controller version : ' + controller.getControllerVersion(), + ].join('\n'); + liberator.echo(info, liberator.commandline.FORCE_MULTILINE); + } + catch(e) { liberator.echoerr(e); } + }, + {} +); + +liberator.commands.addUserCommand( + ['ytpause'], + 'toggle play / pause', + function() { + try { controller.togglePlay(); } + catch(e) { liberator.echoerr(e); } + }, + {} +); + +liberator.commands.addUserCommand( + ['ytmute'], + 'toggle mute', + function() { + try { controller.toggleMute(); } + catch(e) { liberator.echoerr(e); } + }, + {} +); + +liberator.commands.addUserCommand( + ['ytseek'], + 'controll seek bar', + function(arg, special) { + try { + special ? controller.seekBy(arg) : controller.seekTo(arg); + } + catch(e) { liberator.echoerr(e); } + }, + { + bang: true, + } +); + +liberator.commands.addUserCommand( + ['ytvolume'], + 'controll volume', + function(arg, special) { + try { + special ? controller.volumeBy(arg) : controller.volumeTo(arg); + } + catch(e) { liberator.echoerr(e); } + }, + { + bang: true, + } +); + +liberator.commands.addUserCommand( + ['ytsize'], + 'toggle video size', + function() { + try { controller.toggleSize(); } + catch(e) { liberator.echoerr(e); } + }, + {} +); + +})() + +// vim: set sw=4 ts=4 et; -- cgit v1.2.3