From 60c9432ac9cec1dc6f35049b9ff830052f4bb3c6 Mon Sep 17 00:00:00 2001
From: anekos
Date: Sun, 17 Jul 2011 02:34:11 +0900
Subject: teramako さんの投稿プラグインを取り込み
---
google-plus-commando.js | 634 +++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 631 insertions(+), 3 deletions(-)
diff --git a/google-plus-commando.js b/google-plus-commando.js
index 855a30f..855c51b 100644
--- a/google-plus-commando.js
+++ b/google-plus-commando.js
@@ -1,7 +1,8 @@
/* NEW BSD LICENSE {{{
Copyright (c) 2011, anekos.
+Copyright (c) 2011, teramako.
All rights reserved.
-
+>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
@@ -35,16 +36,100 @@ THE POSSIBILITY OF SUCH DAMAGE.
// INFO {{{
let INFO =
<>
- Mappings for Google+ Many Mappings and Post command for Google+ require: feedSomeKeys_3.js and x-hint.js and _libly.js Should setup at first when argument is none, select the Google+ tab or open in new tab Post message
+ Map map-keys for command.
+ The possible commands.
+
+
+
+
+
+
+
rc file example
+
+let g:gplus_commando_map_next = "j"
+let g:gplus_commando_map_prev = "k"
+let g:gplus_commando_map_share = "s"
+let g:gplus_commando_map_plusone = "p"
+let g:gplus_commando_map_comment = "c"
+let g:gplus_commando_map_post = "C"
+let g:gplus_commando_map_yank = "y"
+let g:gplus_commando_map_notification = "n"
+let g:gplus_commando_map_submit = "<C-CR>"
+let g:gplus_commando_map_cancel = "<Esc>"
+let g:gplus_commando_map_unfold = "e"
+let g:gplus_commando_map_menu = "m"
+
+
+
>;
// }}}
@@ -508,6 +593,549 @@ let INFO =
// }}}
+ // Post Command {{{
+
+ (function () {
+ const HOME_URL = 'https://plus.google.com/',
+ POST_URL_BASE = 'https://plus.google.com/u/0/_/sharebox/post/';
+
+ /**
+ * ${RUNTIMEPATH}/info/{profileName}/googlePlus のデータ取得/保存
+ * @type {Object}
+ */
+ let store = storage.newMap('googlePlus', {store: true});
+
+ commands.addUserCommand(
+ ['gp', 'googleplus'],
+ 'Post to Google+',
+ function (args) {
+ // ----------------------
+ // -setup オプション
+ // ----------------------
+ if ('-setup' in args) {
+ setupGooglePlus();
+ return;
+ }
+
+ let 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']);
+
+ // 引数が何も無い場合は、Google+のページへ
+ if (!message && !useContents) {
+ let tab = getGooglePlusTab();
+ if (tab) {
+ gBrowser.mTabContainer.selectedItem = tab;
+ } else {
+ liberator.open(HOME_URL, {where: liberator.NEW_TAB});
+ }
+ return;
+ }
+
+ postGooglePlus(new PostData(message, useContents ? page : null, acls));
+ },
+ {
+ 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],
+ ],
+ },
+ true
+ );
+
+ /**
+ * Google+のページから必要データを保存する
+ * @return {Boolean}
+ */
+ function setupGooglePlus () {
+ function onSuccess () {
+ liberator.echomsg('Initialized: googleplus');
+ }
+
+ function onFail () {
+ liberator.echoerr('Faild: initialize googleplus');
+ }
+
+ function getFromWindow (win) {
+ let data = win.OZ_initData;
+ if (!data)
+ return false;
+ // XXX 全てのデータが揃っていないケースがあるようなので、検査する
+ try {
+ store.set('UID', data[2][0]);
+ store.set('AT', data[1][15]);
+ let circles = data[12][0];
+ // CIRCLES[]: [[Name, Description, ID], ...]
+ store.set('CIRCLES', circles.slice(0, circles.length / 2).map(function (c) [c[1][0], c[1][2], c[0][0]]));
+ onSuccess();
+ return true;
+ } catch (e) {
+ liberator.log(e);
+ return false;
+ }
+ }
+
+ // XXX ブラチラ大作戦
+ function getFromMakedBrowser () {
+ let browser = document.createElementNS(XUL, 'browser');
+ browser.setAttribute('type', 'content');
+ browser.setAttribute('src', 'https://plus.google.com/');
+ document.getElementById('main-window').appendChild(browser);
+
+ browser.addEventListener(
+ 'DOMContentLoaded',
+ function (e) {
+ if (e.target !== browser.contentWindow.document)
+ return;
+ browser.removeEventListener('DOMContentLoaded', arguments.callee, false);
+ getFromWindow(browser.contentWindow.wrappedJSObject);
+ browser.parentNode.removeChild(browser);
+ },
+ false
+ );
+ }
+
+ let found = false;
+
+ let tab = getGooglePlusTab();
+ if (tab)
+ found = getFromWindow(tab.linkedBrowser.contentWindow.wrappedJSObject);
+
+ if (!found)
+ getFromMakedBrowser();
+ }
+
+ /**
+ * Google+のタブを取ってくる
+ * @return {Element|null}
+ */
+ function getGooglePlusTab () {
+ let 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) {
+ let data = aPostData.getPostData();
+ let queries = [];
+ for (let key in data)
+ queries.push(key + '=' + encodeURIComponent(data[key]));
+
+ let 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) {
+ let 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 -setup\'');
+ this.AT = store.get('AT', null);
+ liberator.assert(this.AT, 'Google+ Error: AT is not set. Please login and `:googleplus -setup\'');
+
+ this.setACLEnties(aACLs);
+ },
+ get token () {
+ let t = 'oz:' + this.UID + '.' + this.date.getTime().toString(16) + '.' + this.sequence.toString(16);
+ Object.defineProperty(this, 'token', {value: t});
+ return t;
+ },
+ get date () {
+ let d = new Date;
+ Object.defineProperty(this, 'date', {value: d});
+ return d;
+ },
+ get sequence () {
+ let s = PostData.sequence++;
+ Object.defineProperty(this, 'sequence', {value: s});
+ return s;
+ },
+ get reqid () {
+ let 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 () {
+ let 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;
+
+ let 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 () {
+ let 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.title) {
+ yield null;
+ throw StopIteration;
+ }
+ let url = this.page.url;
+ let youtubeReg = /http:\/\/(?:.*\.)?youtube.com\/watch\?v=([a-zA-Z0-9_-]+)[-_.!~*'()a-zA-Z0-9;\/?:@&=+\$,%#]*/;
+ let m = url.match(youtubeReg);
+ for (let i = 0, len = 48; i < len; ++i) {
+ switch(i) {
+ case 3:
+ yield this.page.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('