-
-
Save youpy/1090106 to your computer and use it in GitHub Desktop.
[Vimperator-plugin]Google+ Poster
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let INFO = <> | |
<plugin name="GooglePlusPoster" version="0.1" | |
summary="Post to Google+" | |
lang="en-US" | |
xmlns="http://vimperator.org/namespaces/liberator"> | |
<author email="[email protected]">teramako</author> | |
<license>MPL 1.1</license> | |
<project name="Vimperator" minVersion="3.0"/> | |
<item> | |
<tags>:googleplus-setup</tags> | |
<spec>:googleplus -setup</spec> | |
<spec>:gp -setup</spec> | |
<description> | |
<p>Should setup at first</p> | |
<ol> | |
<li>Login to <a href="htts://plus.google.com/">Google+</a></li> | |
<li>Execute <ex>:googleplus -setup</ex></li> | |
</ol> | |
</description> | |
</item> | |
<item> | |
<tags>:googleplus-nonargs</tags> | |
<spec>:googleplus</spec> | |
<spec>:gp</spec> | |
<description> | |
<p>when argument is none, select the Google+ tab or open in new tab</p> | |
</description> | |
</item> | |
<item> | |
<tags>:googleplus :gp</tags> | |
<spec>:googleplus <oa>-l[link]</oa> <oa>-i[mage] <a>imageURL</a></oa> <oa>-t[o] <a>to</a></oa> <a>message</a></spec> | |
<spec>:gp <oa>-l[ink]</oa> <oa>-i[mage] <a>imageURL</a></oa> <oa>-t[o]> <a>to</a></oa> <a>message</a></spec> | |
<description> | |
<p>Post <a>message</a></p> | |
<dl> | |
<dt>-link</dt> | |
<dd> | |
Add the current URL. If the selections are available, add the selections as relataed page. | |
And when <a>-image</a> option is not specified and image elements is contained in the selections, | |
add the URL of the largest image. | |
</dd> | |
<dt>-image</dt> | |
<dd> | |
Specify image URL | |
</dd> | |
<dt>-to</dt> | |
<dd> | |
Specify the circles. Can set multiple. (Default: Anyone) | |
</dd> | |
</dl> | |
</description> | |
</item> | |
</plugin> | |
</>; | |
var HOME_URL = "https://plus.google.com/", | |
POST_URL_BASE = "https://plus.google.com/u/0/_/sharebox/post/"; | |
/** | |
* ${RUNTIMEPATH}/info/{profileName}/googlePlus のデータ取得/保存 | |
* @type {Object} | |
*/ | |
var store = storage.newMap("googlePlus", { store: true }); | |
commands.addUserCommand(["gp", "googleplus"], "Google+", | |
function (args) { | |
// ---------------------- | |
// -setup オプション | |
// ---------------------- | |
if ("-setup" in args) { | |
setupGooglePlus(); | |
return; | |
} | |
var message = args[0] || "", | |
page = {}, | |
acls = null, | |
useContents = false; | |
// ---------------------- | |
// -list オプション | |
// ---------------------- | |
if ("-l" in args) { | |
let sel = content.getSelection(); | |
page.selection = sel.isCollapsed ? null : sel; | |
page.title = buffer.title; | |
page.url = buffer.URI; | |
useContents = true; | |
} | |
// ---------------------- | |
// -imageURL オプション | |
// ---------------------- | |
if ("-i" in args) { | |
page.image = args["-i"]; | |
useContents = true; | |
} | |
// ---------------------- | |
// -to オプション | |
// ---------------------- | |
if ("-t" in args && args["-t"].indexOf("anyone") == -1) | |
acls = store.get("CIRCLES", []).filter(function(c) this.indexOf(c[0]) != -1, args["-t"]); | |
// ---------------------- | |
// -all オプション | |
// ---------------------- | |
if ("-a" in args) | |
acls = store.get("CIRCLES", []); | |
// 引数が何も無い場合は、Google+のページへ | |
if (!message && !useContents) { | |
let tab = getGooglePlusTab(); | |
if (tab) | |
gBrowser.mTabContainer.selectedItem = tab; | |
else | |
liberator.open(HOME_URL, { where: liberator.NEW_TAB }); | |
return; | |
} | |
var pd = new PostData(message, useContents ? page : null, acls); | |
postGooglePlus(pd); | |
}, { | |
literal: 0, | |
options: [ | |
[["-l", "-link"], commands.OPTION_NOARG], | |
[["-i", "-imageURL"], commands.OPTION_STRING], | |
[["-t", "-to"], commands.OPTION_LIST, null, | |
function (context, args) { | |
let [, prefix] = context.filter.match(/^(.*,)[^,]*$/) || []; | |
if (prefix) | |
context.advance(prefix.length); | |
return [["anyone", "to public"]].concat(Array.slice(store.get("CIRCLES", []))) | |
}], | |
[["-setup"], commands.OPTION_NOARG], | |
[["-a", "-all"], commands.OPTION_NOARG], | |
], | |
},true); | |
/** | |
* Google+のページから必要データを保存する | |
* @return {Boolean} | |
*/ | |
function setupGooglePlus () { | |
var tab = getGooglePlusTab(); | |
if (tab) { | |
let data = tab.linkedBrowser.contentWindow.wrappedJSObject.OZ_initData; | |
if (data) { | |
store.set("UID", data[2][0]); | |
store.set("AT", data[1][15]); | |
let circleData = data[12][0]; | |
let circles = circleData.slice(0, circleData.length / 2).map(function (c) [c[1][0], c[1][2], c[0][0]]); | |
let storedData = []; | |
for(index in circles) { | |
if(circles.hasOwnProperty(index)) { | |
storedData.push(circles[index]); | |
} | |
} | |
// CIRCLES[]: [[Name, Description, ID], ...] | |
store.set("CIRCLES", storedData); | |
liberator.echomsg("Initialized: googleplus"); | |
return true; | |
} | |
} | |
liberator.echoerr("Faild: initialize googleplus"); | |
return false; | |
} | |
/** | |
* Google+のタブを取ってくる | |
* @return {Element|null} | |
*/ | |
function getGooglePlusTab () { | |
var tabs = gBrowser.tabs; | |
for (let i = 0, tab; tab = tabs[i]; ++i) { | |
if (tab.linkedBrowser.currentURI.spec.indexOf(HOME_URL) == 0) { | |
return tab; | |
} | |
} | |
return null; | |
} | |
/** | |
* Post to Google+ | |
* @param {PostData} aPostData | |
*/ | |
function postGooglePlus (aPostData) { | |
var data = aPostData.getPostData(); | |
var queries = []; | |
for (let key in data) | |
queries.push(key + "=" + encodeURIComponent(data[key])); | |
var xhr = new XMLHttpRequest(); | |
xhr.mozBackgroundRequest = true; | |
xhr.open("POST", aPostData.POST_URL, true); | |
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); | |
xhr.setRequestHeader("Origin", HOME_URL); | |
xhr.onreadystatechange = postGooglePlus.readyStateChange; | |
xhr.send(queries.join("&")); | |
} | |
/** | |
* Google+への送信状況を表示する | |
* @param {Event} aEvent | |
* aEvent.target は XMLHttpRequestオブジェクト | |
*/ | |
postGooglePlus.readyStateChange = function GooglePlus_readyStateChange (aEvent) { | |
var xhr = aEvent.target, | |
msg = "Google+: ", | |
XBW = window.XULBrowserWindow; | |
if (xhr.readyState == 4) { | |
msg += (xhr.status == 200) ? "Posted" : "Post faild (" + xhr.statusText + ")"; | |
window.setTimeout(function(XBW, msg){ | |
if (XBW.jsDefaultStatus.indexOf("Google+:") == 0) | |
XBW.setJSDefaultStatus(""); | |
}, 2000, XBW, msg); | |
} else { | |
msg += "sending..."; | |
} | |
liberator.log(msg, 0); | |
XBW.setJSDefaultStatus(msg); | |
}; | |
XPCOMUtils.defineLazyServiceGetter(this, "MIME", "@mozilla.org/mime;1", "nsIMIMEService"); | |
/** | |
* Google+への送信データ生成 | |
* @Constructor | |
* @param {String} aMessage | |
* @param {Object} aPage 現ページのコンテンツ情報 | |
* @param {Selection} [aPage.selection] 選択オブジェクト | |
* @param {String} [apage.title] 現ページのタイトル | |
* @param {String} [aPage.url] 現ページURL | |
* @param {String} [aPage.image] 表示させたい画像URL | |
* @param {Array} aACLs ACL[] | |
*/ | |
function PostData () { this.init.apply(this, arguments); } | |
PostData.sequence = 0; | |
PostData.prototype = { | |
init: function PD_init (aMessage, aPage, aACLs) { | |
this.message = aMessage; | |
this.page = aPage || null; | |
this.UID = store.get("UID", null); | |
liberator.assert(this.UID, "Google+ Error: UID is not set. Please login and `:googleplus -init'"); | |
this.AT = store.get("AT", null); | |
liberator.assert(this.AT, "Google+ Error: AT is not set. Please login and `:googleplus -init'"); | |
this.setACLEnties(aACLs); | |
}, | |
get token () { | |
var t = "oz:" + this.UID + "." + this.date.getTime().toString(16) + "." + this.sequence.toString(16); | |
Object.defineProperty(this, "token", { value: t, }); | |
return t; | |
}, | |
get date () { | |
var d = new Date; | |
Object.defineProperty(this, "date", { value: d, }); | |
return d; | |
}, | |
get sequence () { | |
var s = PostData.sequence++; | |
Object.defineProperty(this, "sequence", { value: s }); | |
return s; | |
}, | |
get reqid () { | |
var r = this.date.getHours() + 3600 + this.date.getMinutes() + 60 + this.date.getSeconds() + this.sequence * 100000; | |
Object.defineProperty(this, "reqid", { value: r }); | |
return r; | |
}, | |
get POST_URL () { | |
var url = POST_URL_BASE + "?_reqid=" + this.reqid + "&rt=j"; | |
Object.defineProperty(this, "POST_URL", { value: url }); | |
return url | |
}, | |
aclEntries: [{ | |
scope: { | |
scopeType: "anyone", | |
name: "Anyone", | |
id: "anyone", | |
me: true, | |
requiresKey: false | |
}, | |
role: 20, | |
}, { | |
scope: { | |
scopeType: "anyone", | |
name: "Anyone", | |
id: "anyone", | |
me: true, | |
requiresKey: false, | |
}, | |
role: 60 | |
}], | |
setACLEnties: function PD_setACLEnties (aACLs) { | |
if (!aACLs || aACLs.length == 0) | |
return this.aclEntries = Object.getPrototypeOf(this).aclEntries; | |
var entries = []; | |
for (let i = 0, len = aACLs.length; i < len; ++i) { | |
let acl = aACLs[i]; | |
let scope = { | |
scopeType: "focusGroup", | |
name: acl[0], | |
id: this.UID + "." + acl[2], | |
me: false, | |
requiresKey: false, | |
groupType: "p" | |
}; | |
entries.push({ scope: scope, role: 60 }); | |
entries.push({ scope: scope, role: 20 }); | |
} | |
return this.aclEntries = entries; | |
}, | |
getPostData: function PD_getPostData () { | |
var spar = [v for each(v in this.generateSpar())]; | |
return { | |
spar: JSON.stringify(spar), | |
at : this.AT | |
}; | |
}, | |
generateSpar: function PD_generateSpar() { | |
for (let i = 0, len = 17; i < len; ++i) { | |
switch (i) { | |
case 0: | |
yield this.message; | |
break; | |
case 1: | |
yield this.token; | |
break; | |
case 6: | |
if (this.page) { | |
let link = [v for each(v in this.generateLink())], | |
photo = []; | |
if (link.length > 0) { | |
photo = [v for each(v in this.generateImage())]; | |
yield JSON.stringify([JSON.stringify(link), JSON.stringify(photo)]); | |
} else { | |
yield JSON.stringify([JSON.stringify(link)]); | |
} | |
} else { | |
yield null; | |
} | |
break; | |
case 8: | |
yield JSON.stringify({ aclEntries: this.aclEntries }); | |
break; | |
case 9: | |
case 11: | |
case 12: | |
yield true; | |
break; | |
case 15: | |
case 16: | |
yield false; | |
break; | |
case 10: | |
case 14: | |
yield []; | |
break; | |
default: | |
yield null; | |
break; | |
} | |
} | |
}, | |
generateLink: function PD_generateLink () { | |
if (!this.page.url && !this.page.image) { | |
yield null; | |
throw StopIteration; | |
} | |
var url = this.page.url || this.page.image, | |
title = this.page.title || url; | |
var youtubeReg = /http:\/\/(?:.*\.)?youtube.com\/watch\?v=([a-zA-Z0-9_-]+)[-_.!~*'()a-zA-Z0-9;\/?:@&=+\$,%#]*/; | |
var m = url.match(youtubeReg); | |
for (let i = 0, len = 48; i < len; ++i) { | |
switch(i) { | |
case 3: | |
yield title; | |
break; | |
case 5: | |
yield m ? [null, "http://www.youtube.com/v/" + m[1] + "&hl=en&fs=1&autoplay=1", 385, 640] : null; | |
break; | |
case 9: | |
yield m ? [[null, content.wrappedJSObject.yt.config_.VIDEO_USERNAME, "uploader"]] : []; | |
break; | |
case 21: | |
if (this.page.selection) { | |
let sels = []; | |
let image = ("image" in this.page), imgElms = []; | |
for (let k = 0, count = this.page.selection.rangeCount; k < count; ++k) { | |
let r = this.page.selection.getRangeAt(k), | |
fragment = r.cloneContents(); | |
sels.push(node2txt(fragment, r.commonAncestorContainer.localName)); | |
if (!image) { | |
imgElms.push.apply(imgElms, Array.slice(fragment.querySelectorAll("img"))); | |
} | |
} | |
if (imgElms.length > 0) | |
this.page.image = imgElms.reduce(function(p, c) (p.width * p.height < c.width * c.height) ? c : p).src; | |
yield sels.join("<br/>(snip)<br/>"); | |
} else { | |
yield ""; | |
} | |
break; | |
case 24: | |
yield m ? | |
[null, url, null, "application/x-shockwave-flash", "video"] : | |
[null, url, null, "text/html", "document"]; | |
break; | |
case 41: | |
let imageURL = m ? | |
"http://ytimg.googleusercontent.com/vi/" + m[1] + "/default.jpg" : | |
"//s2.googleusercontent.com/s2/favicons?domain=" + util.createURI(url).host; | |
yield [[null, imageURL, null, null], [null, imageURL, null, null]]; | |
break; | |
case 47: | |
yield [[null, (m ? "youtube" : ""), "http://google.com/profiles/media/provider"]]; | |
break; | |
default: | |
yield null; | |
} | |
} | |
}, | |
generateImage: function PD_generateImage() { | |
if (this.page.image) { | |
let uri = util.createURI(this.page.image); | |
let reg = /https?:\/\/[^\s]+\.(jpe?g|png|gif)/i; | |
let mime = ""; | |
try { | |
mime = MIME.getTypeFromURI(uri); | |
} catch(e) { | |
if (url.host == "gazo.com") { | |
mime = "image/png"; | |
} else { | |
yield null; | |
throw StopIteration; | |
} | |
} | |
for (let i = 0, len = 48; i < len; ++i) { | |
switch(i) { | |
case 5: | |
yield [null, uri.spec]; | |
break; | |
case 9: | |
yield []; | |
break; | |
case 24: | |
yield [null, uri.spec, null, mime, "photo", null,null,null,null,null,null,null,null,null]; | |
break; | |
case 41: | |
yield [[null, uri.spec, null, null], [null, uri.spec, null, null]]; | |
break; | |
case 47: | |
yield [[null,"images","http://google.com/profiles/media/provider"]]; | |
break; | |
default: | |
yield null; | |
} | |
} | |
} else { | |
yield null; | |
} | |
}, | |
}; | |
/** | |
* ノードをHTMLテキストに変換 | |
* @param {Node} aNode | |
* @param {String} [aParentTag] 親ノードのタグ名 | |
* @param {String} [aIndent] インデント文字列 | |
* @param {Number} [aIndex] ノード番号(ol>li 時のみ使用) | |
* @return {String} | |
*/ | |
function node2txt (aNode, aParentTag, aIndent, aIndex) { | |
var txt = ""; | |
switch (aNode.nodeType) { | |
case Node.DOCUMENT_NODE: // 9 | |
case Node.DOCUMENT_FRAGMENT_NODE: // 11 | |
switch (aParentTag) { | |
case "ol": | |
case "ul": | |
case "dl": | |
aIndent = " "; | |
break; | |
default: | |
aIndent = ""; | |
} | |
txt = nodelist2txt(aNode.childNodes, aParentTag, aIndent).join(""); | |
break; | |
case Node.TEXT_NODE: // 3 | |
txt = aNode.nodeValue; | |
break; | |
case Node.ELEMENT_NODE: // 1 | |
let localName = aNode.localName, | |
children = aNode.childNodes; | |
switch (localName) { | |
case "ul": | |
case "ol": | |
case "dl": | |
txt = nodelist2txt(children, localName, aIndent + " ").join(""); | |
break; | |
case "li": | |
txt = aIndent + (aParentTag == "ol" ? (" " + (aIndex+1)).slice(-2) + ". " : " * ").replace(" ", " ", "g") + | |
nodelist2txt(children, "li", aIndent).join("") + | |
"<br/>\n"; | |
break; | |
case "dt": | |
txt = aIndent + "<b>" + nodelist2txt(children, localName, aIndent) + "</b>:<br/>\n"; | |
break; | |
case "dd": | |
txt = aIndent + " " + nodelist2txt(children, localName, aIndent) + "<br/>\n"; | |
break; | |
case "br": | |
txt = "<br/>\n"; | |
break; | |
case "img": | |
txt = "<img src=" + aNode.src.quote() + " width=\"" + aNode.width + "\" height=\"" + aNode.height + "\"/>"; | |
break; | |
case "p": | |
txt = nodelist2txt(children, "p", "").join("") + "<br/>\n"; | |
break; | |
case "a": | |
if (aNode.hasAttribute("href") && aNode.href.indexOf("http") == 0) { | |
txt = "<a href=" + aNode.href.quote() + (aNode.title ? " title=" + aNode.title.quote() : "") + ">" + | |
nodelist2txt(children, "a", "").join("") + | |
"</a>"; | |
break; | |
} | |
default: | |
txt = '<' + localName + '>' + | |
nodelist2txt(children, localName, aIndent).join("") + | |
'</' + localName + '>'; | |
} | |
break; | |
} | |
return txt; | |
} | |
/** | |
* NodeListの子をテキストにして配列で返す | |
* @param {NodeList} aChildNoes | |
* @param {String} aParentTag | |
* @param {String} aIndent | |
* @return {String[]} | |
*/ | |
function nodelist2txt (aChildNodes, aParentTag, aIndent) { | |
var a = [], index = 0; | |
for (let i = 0, len = aChildNodes.length, child; child = aChildNodes[i]; ++i){ | |
let txt = node2txt(child, aParentTag, aIndent, index); | |
if (txt) { | |
a.push(txt); | |
++index; | |
} | |
} | |
return a; | |
} | |
// vim: sw=2 ts=2 et: |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment