Skip to content

Instantly share code, notes, and snippets.

@youpy
Forked from teramako/google-plus-imageuploader.js
Created July 18, 2011 17:28
Show Gist options
  • Save youpy/1090106 to your computer and use it in GitHub Desktop.
Save youpy/1090106 to your computer and use it in GitHub Desktop.
[Vimperator-plugin]Google+ Poster
let INFO = <>
<plugin name="GooglePlusPoster" version="0.1"
summary="Post to Google+"
lang="en-US"
xmlns="http://vimperator.org/namespaces/liberator">
<author email="teramako@gmail.com">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 = "&nbsp;&nbsp;";
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 + "&nbsp;&nbsp;").join("");
break;
case "li":
txt = aIndent + (aParentTag == "ol" ? (" " + (aIndex+1)).slice(-2) + ". " : " * ").replace(" ", "&nbsp;", "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 + "&nbsp;&nbsp;" + 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