diff options
| author | teramako | 2011-07-21 21:33:48 +0900 | 
|---|---|---|
| committer | teramako | 2011-07-21 21:33:48 +0900 | 
| commit | 0cc570d53e1027585b435dd5fbf791379f1d2dc4 (patch) | |
| tree | 0d4a2da55c8850cb9679140e3b5054caf66cac81 | |
| parent | 8e7cd570e569916a4bb3ddea8d62054ee27a2926 (diff) | |
| download | vimperator-plugins-0cc570d53e1027585b435dd5fbf791379f1d2dc4.tar.bz2 | |
merge Google+ Poster
| -rw-r--r-- | google-plus-commando.js | 596 | 
1 files changed, 595 insertions, 1 deletions
diff --git a/google-plus-commando.js b/google-plus-commando.js index 509bfb8..b5ec8b3 100644 --- a/google-plus-commando.js +++ b/google-plus-commando.js @@ -210,7 +210,80 @@ let INFO =          let win = document.commandDispatcher.focusedWindow;          return get1() || get2(); -      } +      }, + +      /** +       * ノードをHTMLテキストに変換 +       * @param {Node} aNode +       * @param {String} [aParentTag] 親ノードのタグ名 +       * @param {String} [aIndent]    インデント文字列 +       * @param {Number} [aIndex]     ノード番号(ol>li 時のみ使用) +       * @return {String} +       */ +      node2txt: function (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.replace(/\s+/g, " "); +          break; +        case Node.ELEMENT_NODE: // 1 +          let localName = aNode.localName, +              children = aNode.childNodes; +          switch (localName) { +          case "ul": +          case "ol": +          case "dl": +            txt = "<br/>\n" + 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; +      },      };      function MakeElement (constructor, root) { @@ -293,6 +366,25 @@ let INFO =        return self;      } +    /** +     * 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 = Elements.node2txt(child, aParentTag, aIndent, index); +        if (txt) { +          a.push(txt); +          ++index; +        } +      } +      return a; +    } +      return Elements;    })(); @@ -591,10 +683,512 @@ let INFO =    // }}} +  // Define Google+ post command {{{ + +  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] || "", +          acls = null; + +      // ---------------------- +      // -link オプション +      // ---------------------- +      var win = null; +      if ("-link" in args) { +        win = content; +      } +      // ---------------------- +      // -imageURL オプション +      // ---------------------- +      var image = null; +      if ("-imageURL" in args) { +        image = args["-imageURL"]; +      } + +      // ---------------------- +      // -to オプション +      // ---------------------- +      if ("-to" in args && args["-to"].indexOf("anyone") == -1) +        acls = [acl for ([,acl] in Iterator(store.get("CIRCLES", []))) if (args["-to"].indexOf(acl[0]) != -1)]; + +      // 引数が何も無い場合は、Google+のページへ +      if (!message && !win && !image) { +        let tab = getGooglePlusTab(); +        if (tab) +          gBrowser.mTabContainer.selectedItem = tab; +        else +          liberator.open(HOME_URL, { where: liberator.NEW_TAB }); + +        return; +      } +      window.setTimeout(function() { +        var pd = new PostData(message, win, image, acls); +        postGooglePlus(pd); +      }, 0); +    }, { +      literal: 0, +      options: [ +        [["-link", "-l"], commands.OPTION_NOARG], +        [["-imageURL", "-i"], commands.OPTION_STRING], +        [["-to", "-t"], commands.OPTION_LIST, null, +          function (context, args) { +            let [, prefix] = context.filter.match(/^(.*,)[^,]*$/) || []; +            if (prefix) +              context.advance(prefix.length); + +            return [["anyone", "to public"]].concat([v for ([,v] in Iterator(store.get("CIRCLES", [])))]); +          }], +        [["-setup"], 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 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]])); +        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, aWindow, aImageURL, aACLs) { +      this.message = aMessage; +      this.window = aWindow; +      this.imageURL = aImageURL; + +      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.window && !this.imageURL) { +            yield null; +          } else { +            var media = LinkDetector.get(this.window, this.imageURL); +            var data = [JSON.stringify(media.generateData())]; +            if (media.hasPhoto) { +              data.push(JSON.stringify(media.generateData(true))); +            } +            yield JSON.stringify(data); +          } + +          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; +        } +      } +    }, +  }; +  const LinkDetector = (function() { +    var commonProto = { +      init: function (win, imageURL) { +        this.window = win; +        this.imageURL = imageURL; +        if (imageURL) { +          if (win) +            this.hasPhoto = true; + +          this.setupImage(); +        } +      }, +      type: { +        TITLE: 3, +        MEDIA_LINK: 5, +        UPLOADER: 9, +        TEXT: 21, +        TYPE: 24, +        IMAGE: 41, +        PROVIDER: 47, +      }, +      generateData: function (isPhoto) { +        var data = new Array(48); +        data[this.type.TITLE] = this.getTitle(isPhoto); +        data[this.type.MEDIA_LINK] = this.getMediaLink(isPhoto); +        data[this.type.UPLOADER] = this.getUploader(isPhoto); +        data[this.type.TEXT] = this.getContentsText(isPhoto); +        data[this.type.TYPE] = this.getMediaType(isPhoto); +        data[this.type.IMAGE] = this.getMediaImage(isPhoto); +        data[this.type.PROVIDER] = this.getProvider(isPhoto); +        return data; +      }, +      hasPhoto: false, +      imageElement: null, +      setupImage: function () { +        let imgs = content.document.images; +        for (let i = 0, len = imgs.length, img; img = imgs[i]; ++i) { +          if (img.src == this.imageURL) { +            this.imageElement = img; +          } +        } +      }, +      getMimeType: function (uri, defaultType) { +        if (!(uri instanceof Ci.nsIURI)) +          uri = util.createURI(uri); + +        try { +          return MIME.getTypeFromURI(uri); +        } catch (e) {} +        return defaultType; +      }, +      getTitle: function (isPhoto) { +        return (isPhoto || !this.window) ? null : this.window.document.title; +      }, +      getContentsText: function (isPhoto) { +        if (!this.window || isPhoto) +          return null; + +        var sel = this.window.getSelection(); +        if (sel.isCollapsed) +          return ""; + +        var sels = []; +        for (let i = 0, count = sel.rangeCount; i < count; ++i) { +          let r = sel.getRangeAt(i), +              fragment = r.cloneContents(); +          sels.push(Elements.node2txt(fragment, r.commonAncestorContainer.localName)); +        } +        return sels.join("<br/>(snip)<br/>"); +      }, +      getUploader: function () { return []; }, +      getMediaLink: function (isPhoto) { +        if (this.window && !isPhoto) +          return [null, this.window.location.href]; + +        var data = [null, this.imageURL]; +        if (this.imageElement) +          data.push(this.imageElement.height, this.imageElement.width); + +        return data; +      }, +      getMediaType: function (isPhoto) { +        if (isPhoto) { +          var type = this.getMimeType(this.imageURL, "image/jpeg"); +          var data = [null, this.imageURL, null, type, "photo", null,null,null,null,null,null,null]; +          if (this.imageElement) +            data.push(this.imageElement.width, this.imageElement.height); +          else +            data.push(null,null); + +          return data; +        } +        if (this.window && !isPhoto) { +          type = this.window.document.contentType; +          switch (type.split("/")[0]) { +          case "image": +            return [null, this.window.location.href, null, type, "image"]; +          case "text": +          default: +            return [null, this.window.location.href, null, "text/html", "document"]; +          } +        } else if (this.imageURL) { +          type = this.getMimeType(this.imageURL, "image/jpeg"); +          return [null, this.imageURL, null, type, "image"]; +        } +        return null +      }, +      getMediaImage: function (isPhoto) { +        var url; +        if (this.window && !isPhoto) { +          let type = this.window.document.contentType.split("/"); +          if (type[0] != "image") { +            let host = this.window.location.host; +            url = "//s2.googleusercontent.com/s2/favicons?domain=" + host; +            return [ [null, url, null, null], [null, url, null, null] ]; +          } else { +            url = this.window.location.href; +            return [ [null, url, null, null], [null, url, null, null] ]; +          } +        } + +        let data = [null, this.imageURL]; +        let w = null, h = null; +        if (this.imageElement) { +          w = this.imageElement.width, h = this.imageElement.height; +          w = w / h * 120; +          h = 120; +        } +        data.push(h, w); +        return [ data, data ]; +      }, +      getProvider: function (isPhoto) { +        return [ [null, (isPhoto ? "images" : ""), "http://google.com/profiles/media/provider"] ]; +      } +    }; +    var classes = {}, checker = {}; +    function MediaLink() { this.init.apply(this, arguments); }; +    MediaLink.prototype = commonProto; + +    var self = { +      addType: function (name, checkFunc, proto) { +        checker[name] = checkFunc; +        var func = function () { this.init.apply(this, arguments); }; +        proto.__super__ = proto.__proto__ = commonProto; +        func.prototype = proto; +        classes[name] = func; +      }, +      get: function (aWindow, aImageURL) { +        for (let [key, checkFunc] in Iterator(checker)) { +          if (checkFunc(aWindow, aImageURL)) { +            return new classes[key](aWindow, aImageURL); +          } +        } +        return new MediaLink(aWindow, aImageURL); +      } +    }; + +    (function() { +      // ------------------------------------------------------------------------- +      // YouTube +      // ----------------------------------------------------------------------{{{ +      self.addType("youtube", +        function (win) { +          if (!win) return false; + +          return /^https?:\/\/(?:.*\.)?youtube.com\/watch/.test(win.location.href); +        }, { +          get VIDEO_ID () { +            var id = this.window.wrappedJSObject.yt.config_.VIDEO_ID; +            Object.defineProperty(this, "VIDEO_ID", { value: id }); +            return id; +          }, +          getMediaLink: function () [null, "http://www.youtube.com/v/" + this.VIDEO_ID + "&hl=en&fs=1&autoplay=1"], +          getContentsText: function () this.window.document.querySelector("meta[name=description]").content, +          getMediaType: function () [null, this.window.location.href, null, "application/x-shockwave-flash", "video"], +          getMediaImage: function () { +            var url = "https://ytimg.googleusercontent.com/vi/" + this.VIDEO_ID + "/hqdefault.jpg"; +            return [ [null, url, 120, 160], [null, url, 120, 160] ]; +          }, +          getProvider: function () [ [null, "youtube", "http://google.com/profiles/media/provider"] ], +        }); // }}} +      // ------------------------------------------------------------------------- +      // Gyazo +      // ----------------------------------------------------------------------{{{ +      self.addType("gyazo", +        function (win, image) { +          var reg = /^http:\/\/gyazo\.com\/\w+(\.png)?/; +          return reg.test(image); +        }, { +          init: function (win, imageURL) { +            this.window = win; +            if (imageURL.lastIndexOf(".png") != imageURL.length - 4) +              imageURL += ".png"; + +            this.imageURL = imageURL; +            this.hasPhoto = true; +          }, +        }); +      // }}} +    })(); +    return self; +  })(); + +  // }}} +    // Export {{{    __context__.command  = Commands;    __context__.element  = Elements; +  __context__.linkDetector = LinkDetector;    // }}}  | 
