aboutsummaryrefslogtreecommitdiffstats
path: root/twittperator.js
diff options
context:
space:
mode:
Diffstat (limited to 'twittperator.js')
-rwxr-xr-xtwittperator.js1277
1 files changed, 1277 insertions, 0 deletions
diff --git a/twittperator.js b/twittperator.js
new file mode 100755
index 0000000..1ab1d4e
--- /dev/null
+++ b/twittperator.js
@@ -0,0 +1,1277 @@
+var GM_OAuth = <><![CDATA[
+// ==UserScript==
+// @name oauth for Greasemonkey
+// @namespace http://efcl.info/
+// @include http://*
+// ==/UserScript==
+]]></>;
+
+// TwitterOauth for Greasemonkey
+function TwitterOauth(){
+ this.initialize.apply(this, arguments);
+}
+
+TwitterOauth.prototype = (function(){
+
+ // {{{2 oauth.js
+ /*
+ OAuth.js
+ SHA-1.js
+ TwitterOauth for Greasemonkey
+ */
+
+ /*
+ * Copyright 2008 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ /* Here's some JavaScript software for implementing OAuth.
+
+ This isn't as useful as you might hope. OAuth is based around
+ allowing tools and websites to talk to each other. However,
+ JavaScript running in web browsers is hampered by security
+ restrictions that prevent code running on one website from
+ accessing data stored or served on another.
+
+ Before you start hacking, make sure you understand the limitations
+ posed by cross-domain XMLHttpRequest.
+
+ On the bright side, some platforms use JavaScript as their
+ language, but enable the programmer to access other web sites.
+ Examples include Google Gadgets, and Microsoft Vista Sidebar.
+ For those platforms, this library should come in handy.
+ */
+
+ // The HMAC-SHA1 signature method calls b64_hmac_sha1, defined by
+ // http://pajhome.org.uk/crypt/md5/sha1.js
+
+ /* An OAuth message is represented as an object like this:
+ {method: "GET", action: "http://server.com/path", parameters: ...}
+
+ The parameters may be either a map {name: value, name2: value2}
+ or an Array of name-value pairs [[name, value], [name2, value2]].
+ The latter representation is more powerful: it supports parameters
+ in a specific sequence, or several parameters with the same name;
+ for example [["a", 1], ["b", 2], ["a", 3]].
+
+ Parameter names and values are NOT percent-encoded in an object.
+ They must be encoded before transmission and decoded after reception.
+ For example, this message object:
+ {method: "GET", action: "http://server/path", parameters: {p: "x y"}}
+ ... can be transmitted as an HTTP request that begins:
+ GET /path?p=x%20y HTTP/1.0
+ (This isn't a valid OAuth request, since it lacks a signature etc.)
+ Note that the object "x y" is transmitted as x%20y. To encode
+ parameters, you can call OAuth.addToURL, OAuth.formEncode or
+ OAuth.getAuthorization.
+
+ This message object model harmonizes with the browser object model for
+ input elements of an form, whose value property isn't percent encoded.
+ The browser encodes each value before transmitting it. For example,
+ see consumer.setInputs in example/consumer.js.
+ */
+
+ /* This script needs to know what time it is. By default, it uses the local
+ clock (new Date), which is apt to be inaccurate in browsers. To do
+ better, you can load this script from a URL whose query string contains
+ an oauth_timestamp parameter, whose value is a current Unix timestamp.
+ For example, when generating the enclosing document using PHP:
+
+ <script src="oauth.js?oauth_timestamp=<?=time()?>" ...
+
+ Another option is to call OAuth.correctTimestamp with a Unix timestamp.
+ */
+
+
+ var OAuth; if (OAuth == null) OAuth = {};
+ OAuth.setProperties = function setProperties(into, from) {
+ if (into != null && from != null) {
+ for (var key in from) {
+ into[key] = from[key];
+ }
+ }
+ return into;
+ }
+
+ OAuth.setProperties(OAuth, // utility functions
+ {
+ percentEncode: function percentEncode(s) {
+ if (s == null) {
+ return "";
+ }
+ if (s instanceof Array) {
+ var e = "";
+ for (var i = 0; i < s.length; ++s) {
+ if (e != "") e += '&';
+ e += OAuth.percentEncode(s[i]);
+ }
+ return e;
+ }
+ s = encodeURIComponent(s);
+ // Now replace the values which encodeURIComponent doesn't do
+ // encodeURIComponent ignores: - _ . ! ~ * ' ( )
+ // OAuth dictates the only ones you can ignore are: - _ . ~
+ // Source: http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Functions:encodeURIComponent
+ s = s.replace(/\!/g, "%21");
+ s = s.replace(/\*/g, "%2A");
+ s = s.replace(/\'/g, "%27");
+ s = s.replace(/\(/g, "%28");
+ s = s.replace(/\)/g, "%29");
+ return s;
+ }
+ ,
+ decodePercent: function decodePercent(s) {
+ if (s != null) {
+ // Handle application/x-www-form-urlencoded, which is defined by
+ // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
+ s = s.replace(/\+/g, " ");
+ }
+ return decodeURIComponent(s);
+ }
+ ,
+ /** Convert the given parameters to an Array of name-value pairs. */
+ getParameterList: function getParameterList(parameters) {
+ if (parameters == null) {
+ return [];
+ }
+ if (typeof parameters != "object") {
+ return OAuth.decodeForm(parameters + "");
+ }
+ if (parameters instanceof Array) {
+ return parameters;
+ }
+ var list = [];
+ for (var p in parameters) {
+ list.push([p, parameters[p]]);
+ }
+ return list;
+ }
+ ,
+ /** Convert the given parameters to a map from name to value. */
+ getParameterMap: function getParameterMap(parameters) {
+ if (parameters == null) {
+ return {};
+ }
+ if (typeof parameters != "object") {
+ return OAuth.getParameterMap(OAuth.decodeForm(parameters + ""));
+ }
+ if (parameters instanceof Array) {
+ var map = {};
+ for (var p = 0; p < parameters.length; ++p) {
+ var key = parameters[p][0];
+ if (map[key] === undefined) { // first value wins
+ map[key] = parameters[p][1];
+ }
+ }
+ return map;
+ }
+ return parameters;
+ }
+ ,
+ getParameter: function getParameter(parameters, name) {
+ if (parameters instanceof Array) {
+ for (var p = 0; p < parameters.length; ++p) {
+ if (parameters[p][0] == name) {
+ return parameters[p][1]; // first value wins
+ }
+ }
+ } else {
+ return OAuth.getParameterMap(parameters)[name];
+ }
+ return null;
+ }
+ ,
+ formEncode: function formEncode(parameters) {
+ var form = "";
+ var list = OAuth.getParameterList(parameters);
+ for (var p = 0; p < list.length; ++p) {
+ var value = list[p][1];
+ if (value == null) value = "";
+ if (form != "") form += '&';
+ form += OAuth.percentEncode(list[p][0])
+ +'='+ OAuth.percentEncode(value);
+ }
+ return form;
+ }
+ ,
+ decodeForm: function decodeForm(form) {
+ var list = [];
+ var nvps = form.split('&');
+ for (var n = 0; n < nvps.length; ++n) {
+ var nvp = nvps[n];
+ if (nvp == "") {
+ continue;
+ }
+ var equals = nvp.indexOf('=');
+ var name;
+ var value;
+ if (equals < 0) {
+ name = OAuth.decodePercent(nvp);
+ value = null;
+ } else {
+ name = OAuth.decodePercent(nvp.substring(0, equals));
+ value = OAuth.decodePercent(nvp.substring(equals + 1));
+ }
+ list.push([name, value]);
+ }
+ return list;
+ }
+ ,
+ setParameter: function setParameter(message, name, value) {
+ var parameters = message.parameters;
+ if (parameters instanceof Array) {
+ for (var p = 0; p < parameters.length; ++p) {
+ if (parameters[p][0] == name) {
+ if (value === undefined) {
+ parameters.splice(p, 1);
+ } else {
+ parameters[p][1] = value;
+ value = undefined;
+ }
+ }
+ }
+ if (value !== undefined) {
+ parameters.push([name, value]);
+ }
+ } else {
+ parameters = OAuth.getParameterMap(parameters);
+ parameters[name] = value;
+ message.parameters = parameters;
+ }
+ }
+ ,
+ setParameters: function setParameters(message, parameters) {
+ var list = OAuth.getParameterList(parameters);
+ for (var i = 0; i < list.length; ++i) {
+ OAuth.setParameter(message, list[i][0], list[i][1]);
+ }
+ }
+ ,
+ /** Fill in parameters to help construct a request message.
+ This function doesn't fill in every parameter.
+ The accessor object should be like:
+ {consumerKey:'foo', consumerSecret:'bar', accessorSecret:'nurn', token:'krelm', tokenSecret:'blah'}
+ The accessorSecret property is optional.
+ */
+ completeRequest: function completeRequest(message, accessor) {
+ if (message.method == null) {
+ message.method = "GET";
+ }
+ var map = OAuth.getParameterMap(message.parameters);
+ if (map.oauth_consumer_key == null) {
+ OAuth.setParameter(message, "oauth_consumer_key", accessor.consumerKey || "");
+ }
+ if (map.oauth_token == null && accessor.token != null) {
+ OAuth.setParameter(message, "oauth_token", accessor.token);
+ }
+ if (map.oauth_version == null) {
+ OAuth.setParameter(message, "oauth_version", "1.0");
+ }
+ if (map.oauth_timestamp == null) {
+ OAuth.setParameter(message, "oauth_timestamp", OAuth.timestamp());
+ }
+ if (map.oauth_nonce == null) {
+ OAuth.setParameter(message, "oauth_nonce", OAuth.nonce(6));
+ }
+ OAuth.SignatureMethod.sign(message, accessor);
+ }
+ ,
+ setTimestampAndNonce: function setTimestampAndNonce(message) {
+ OAuth.setParameter(message, "oauth_timestamp", OAuth.timestamp());
+ OAuth.setParameter(message, "oauth_nonce", OAuth.nonce(6));
+ }
+ ,
+ addToURL: function addToURL(url, parameters) {
+ newURL = url;
+ if (parameters != null) {
+ var toAdd = OAuth.formEncode(parameters);
+ if (toAdd.length > 0) {
+ var q = url.indexOf('?');
+ if (q < 0) newURL += '?';
+ else newURL += '&';
+ newURL += toAdd;
+ }
+ }
+ return newURL;
+ }
+ ,
+ /** Construct the value of the Authorization header for an HTTP request. */
+ getAuthorizationHeader: function getAuthorizationHeader(realm, parameters) {
+ var header = 'OAuth realm="' + OAuth.percentEncode(realm) + '"';
+ var list = OAuth.getParameterList(parameters);
+ for (var p = 0; p < list.length; ++p) {
+ var parameter = list[p];
+ var name = parameter[0];
+ if (name.indexOf("oauth_") == 0) {
+ header += ',' + OAuth.percentEncode(name) + '="' + OAuth.percentEncode(parameter[1]) + '"';
+ }
+ }
+ return header;
+ }
+ ,
+ /** Correct the time using a parameter from the URL from which the last script was loaded. */
+ correctTimestampFromSrc: function correctTimestampFromSrc(parameterName) {
+ parameterName = parameterName || "oauth_timestamp";
+ var scripts = document.getElementsByTagName('script');
+ if (scripts == null || !scripts.length) return;
+ var src = scripts[scripts.length-1].src;
+ if (!src) return;
+ var q = src.indexOf("?");
+ if (q < 0) return;
+ parameters = OAuth.getParameterMap(OAuth.decodeForm(src.substring(q+1)));
+ var t = parameters[parameterName];
+ if (t == null) return;
+ OAuth.correctTimestamp(t);
+ }
+ ,
+ /** Generate timestamps starting with the given value. */
+ correctTimestamp: function correctTimestamp(timestamp) {
+ OAuth.timeCorrectionMsec = (timestamp * 1000) - (new Date()).getTime();
+ }
+ ,
+ /** The difference between the correct time and my clock. */
+ timeCorrectionMsec: 0
+ ,
+ timestamp: function timestamp() {
+ var t = (new Date()).getTime() + OAuth.timeCorrectionMsec;
+ return Math.floor(t / 1000);
+ }
+ ,
+ nonce: function nonce(length) {
+ var chars = OAuth.nonce.CHARS;
+ var result = "";
+ for (var i = 0; i < length; ++i) {
+ var rnum = Math.floor(Math.random() * chars.length);
+ result += chars.substring(rnum, rnum+1);
+ }
+ return result;
+ }
+ });
+
+ OAuth.nonce.CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
+
+ /** Define a constructor function,
+ without causing trouble to anyone who was using it as a namespace.
+ That is, if parent[name] already existed and had properties,
+ copy those properties into the new constructor.
+ */
+ OAuth.declareClass = function declareClass(parent, name, newConstructor) {
+ var previous = parent[name];
+ parent[name] = newConstructor;
+ if (newConstructor != null && previous != null) {
+ for (var key in previous) {
+ if (key != "prototype") {
+ newConstructor[key] = previous[key];
+ }
+ }
+ }
+ return newConstructor;
+ }
+
+ /** An abstract algorithm for signing messages. */
+ OAuth.declareClass(OAuth, "SignatureMethod", function OAuthSignatureMethod(){});
+
+ OAuth.setProperties(OAuth.SignatureMethod.prototype, // instance members
+ {
+ /** Add a signature to the message. */
+ sign: function sign(message) {
+ var baseString = OAuth.SignatureMethod.getBaseString(message);
+ var signature = this.getSignature(baseString);
+ OAuth.setParameter(message, "oauth_signature", signature);
+ return signature; // just in case someone's interested
+ }
+ ,
+ /** Set the key string for signing. */
+ initialize: function initialize(name, accessor) {
+ var consumerSecret;
+ if (accessor.accessorSecret != null
+ && name.length > 9
+ && name.substring(name.length-9) == "-Accessor")
+ {
+ consumerSecret = accessor.accessorSecret;
+ } else {
+ consumerSecret = accessor.consumerSecret;
+ }
+ this.key = OAuth.percentEncode(consumerSecret)
+ +"&"+ OAuth.percentEncode(accessor.tokenSecret);
+ }
+ });
+
+ /* SignatureMethod expects an accessor object to be like this:
+ {tokenSecret: "lakjsdflkj...", consumerSecret: "QOUEWRI..", accessorSecret: "xcmvzc..."}
+ The accessorSecret property is optional.
+ */
+ // Class members:
+ OAuth.setProperties(OAuth.SignatureMethod, // class members
+ {
+ sign: function sign(message, accessor) {
+ var name = OAuth.getParameterMap(message.parameters).oauth_signature_method;
+ if (name == null || name == "") {
+ name = "HMAC-SHA1";
+ OAuth.setParameter(message, "oauth_signature_method", name);
+ }
+ OAuth.SignatureMethod.newMethod(name, accessor).sign(message);
+ }
+ ,
+ /** Instantiate a SignatureMethod for the given method name. */
+ newMethod: function newMethod(name, accessor) {
+ var impl = OAuth.SignatureMethod.REGISTERED[name];
+ if (impl != null) {
+ var method = new impl();
+ method.initialize(name, accessor);
+ return method;
+ }
+ var err = new Error("signature_method_rejected");
+ var acceptable = "";
+ for (var r in OAuth.SignatureMethod.REGISTERED) {
+ if (acceptable != "") acceptable += '&';
+ acceptable += OAuth.percentEncode(r);
+ }
+ err.oauth_acceptable_signature_methods = acceptable;
+ throw err;
+ }
+ ,
+ /** A map from signature method name to constructor. */
+ REGISTERED : {}
+ ,
+ /** Subsequently, the given constructor will be used for the named methods.
+ The constructor will be called with no parameters.
+ The resulting object should usually implement getSignature(baseString).
+ You can easily define such a constructor by calling makeSubclass, below.
+ */
+ registerMethodClass: function registerMethodClass(names, classConstructor) {
+ for (var n = 0; n < names.length; ++n) {
+ OAuth.SignatureMethod.REGISTERED[names[n]] = classConstructor;
+ }
+ }
+ ,
+ /** Create a subclass of OAuth.SignatureMethod, with the given getSignature function. */
+ makeSubclass: function makeSubclass(getSignatureFunction) {
+ var superClass = OAuth.SignatureMethod;
+ var subClass = function() {
+ superClass.call(this);
+ };
+ subClass.prototype = new superClass();
+ // Delete instance variables from prototype:
+ // delete subclass.prototype... There aren't any.
+ subClass.prototype.getSignature = getSignatureFunction;
+ subClass.prototype.constructor = subClass;
+ return subClass;
+ }
+ ,
+ getBaseString: function getBaseString(message) {
+ var URL = message.action;
+ var q = URL.indexOf('?');
+ var parameters;
+ if (q < 0) {
+ parameters = message.parameters;
+ } else {
+ // Combine the URL query string with the other parameters:
+ parameters = OAuth.decodeForm(URL.substring(q + 1));
+ var toAdd = OAuth.getParameterList(message.parameters);
+ for (var a = 0; a < toAdd.length; ++a) {
+ parameters.push(toAdd[a]);
+ }
+ }
+ return OAuth.percentEncode(message.method.toUpperCase())
+ +'&'+ OAuth.percentEncode(OAuth.SignatureMethod.normalizeUrl(URL))
+ +'&'+ OAuth.percentEncode(OAuth.SignatureMethod.normalizeParameters(parameters));
+ }
+ ,
+ normalizeUrl: function normalizeUrl(url) {
+ var uri = OAuth.SignatureMethod.parseUri(url);
+ var scheme = uri.protocol.toLowerCase();
+ var authority = uri.authority.toLowerCase();
+ var dropPort = (scheme == "http" && uri.port == 80)
+ || (scheme == "https" && uri.port == 443);
+ if (dropPort) {
+ // find the last : in the authority
+ var index = authority.lastIndexOf(":");
+ if (index >= 0) {
+ authority = authority.substring(0, index);
+ }
+ }
+ var path = uri.path;
+ if (!path) {
+ path = "/"; // conforms to RFC 2616 section 3.2.2
+ }
+ // we know that there is no query and no fragment here.
+ return scheme + "://" + authority + path;
+ }
+ ,
+ parseUri: function parseUri (str) {
+ /* This function was adapted from parseUri 1.2.1
+ http://stevenlevithan.com/demo/parseuri/js/assets/parseuri.js
+ */
+ var o = {key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
+ parser: {strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/ }};
+ var m = o.parser.strict.exec(str);
+ var uri = {};
+ var i = 14;
+ while (i--) uri[o.key[i]] = m[i] || "";
+ return uri;
+ }
+ ,
+ normalizeParameters: function normalizeParameters(parameters) {
+ if (parameters == null) {
+ return "";
+ }
+ var list = OAuth.getParameterList(parameters);
+ var sortable = [];
+ for (var p = 0; p < list.length; ++p) {
+ var nvp = list[p];
+ if (nvp[0] != "oauth_signature") {
+ sortable.push([ OAuth.percentEncode(nvp[0])
+ + " " // because it comes before any character that can appear in a percentEncoded string.
+ + OAuth.percentEncode(nvp[1])
+ , nvp]);
+ }
+ }
+ sortable.sort(function(a,b) {
+ if (a[0] < b[0]) return -1;
+ if (a[0] > b[0]) return 1;
+ return 0;
+ });
+ var sorted = [];
+ for (var s = 0; s < sortable.length; ++s) {
+ sorted.push(sortable[s][1]);
+ }
+ return OAuth.formEncode(sorted);
+ }
+ });
+
+ OAuth.SignatureMethod.registerMethodClass(["PLAINTEXT", "PLAINTEXT-Accessor"],
+ OAuth.SignatureMethod.makeSubclass(
+ function getSignature(baseString) {
+ return this.key;
+ }
+ ));
+
+ OAuth.SignatureMethod.registerMethodClass(["HMAC-SHA1", "HMAC-SHA1-Accessor"],
+ OAuth.SignatureMethod.makeSubclass(
+ function getSignature(baseString) {
+ b64pad = '=';
+ var signature = b64_hmac_sha1(this.key, baseString);
+ return signature;
+ }
+ ));
+
+ OAuth.correctTimestamp(new Date());
+ // oauth.js 2}}}
+ // {{{2 sha1.js
+ /*
+ * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
+ * in FIPS PUB 180-1
+ * Version 2.1a Copyright Paul Johnston 2000 - 2002.
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for details.
+ */
+
+ /*
+ * Configurable variables. You may need to tweak these to be compatible with
+ * the server-side, but the defaults work in most cases.
+ */
+ var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
+ var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
+ var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
+
+ /*
+ * These are the functions you'll usually want to call
+ * They take string arguments and return either hex or base-64 encoded strings
+ */
+ function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));}
+ function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));}
+ function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));}
+ function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));}
+ function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));}
+ function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));}
+
+ /*
+ * Perform a simple self-test to see if the VM is working
+ */
+ function sha1_vm_test()
+ {
+ return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d";
+ }
+
+ /*
+ * Calculate the SHA-1 of an array of big-endian words, and a bit length
+ */
+ function core_sha1(x, len)
+ {
+ /* append padding */
+ x[len >> 5] |= 0x80 << (24 - len % 32);
+ x[((len + 64 >> 9) << 4) + 15] = len;
+
+ var w = Array(80);
+ var a = 1732584193;
+ var b = -271733879;
+ var c = -1732584194;
+ var d = 271733878;
+ var e = -1009589776;
+
+ for(var i = 0; i < x.length; i += 16)
+ {
+ var olda = a;
+ var oldb = b;
+ var oldc = c;
+ var oldd = d;
+ var olde = e;
+
+ for(var j = 0; j < 80; j++)
+ {
+ if(j < 16) w[j] = x[i + j];
+ else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
+ var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)),
+ safe_add(safe_add(e, w[j]), sha1_kt(j)));
+ e = d;
+ d = c;
+ c = rol(b, 30);
+ b = a;
+ a = t;
+ }
+
+ a = safe_add(a, olda);
+ b = safe_add(b, oldb);
+ c = safe_add(c, oldc);
+ d = safe_add(d, oldd);
+ e = safe_add(e, olde);
+ }
+ return Array(a, b, c, d, e);
+
+ }
+
+ /*
+ * Perform the appropriate triplet combination function for the current
+ * iteration
+ */
+ function sha1_ft(t, b, c, d)
+ {
+ if(t < 20) return (b & c) | ((~b) & d);
+ if(t < 40) return b ^ c ^ d;
+ if(t < 60) return (b & c) | (b & d) | (c & d);
+ return b ^ c ^ d;
+ }
+
+ /*
+ * Determine the appropriate additive constant for the current iteration
+ */
+ function sha1_kt(t)
+ {
+ return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 :
+ (t < 60) ? -1894007588 : -899497514;
+ }
+
+ /*
+ * Calculate the HMAC-SHA1 of a key and some data
+ */
+ function core_hmac_sha1(key, data)
+ {
+ var bkey = str2binb(key);
+ if(bkey.length > 16) bkey = core_sha1(bkey, key.length * chrsz);
+
+ var ipad = Array(16), opad = Array(16);
+ for(var i = 0; i < 16; i++)
+ {
+ ipad[i] = bkey[i] ^ 0x36363636;
+ opad[i] = bkey[i] ^ 0x5C5C5C5C;
+ }
+
+ var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz);
+ return core_sha1(opad.concat(hash), 512 + 160);
+ }
+
+ /*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+ function safe_add(x, y)
+ {
+ var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+ var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+ return (msw << 16) | (lsw & 0xFFFF);
+ }
+
+ /*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+ function rol(num, cnt)
+ {
+ return (num << cnt) | (num >>> (32 - cnt));
+ }
+
+ /*
+ * Convert an 8-bit or 16-bit string to an array of big-endian words
+ * In 8-bit function, characters >255 have their hi-byte silently ignored.
+ */
+ function str2binb(str)
+ {
+ var bin = Array();
+ var mask = (1 << chrsz) - 1;
+ for(var i = 0; i < str.length * chrsz; i += chrsz)
+ bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32);
+ return bin;
+ }
+
+ /*
+ * Convert an array of big-endian words to a string
+ */
+ function binb2str(bin)
+ {
+ var str = "";
+ var mask = (1 << chrsz) - 1;
+ for(var i = 0; i < bin.length * 32; i += chrsz)
+ str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask);
+ return str;
+ }
+
+ /*
+ * Convert an array of big-endian words to a hex string.
+ */
+ function binb2hex(binarray)
+ {
+ var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+ var str = "";
+ for(var i = 0; i < binarray.length * 4; i++)
+ {
+ str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
+ hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF);
+ }
+ return str;
+ }
+
+ /*
+ * Convert an array of big-endian words to a base-64 string
+ */
+ function binb2b64(binarray)
+ {
+ var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ var str = "";
+ for(var i = 0; i < binarray.length * 4; i += 3)
+ {
+ var triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16)
+ | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 )
+ | ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF);
+ for(var j = 0; j < 4; j++)
+ {
+ if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
+ else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
+ }
+ }
+ return str;
+ }
+ // sha1.js 2}}}
+
+ let p = {
+ initialize: function(accessor) {
+ if (accessor)
+ this.accessor = accessor;
+ if (this.isAuthorize()){
+ setup();
+ } else {
+ preSetup();
+ }
+ },
+ // temp for request
+ request : {
+ token :"",// response oauth_token
+ tokenSecret: ""// response oauth_token_secret
+ },
+ // トークンが取得済みかの真偽値を返す
+ isAuthorize : function(){
+ let accessor = this.getAccessor();
+ if (accessor.consumerKey && accessor.consumerSecret &&
+ accessor.token && accessor.tokenSecret) {
+ return true;
+ }else{
+ return false;
+ }
+ },
+ getAccessor: function(){
+ return {
+ consumerKey: this.accessor.get("consumerKey",""),
+ consumerSecret: this.accessor.get("consumerSecret",""),
+ token: this.accessor.get("token",""),
+ tokenSecret: this.accessor.get("tokenSecret","")
+ };
+ },
+ deleteAccessor : function(){
+ var clientInfo = {
+ clientName: this.accessor.get("clientName", ""),
+ consumerKey: this.accessor.get("consumerKey",""),
+ consumerSecret: this.accessor.get("consumerSecret",""),
+ };
+ this.accessor.clear();
+ this.accessor.set("clientName", clientInfo.clientName);
+ this.accessor.set("consumerKey", clientInfo.consumerKey);
+ this.accessor.set("consumerSecret", clientInfo.consumerSecret);
+ },
+ // 認証ページのURLを取得
+ getRequestToken : function(callback){
+ let message = {
+ method: "GET",
+ action: "https://twitter.com/oauth/request_token",
+ parameters: {
+ oauth_signature_method: "HMAC-SHA1",
+ oauth_consumer_key: this.accessor.get("consumerKey","")
+ }
+ };
+ OAuth.setTimestampAndNonce(message);
+ OAuth.SignatureMethod.sign(message, this.getAccessor());
+ var target = OAuth.addToURL(message.action, message.parameters);
+ var self = this;
+ var options = {
+ method: message.method,
+ url: target,
+ onload: function(d) {
+ if(d.status == 200){
+ var res = d.responseText;
+ var parameter = self.getParameter(res);
+ self.request.token = parameter["oauth_token"];
+ self.request.tokenSecret = parameter["oauth_token_secret"];
+ // requestURLを引数にcallback
+ if(callback){
+ callback("https://twitter.com/oauth/authorize?oauth_token="+self.request.token);
+ }
+ }else{
+ alert(d.statusText);
+ }
+ },
+ };
+ xmlhttpRequest(options);
+
+ },
+ setPin: function (pin) {
+ let self = this;
+ this.getAccessToken(pin, function(){
+ liberator.echo("Twittperator: getting access token is success.", true);
+ self.initialize();
+ });
+ },
+ // pinを元にAccess Tokenを取得して保存、callbackにはaccessorオブジェクトを渡す
+ getAccessToken: function(pin ,callback) {
+ var message = {
+ method: "GET",
+ action: "https://twitter.com/oauth/access_token",
+ parameters: {
+ oauth_signature_method: "HMAC-SHA1",
+ oauth_consumer_key: this.accessor.get("consumerKey",""),
+ oauth_token: this.request.token, // Request Token
+ oauth_verifier: pin
+ }
+ };
+ OAuth.setTimestampAndNonce(message);
+ OAuth.SignatureMethod.sign(message, this.request);
+ var target = OAuth.addToURL(message.action, message.parameters);
+ var self = this;
+ var options = {
+ method: message.method,
+ url: target,
+ onload: function(d) {
+ if(d.status == 200){
+ /* 返り値からAccess Token/Access Token Secretを取り出す */
+ var res = d.responseText;
+ var parameter = self.getParameter(res);
+ self.accessor.set("token", parameter["oauth_token"]);
+ self.accessor.set("tokenSecret", parameter["oauth_token_secret"]);
+ // Accessorの保存
+ //self.saveAccessor();
+ if(callback){
+ callback(self.accessor);
+ }
+ }else{
+ alert(d.statusText);
+ }
+ },
+ };
+
+ xmlhttpRequest(options); // 送信
+ },
+ // api+?+query にアクセスした結果をcallbackに渡す
+ get: function(api, query, callback) {
+ var btquery = (query)? "?"+this.buildQuery(query) : "";
+ var message = {
+ method: "GET",
+ action: api + btquery,
+ parameters: {
+ oauth_signature_method: "HMAC-SHA1",
+ oauth_consumer_key: this.accessor.get("consumerKey",""),// queryの構築
+ oauth_token: this.accessor.get("token","") // Access Token
+ }
+ };
+ OAuth.setTimestampAndNonce(message);
+ OAuth.SignatureMethod.sign(message, this.getAccessor());
+ var target = OAuth.addToURL(message.action, message.parameters);
+ var options = {
+ method: message.method,
+ url: target,
+ onload: function(d) {
+ if(d.status == 200){
+ if(callback){
+ callback(d.responseText);
+ }
+ }else{
+ callback(d.statusText);
+ }
+ },
+ };
+ xmlhttpRequest(options); // 送信
+ },
+ post: function(api, content, callback) {
+ var message = {
+ method: "POST",
+ action: api,
+ parameters: {
+ oauth_signature_method: "HMAC-SHA1",
+ oauth_consumer_key: this.accessor.get("consumerKey",""),
+ oauth_token: this.accessor.get("token","") // Access Token
+ }
+ };
+ // 送信するデータをパラメータに追加する
+ for ( var key in content ) {
+ message.parameters[key] = content[key];
+ }
+ OAuth.setTimestampAndNonce(message);
+ OAuth.SignatureMethod.sign(message, this.getAccessor());
+ var target = OAuth.addToURL(message.action, message.parameters);
+ var options = {
+ method: message.method,
+ url: target,
+ onload: function(d) {
+ if (d.status == 200) {
+ if (callback) {
+ callback(d.responseText);
+ }
+ } else {
+ // typeof d == object
+ callback(d);
+ }
+ }
+ };
+ xmlhttpRequest(options); // 送信
+ },
+ // utility関数
+ // http://kevin.vanzonneveld.net
+ urlencode : function (str) {
+ str = (str+'').toString();
+ return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28')
+ .replace(/\)/g, '%29').replace(/\*/g, '%2A').replace(/%20/g, '+');
+ },
+ // オブジェクトからクエリを生成
+ buildQuery : function(formdata, numeric_prefix, arg_separator) {
+ // * example 1: http_build_query({foo: 'bar', php: 'hypertext processor', baz: 'boom', cow: 'milk'}, '', '&amp;');
+ // * returns 1: 'foo=bar&amp;php=hypertext+processor&amp;baz=boom&amp;cow=milk'
+ // * example 2: http_build_query({'php': 'hypertext processor', 0: 'foo', 1: 'bar', 2: 'baz', 3: 'boom', 'cow': 'milk'}, 'myvar_');
+ // * returns 2: 'php=hypertext+processor&myvar_0=foo&myvar_1=bar&myvar_2=baz&myvar_3=boom&cow=milk'
+ var value, key, tmp = [];
+ var self = this;
+ var _http_build_query_helper = function (key, val, arg_separator) {
+ var k, tmp = [];
+ if (val === true) {
+ val = "1";
+ } else if (val === false) {
+ val = "0";
+ }
+ if (val !== null && typeof(val) === "object") {
+ for (k in val) {
+ if (val[k] !== null) {
+ tmp.push(_http_build_query_helper(key + "[" + k + "]", val[k], arg_separator));
+ }
+ }
+ return tmp.join(arg_separator);
+ } else if (typeof(val) !== "function") {
+ return self.urlencode(key) + "=" + self.urlencode(val);
+ } else {
+ throw new Error('There was an error processing for http_build_query().');
+ }
+ };
+
+ if (!arg_separator) {
+ arg_separator = "&";
+ }
+ for (key in formdata) {
+ value = formdata[key];
+ if (numeric_prefix && !isNaN(key)) {
+ key = String(numeric_prefix) + key;
+ }
+ tmp.push(_http_build_query_helper(key, value, arg_separator));
+ }
+
+ return tmp.join(arg_separator);
+ },
+ // Query String から 連想配列を返す
+ getParameter: function(str){
+ var dec = decodeURIComponent;
+ var par = {}, itm;
+ if(typeof(str) == 'undefined') return par;
+ if(str.indexOf('?', 0) > -1) str = str.split('?')[1];
+ str = str.split('&');
+ for(var i = 0; str.length > i; i++){
+ itm = str[i].split("=");
+ if(itm[0] != ''){
+ par[itm[0]] = typeof(itm[1]) == 'undefined' ? true : dec(itm[1]);
+ }
+ }
+ return par;
+ }
+ };
+ return p;
+})();
+
+
+// Twittperator
+function xmlhttpRequest(options){
+ let xhr = new XMLHttpRequest();
+ xhr.open(options.method, options.url, true);
+ if (typeof options.onload == "function"){
+ xhr.onload = function(){
+ options.onload(xhr);
+ }
+ }
+ xhr.send(null);
+}
+
+let accessor = storage.newMap("twittperator", { store: true });
+accessor.set("clientName", "Twittperator");
+accessor.set("consumerKey", "GQWob4E5tCHVQnEVPvmorQ");
+accessor.set("consumerSecret","gVwj45GaW6Sp7gdua6UFyiF910ffIety0sD1dv36Cz8");
+let tw = new TwitterOauth(accessor);
+plugins.twittperator = tw;
+
+let statuses = [];
+let expiredStatus = false;
+let autoStatusUpdate = !!parseInt(liberator.globalVariables.twittperator_auto_status_update || 0);
+let statusValidDuration = parseInt(liberator.globalVariables.twitperator_status_valid_duration || 90);
+let statusRefreshTimer;
+
+function showTL (s) {
+ let html = <style type="text/css"><![CDATA[
+ span.twitter.entry-content a { text-decoration: none; }
+ span.twitter.entry-content.rt:before { content: "RT "; color: silver; }
+ img.twitter.photo { border; 0px; width: 16px; height: 16px; vertical-align: baseline; margin: 1px; }
+ ]]></style>.toSource()
+ .replace(/(?:\r\n|[\r\n])[ \t]*/g, " ") +
+ s.map(function(status){
+ let xml;
+ if ("retweeted_status" in status) {
+ let rt = status.retweeted_status;
+ xml = <>
+ <img src={rt.user.profile_image_url} alt={rt.user.screen_name} class="twitter photo"/>
+ <strong>{rt.user.screen_name}&#x202C;</strong>
+ <img src={status.user.profile_image_url} alt={status.user.screen_name} class="twitter photo"/>
+ : <span class="twitter entry-content rt">{detectLink(rt.text)}</span>
+ </>
+ } else {
+ xml = <>
+ <img src={status.user.profile_image_url} alt={status.user.screen_name} class="twitter photo"/>
+ <strong title={status.user.name}>{status.user.screen_name}&#x202C;</strong>
+ : <span class="twitter entry-content">{detectLink(status.text)}</span>
+ </>;
+ }
+ return xml.toSource().replace(/(?:\r\n|[\r\n])[ \t]*/g, " ");
+ }).join("<br/>");
+
+ //liberator.log(html);
+ liberator.echo(html, true);
+}
+function detectLink (str) {
+ let m = str.match(/https?:\/\/\S+/);
+ if (m) {
+ let left = str.substr(0, m.index);
+ let url = m[0];
+ let right = str.substring(m.index + m[0].length);
+ return <>{detectLink(left)}<a highlight="URL" href={url}> {url} </a>{detectLink(right)}</>;
+ }
+ return str;
+}
+function showTwitterSearchResult(word){ // {{{
+ tw.get("http://search.twitter.com/search.json", { q: word }, function(text){
+ let results = JSON.parse(text);
+ showTL(results);
+ });
+} // }}}
+function getFollowersStatus(target, force, onload){
+ function setRefresher(){
+ expiredStatus = false;
+ if (statusRefreshTimer)
+ clearTimeout(statusRefreshTimer);
+ statusRefreshTimer = setTimeout(function () expiredStatus = true, statusValidDuration * 1000);
+ }
+ if (!force && !expiredStatus && statuses.length > 0){
+ onload();
+ } else {
+ let api = "http://api.twitter.com/1/statuses/home_timeline.json",
+ query = {};
+ if (target) {
+ api = "http://api.twitter.com/1/statuses/user_timeline.json";
+ query.screen_name = target;
+ } else {
+ query = null;
+ }
+ tw.get(api, query, function(text){
+ setRefresher();
+ statuses = JSON.parse(text);
+ onload();
+ });
+ }
+}
+function showFollowersStatus(arg, force){ // {{{
+ getFollowersStatus(arg, force, function(text){
+ showTL(statuses);
+ });
+} // }}}
+
+function showTwitterMentions(arg){ // {{{
+ if (/^@/.test(arg))
+ arg = arg.substr(1);
+ tw.get("http://api.twitter.com/1/statuses/mentions.json", null, function(text) {
+ statuses = JSON.parse(text);
+ showTL(statuses);
+ });
+} // }}}
+function favTwitter(id){ // {{{
+ tw.post("http://api.twitter.com/1/favorites/create/" + id + ".json", null, function(text){
+ let res = JSON.parse(text);
+ liberator.echo("[Twittperator] fav: " + res.user.name + " " + res.text, true);
+ });
+} // }}}
+function unfavTwitter(id){ // {{{
+ tw.post("http://api.twitter.com/1/favorites/destroy/" + id + ".json", null, function(text){
+ let res = JSON.parse(text);
+ liberator.echo("[Twittperator] unfav: " + res.user.name + " " + res.text, true);
+ });
+} // }}}
+function sayTwitter(stat){ // {{{
+ let sendData = {};
+ if (stat.match(/^(.*)@([^\s#]+)(?:#(\d+))(.*)$/)){
+ let [prefix, replyUser, replyID, postfix] = [RegExp.$1, RegExp.$2, RegExp.$3, RegExp.$4];
+ if (stat.indexOf("RT @" + replyUser + "#" + replyID) == 0){
+ ReTweet(replyID);
+ return;
+ }
+ stat = prefix + "@" + replyUser + postfix;
+ if (replyID && !prefix)
+ sendData.in_reply_to_status_id = replyID;
+ }
+ sendData.status = stat;
+ sendData.source = "Twittperator";
+ tw.post("http://api.twitter.com/1/statuses/update.json", sendData, function(text){
+ let result = JSON.parse(text || "{}");
+ let t = result.text;
+ liberator.echo("[Twittperator] Your post " + '"' + t + '" (' + t.length + " characters) was sent.", true);
+ });
+} // }}}
+function ReTweet(id){ // {{{
+ let url = "http://api.twitter.com/1/statuses/retweet/" + id + ".json";
+ tw.post(url, null, function(text) {
+ let res = JSON.parse(text);
+ liberator.log(res.toSource(), 0);
+ liberator.echo("[Twittperator] ReTweet: " + res.retweeted_status.text, true);
+ });
+} // }}}
+function setup() {
+ commands.addUserCommand(["tw[ittperator]"], "Twittperator command",
+ function (arg) {
+ var special = arg.bang;
+ arg = arg.string.replace(/%URL%/g, liberator.modules.buffer.URL)
+ .replace(/%TITLE%/g, liberator.modules.buffer.title);
+
+ if (special && arg.match(/^\?\s*(.*)/))
+ showTwitterSearchResult(RegExp.$1);
+ else
+ if (special && arg.match(/^\+\s*.*#(\d+)/))
+ favTwitter(RegExp.$1);
+ else
+ if (special && arg.match(/^-\s*.*#(\d+)/))
+ unfavTwitter(RegExp.$1);
+ else
+ if (special && arg.match(/^@/))
+ showTwitterMentions();
+ else
+ if (special || arg.length == 0)
+ showFollowersStatus(arg, special);
+ else
+ sayTwitter(arg);
+ }, {
+ bang: true,
+ literal: 0,
+ completer: let (getting, targetContext) function(context, args){
+ function compl(){
+ if (args.bang && !/^[-+]/.test(args[0])){
+ targetContext.title = ["Name","Entry"];
+ list = statuses.map(function(s) ("retweeted_status" in s) ?
+ ["@" + s.retweeted_status.user.screen_name, s.retweeted_status.text ] :
+ ["@" + s.user.screen_name, s.text]);
+ } else if (/RT\s+@\w*$/.test(args[0])){
+ targetContext.title = ["Name + Text"];
+ list = statuses.map(function(s) ("retweeted_status" in s) ?
+ ["@"+s.retweeted_status.user.screen_name+"#"+s.retweeted_status.id+": "+s.retweeted_status.text, "-" ] :
+ ["@"+s.user.screen_name+"#"+s.id+": "+s.text, "-"]);
+ } else {
+ targetContext.title = ["Name#ID","Entry"];
+ list = statuses.map(function(s) ("retweeted_status" in s) ?
+ ["@"+s.retweeted_status.user.screen_name+"#"+s.retweeted_status.id+" ", s.retweeted_status.text] :
+ ["@"+s.user.screen_name+"#"+s.id+" ", s.text]);
+ }
+
+ if (target){
+ list = list.filter(function($_) $_[0].indexOf(target) >= 0);
+ }
+ targetContext.completions = list;
+ targetContext.incomplete = false;
+ targetContext = getting = null;
+ }
+
+ var matches= context.filter.match(/@(\w*)$/);
+ if (!matches) return;
+ var list = [];
+ var target = matches[1];
+ var doGet = (expiredStatus || !(statuses && statuses.length)) && autoStatusUpdate;
+ context.offset += matches.index;
+ context.incomplete = doGet;
+ context.hasitems = !doGet;
+ targetContext = context;
+ if (doGet) {
+ if (!getting) {
+ getting = true;
+ getFollowersStatus(null, compl);
+ }
+ } else {
+ compl();
+ }
+ }
+ }, true);
+}
+// PIN code を取得して AccessToken を得る前
+function preSetup() {
+ commands.addUserCommand(["tw[ittperator]"], "Twittperator setup command",
+ function (args) {
+ if (args["-getPIN"]){
+ tw.getRequestToken(function(url){
+ liberator.open(url, { where: liberator.NEW_TAB });
+ });
+ liberator.echo("Twittperator","Please get PIN code and execute\n :tw -setPIN {PINcode}");
+ } else if (args["-setPIN"]) {
+ tw.setPin(args["-setPIN"]);
+ }
+ }, {
+ options: [
+ [["-getPIN"], commands.OPTION_NOARG],
+ [["-setPIN"], commands.OPTION_STRING, null, null]
+ ],
+ }, true);
+}
+
+// vim: sw=2 ts=2 et fdm=marker: