diff --git a/codebird.es7.js b/codebird.es7.js index c4fda85..a99b12b 100644 --- a/codebird.es7.js +++ b/codebird.es7.js @@ -971,7 +971,7 @@ * @return string API method to call */ _mapFnInsertSlashes(fn) { - return fn.split("_").join("/"); + return fn.split("_").join("/").split(" ").join("_"); } /** @@ -1553,7 +1553,7 @@ params = this._http_build_query(params); } post_fields = params; - if (this._use_proxy || multipart) { + if (this._use_proxy) { // force proxy for multipart base64 url = url .replace(this._endpoint_base, this._endpoint_proxy) diff --git a/codebird.js b/codebird.js index 459a752..2f304e1 100644 --- a/codebird.js +++ b/codebird.js @@ -1,12 +1,69 @@ "use strict"; -var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); +var _slicedToArray = function () { + function sliceIterator(arr, i) { + var _arr = []; + var _n = true; + var _d = false; + var _e = undefined; + try { + for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { + _arr.push(_s.value); + if (i && _arr.length === i) break; + } + } catch (err) { + _d = true; + _e = err; + } finally { + try { + if (!_n && _i["return"]) _i["return"](); + } finally { + if (_d) throw _e; + } + } + return _arr; + } -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + return function (arr, i) { + if (Array.isArray(arr)) { + return arr; + } else if (Symbol.iterator in Object(arr)) { + return sliceIterator(arr, i); + } else { + throw new TypeError("Invalid attempt to destructure non-iterable instance"); + } + }; +}(); + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { + return typeof obj; +} : function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; +}; + +var _createClass = function () { + function defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + return function (Constructor, protoProps, staticProps) { + if (protoProps) defineProperties(Constructor.prototype, protoProps); + if (staticProps) defineProperties(Constructor, staticProps); + return Constructor; + }; +}(); -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } +function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } +} /** * A Twitter library in JavaScript @@ -29,1592 +86,1607 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons require */ (function () { - /** - * A Twitter library in JavaScript - * - * @package codebird - * @subpackage codebird-js - */ - var Codebird = function () { - function Codebird() { - _classCallCheck(this, Codebird); - - /** - * The OAuth consumer key of your registered app - */ - this._oauth_consumer_key = null; - - /** - * The corresponding consumer secret - */ - this._oauth_consumer_secret = null; - - /** - * The app-only bearer token. Used to authorize app-only requests - */ - this._oauth_bearer_token = null; - - /** - * The API endpoint base to use - */ - this._endpoint_base = "https://api.twitter.com/"; - - /** - * The media API endpoint base to use - */ - this._endpoint_base_media = "https://upload.twitter.com/"; - - /** - * The API endpoint to use - */ - this._endpoint = this._endpoint_base + "1.1/"; - - /** - * The media API endpoint to use - */ - this._endpoint_media = this._endpoint_base_media + "1.1/"; - - /** - * The publish API endpoint to use - */ - this._endpoint_publish = "https://publish.twitter.com/"; - - /** - * The API endpoint base to use - */ - this._endpoint_oauth = this._endpoint_base; - - /** - * API proxy endpoint - */ - this._endpoint_proxy = "https://api.jublo.net/codebird/"; - - /** - * Whether to access the API via a proxy that is allowed by CORS - * Assume that CORS is only necessary in browsers - */ - this._use_proxy = typeof navigator !== "undefined" && typeof navigator.userAgent !== "undefined"; - - /** - * The Request or access token. Used to sign requests - */ - this._oauth_token = null; - - /** - * The corresponding request or access token secret - */ - this._oauth_token_secret = null; - - /** - * The current Codebird version - */ - this._version = "3.0.0-dev"; - - this.b64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; - } - /** - * Sets the OAuth consumer key and secret (App key) + * A Twitter library in JavaScript * - * @param string key OAuth consumer key - * @param string secret OAuth consumer secret - * - * @return void + * @package codebird + * @subpackage codebird-js */ + var Codebird = function () { + function Codebird() { + _classCallCheck(this, Codebird); + + /** + * The OAuth consumer key of your registered app + */ + this._oauth_consumer_key = null; + + /** + * The corresponding consumer secret + */ + this._oauth_consumer_secret = null; + + /** + * The app-only bearer token. Used to authorize app-only requests + */ + this._oauth_bearer_token = null; + + /** + * The API endpoint base to use + */ + this._endpoint_base = "https://api.twitter.com/"; + + /** + * The media API endpoint base to use + */ + this._endpoint_base_media = "https://upload.twitter.com/"; + + /** + * The API endpoint to use + */ + this._endpoint = this._endpoint_base + "1.1/"; + + /** + * The media API endpoint to use + */ + this._endpoint_media = this._endpoint_base_media + "1.1/"; + + /** + * The publish API endpoint to use + */ + this._endpoint_publish = "https://publish.twitter.com/"; + + /** + * The API endpoint base to use + */ + this._endpoint_oauth = this._endpoint_base; + + /** + * API proxy endpoint + */ + this._endpoint_proxy = "https://api.jublo.net/codebird/"; + + /** + * Whether to access the API via a proxy that is allowed by CORS + * Assume that CORS is only necessary in browsers + */ + this._use_proxy = typeof navigator !== "undefined" && typeof navigator.userAgent !== "undefined"; + + /** + * The Request or access token. Used to sign requests + */ + this._oauth_token = null; + + /** + * The corresponding request or access token secret + */ + this._oauth_token_secret = null; + + /** + * The current Codebird version + */ + this._version = "3.0.0-dev"; + + this.b64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + } + + /** + * Sets the OAuth consumer key and secret (App key) + * + * @param string key OAuth consumer key + * @param string secret OAuth consumer secret + * + * @return void + */ + + + _createClass(Codebird, [{ + key: "setConsumerKey", + value: function setConsumerKey(key, secret) { + this._oauth_consumer_key = key; + this._oauth_consumer_secret = secret; + } + /** + * Sets the OAuth2 app-only auth bearer token + * + * @param string token OAuth2 bearer token + * + * @return void + */ + + }, { + key: "setBearerToken", + value: function setBearerToken(token) { + this._oauth_bearer_token = token; + } - _createClass(Codebird, [{ - key: "setConsumerKey", - value: function setConsumerKey(key, secret) { - this._oauth_consumer_key = key; - this._oauth_consumer_secret = secret; - } - - /** - * Sets the OAuth2 app-only auth bearer token - * - * @param string token OAuth2 bearer token - * - * @return void - */ - - }, { - key: "setBearerToken", - value: function setBearerToken(token) { - this._oauth_bearer_token = token; - } - - /** - * Gets the current Codebird version - * - * @return string The version number - */ - - }, { - key: "getVersion", - value: function getVersion() { - return this._version; - } - - /** - * Sets the OAuth request or access token and secret (User key) - * - * @param string token OAuth request or access token - * @param string secret OAuth request or access token secret - * - * @return void - */ - - }, { - key: "setToken", - value: function setToken(token, secret) { - this._oauth_token = token; - this._oauth_token_secret = secret; - } - - /** - * Forgets the OAuth request or access token and secret (User key) - * - * @return bool - */ - - }, { - key: "logout", - value: function logout(callback) { - var _this = this; - - var dfd = this._getDfd(); - - if (!dfd && typeof callback === "undefined") { - callback = function callback() {}; - } + /** + * Gets the current Codebird version + * + * @return string The version number + */ - this.__call("oauth_invalidateToken", { - access_key: this._oauth_token, - access_key_secret: this._oauth_token_secret - }).then(function () { - _this._oauth_token = _this._oauth_token_secret = null; - if (typeof callback === "function") { - callback(true); - } - if (dfd) { - dfd.resolve(true); - } - }); - - if (dfd) { - return this._getPromise(dfd); - } - } - - /** - * Enables or disables CORS proxy - * - * @param bool use_proxy Whether to use CORS proxy or not - * - * @return void - */ - - }, { - key: "setUseProxy", - value: function setUseProxy(use_proxy) { - this._use_proxy = !!use_proxy; - } - - /** - * Sets custom CORS proxy server - * - * @param string proxy Address of proxy server to use - * - * @return void - */ - - }, { - key: "setProxy", - value: function setProxy(proxy) { - // add trailing slash if missing - if (!proxy.match(/\/$/)) { - proxy += "/"; - } - this._endpoint_proxy = proxy; - } - - /** - * Signing helpers - */ - - /** - * URL-encodes the given data - * - * @param mixed data - * - * @return mixed The encoded data - */ - - }, { - key: "_url", - value: function _url(data) { - if (/boolean|number|string/.test(typeof data === "undefined" ? "undefined" : _typeof(data))) { - return encodeURIComponent(data).replace(/!/g, "%21").replace(/'/g, "%27").replace(/\(/g, "%28").replace(/\)/g, "%29").replace(/\*/g, "%2A"); - } else { - return ""; - } - } - - /** - * Gets the base64-encoded SHA1 hash for the given data - * - * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined - * in FIPS PUB 180-1 - * Based on version 2.1 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. - * - * @param string data The data to calculate the hash from - * - * @return string The hash - */ - - }, { - key: "_sha1", - value: function _sha1(e) { - function n(e, b) { - e[b >> 5] |= 128 << 24 - b % 32; - e[(b + 64 >> 9 << 4) + 15] = b; - for (var c = new Array(80), a = 1732584193, d = -271733879, h = -1732584194, k = 271733878, g = -1009589776, p = 0; p < e.length; p += 16) { - for (var o = a, q = d, r = h, s = k, t = g, f = 0; 80 > f; f++) { - var m = void 0; - - if (f < 16) { - m = e[p + f]; - } else { - m = c[f - 3] ^ c[f - 8] ^ c[f - 14] ^ c[f - 16]; - m = m << 1 | m >>> 31; - } - - c[f] = m; - m = l(l(a << 5 | a >>> 27, 20 > f ? d & h | ~d & k : 40 > f ? d ^ h ^ k : 60 > f ? d & h | d & k | h & k : d ^ h ^ k), l(l(g, c[f]), 20 > f ? 1518500249 : 40 > f ? 1859775393 : 60 > f ? -1894007588 : -899497514)); - g = k; - k = h; - h = d << 30 | d >>> 2; - d = a; - a = m; + }, { + key: "getVersion", + value: function getVersion() { + return this._version; } - a = l(a, o); - d = l(d, q); - h = l(h, r); - k = l(k, s); - g = l(g, t); - } - return [a, d, h, k, g]; - } - function l(e, b) { - var c = (e & 65535) + (b & 65535); - return (e >> 16) + (b >> 16) + (c >> 16) << 16 | c & 65535; - } + /** + * Sets the OAuth request or access token and secret (User key) + * + * @param string token OAuth request or access token + * @param string secret OAuth request or access token secret + * + * @return void + */ + + }, { + key: "setToken", + value: function setToken(token, secret) { + this._oauth_token = token; + this._oauth_token_secret = secret; + } - function q(e) { - for (var b = [], c = (1 << g) - 1, a = 0; a < e.length * g; a += g) { - b[a >> 5] |= (e.charCodeAt(a / g) & c) << 24 - a % 32; - } - return b; - } - var g = 8; + /** + * Forgets the OAuth request or access token and secret (User key) + * + * @return bool + */ - var b = this._oauth_consumer_secret + "&" + (null !== this._oauth_token_secret ? this._oauth_token_secret : ""); - if (this._oauth_consumer_secret === null) { - throw "To generate a hash, the consumer secret must be set."; - } - var c = q(b); - if (c.length > 16) { - c = n(c, b.length * g); - } - var bb = new Array(16); - for (var a = new Array(16), d = 0; d < 16; d++) { - a[d] = c[d] ^ 909522486; - bb[d] = c[d] ^ 1549556828; - } - c = n(a.concat(q(e)), 512 + e.length * g); - bb = n(bb.concat(c), 672); - b = ""; - for (g = 0; g < 4 * bb.length; g += 3) { - for (d = (bb[g >> 2] >> 8 * (3 - g % 4) & 255) << 16 | (bb[g + 1 >> 2] >> 8 * (3 - (g + 1) % 4) & 255) << 8 | bb[g + 2 >> 2] >> 8 * (3 - (g + 2) % 4) & 255, e = 0; 4 > e; e++) { - b = 8 * g + 6 * e > 32 * bb.length ? b + "=" : b + this.b64_alphabet.charAt(d >> 6 * (3 - e) & 63); - } - } - return b; - } - - /* - * Gets the base64 representation for the given data - * - * http://phpjs.org - * + original by: Tyler Akins (http://rumkin.com) - * + improved by: Bayron Guevara - * + improved by: Thunder.m - * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - * + bugfixed by: Pellentesque Malesuada - * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - * + improved by: Rafał Kukawski (http://kukawski.pl) - * - * @param string data The data to calculate the base64 representation from - * - * @return string The base64 representation - */ - - }, { - key: "_base64_encode", - value: function _base64_encode(a) { - var d = void 0, - e = void 0, - f = void 0, - b = void 0, - g = 0, - h = 0, - i = this.b64_alphabet, - c = []; - if (!a) { - return a; - } - do { - d = a.charCodeAt(g++); - e = a.charCodeAt(g++); - f = a.charCodeAt(g++); - b = d << 16 | e << 8 | f; - d = b >> 18 & 63; - e = b >> 12 & 63; - f = b >> 6 & 63; - b &= 63; - c[h++] = i.charAt(d) + i.charAt(e) + i.charAt(f) + i.charAt(b); - } while (g < a.length); - i = c.join(""); - a = a.length % 3; - return (a ? i.slice(0, a - 3) : i) + "===".slice(a || 3); - } - - /* - * Builds a HTTP query string from the given data - * - * http://phpjs.org - * + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - * + improved by: Legaev Andrey - * + improved by: Michael White (http://getsprink.com) - * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - * + improved by: Brett Zamir (http://brett-zamir.me) - * + revised by: stag019 - * + input by: Dreamer - * + bugfixed by: Brett Zamir (http://brett-zamir.me) - * + bugfixed by: MIO_KODUKI (http://mio-koduki.blogspot.com/) - * - * @param string data The data to concatenate - * - * @return string The HTTP query - */ - - }, { - key: "_http_build_query", - value: function _http_build_query(e, f, b) { - function g(c, a, d) { - var b = void 0, - e = []; - if (a === true) { - a = "1"; - } else if (a === false) { - a = "0"; - } - if (null !== a) { - if ((typeof a === "undefined" ? "undefined" : _typeof(a)) === "object") { - for (b in a) { - if (a.hasOwnProperty(b) && a[b] !== null) { - e.push(g.call(this, c + "[" + b + "]", a[b], d)); - } - } - return e.join(d); + }, { + key: "logout", + value: function logout(callback) { + var _this = this; + + var dfd = this._getDfd(); + + if (!dfd && typeof callback === "undefined") { + callback = function callback() { + }; + } + + this.__call("oauth_invalidateToken", { + access_key: this._oauth_token, + access_key_secret: this._oauth_token_secret + }).then(function () { + _this._oauth_token = _this._oauth_token_secret = null; + if (typeof callback === "function") { + callback(true); + } + if (dfd) { + dfd.resolve(true); + } + }); + + if (dfd) { + return this._getPromise(dfd); + } } - if (typeof a !== "function") { - return this._url(c) + "=" + this._url(a); + + /** + * Enables or disables CORS proxy + * + * @param bool use_proxy Whether to use CORS proxy or not + * + * @return void + */ + + }, { + key: "setUseProxy", + value: function setUseProxy(use_proxy) { + this._use_proxy = !!use_proxy; } - throw "There was an error processing for http_build_query()."; - } else { - return ""; - } - } - var d, - c, - h = []; - if (!b) { - b = "&"; - } - for (c in e) { - if (!e.hasOwnProperty(c)) { - continue; - } - d = e[c]; - if (f && !isNaN(c)) { - c = String(f) + c; - } - d = g.call(this, c, d, b); - if (d !== "") { - h.push(d); - } - } - return h.join(b); - } - - /** - * Generates a (hopefully) unique random string - * - * @param int optional length The length of the string to generate - * - * @return string The random string - */ - - }, { - key: "_nonce", - value: function _nonce() { - var length = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 8; - - if (length < 1) { - throw "Invalid nonce length."; - } - var nonce = ""; - for (var i = 0; i < length; i++) { - var character = Math.floor(Math.random() * 61); - nonce += this.b64_alphabet.substring(character, character + 1); - } - return nonce; - } - - /** - * Sort array elements by key - * - * @param array input_arr The array to sort - * - * @return array The sorted keys - */ - - }, { - key: "_ksort", - value: function _ksort(input_arr) { - var keys = [], - sorter = void 0, - k = void 0; - - sorter = function sorter(a, b) { - var a_float = parseFloat(a), - b_float = parseFloat(b), - a_numeric = a_float + "" === a, - b_numeric = b_float + "" === b; - if (a_numeric && b_numeric) { - return a_float > b_float ? 1 : a_float < b_float ? -1 : 0; - } else if (a_numeric && !b_numeric) { - return 1; - } else if (!a_numeric && b_numeric) { - return -1; - } - return a > b ? 1 : a < b ? -1 : 0; - }; - - // Make a list of key names - for (k in input_arr) { - if (input_arr.hasOwnProperty(k)) { - keys.push(k); - } - } - keys.sort(sorter); - return keys; - } - - /** - * Clone objects - * - * @param object obj The object to clone - * - * @return object clone The cloned object - */ - - }, { - key: "_clone", - value: function _clone(obj) { - var clone = {}; - for (var i in obj) { - if (_typeof(obj[i]) === "object") { - clone[i] = this._clone(obj[i]); - } else { - clone[i] = obj[i]; - } - } - return clone; - } - - /** - * Gets the XML HTTP Request object, trying to load it in various ways - * - * @return object The XMLHttpRequest object instance - */ - - }, { - key: "_getXmlRequestObject", - value: function _getXmlRequestObject() { - var xml = null; - // first, try the W3-standard object - if ((typeof window === "undefined" ? "undefined" : _typeof(window)) === "object" && window && typeof window.XMLHttpRequest !== "undefined") { - xml = new window.XMLHttpRequest(); - // then, try Titanium framework object - } else if ((typeof Ti === "undefined" ? "undefined" : _typeof(Ti)) === "object" && Ti && typeof Ti.Network.createHTTPClient !== "undefined") { - xml = Ti.Network.createHTTPClient(); - // are we in an old Internet Explorer? - } else if (typeof ActiveXObject !== "undefined") { - try { - xml = new ActiveXObject("Microsoft.XMLHTTP"); - } catch (e) { - throw "ActiveXObject object not defined."; - } - // now, consider RequireJS and/or Node.js objects - } else if (typeof require === "function") { - var XMLHttpRequest; - // look for xmlhttprequest module - try { - XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; - xml = new XMLHttpRequest(); - } catch (e1) { - // or maybe the user is using xhr2 - try { - XMLHttpRequest = require("xhr2"); - xml = new XMLHttpRequest(); - } catch (e2) { - throw "xhr2 object not defined, cancelling."; + + /** + * Sets custom CORS proxy server + * + * @param string proxy Address of proxy server to use + * + * @return void + */ + + }, { + key: "setProxy", + value: function setProxy(proxy) { + // add trailing slash if missing + if (!proxy.match(/\/$/)) { + proxy += "/"; + } + this._endpoint_proxy = proxy; } - } - } - return xml; - } - - /** - * Parse URL-style parameters into object - * - * version: 1109.2015 - * discuss at: http://phpjs.org/functions/parse_str - * + original by: Cagri Ekin - * + improved by: Michael White (http://getsprink.com) - * + tweaked by: Jack - * + bugfixed by: Onno Marsman - * + reimplemented by: stag019 - * + bugfixed by: Brett Zamir (http://brett-zamir.me) - * + bugfixed by: stag019 - * - depends on: urldecode - * + input by: Dreamer - * + bugfixed by: Brett Zamir (http://brett-zamir.me) - * % note 1: When no argument is specified, will put variables in global scope. - * - * @param string str String to parse - * @param array array to load data into - * - * @return object - */ - - }, { - key: "_parse_str", - value: function _parse_str(str, array) { - var glue1 = "=", - glue2 = "&", - array2 = String(str).replace(/^&?([\s\S]*?)&?$/, "$1").split(glue2), - i, - j, - chr, - tmp, - key, - value, - bracket, - keys, - evalStr, - fixStr = function fixStr(str) { - return decodeURIComponent(str).replace(/([\\"'])/g, "\\$1").replace(/\n/g, "\\n").replace(/\r/g, "\\r"); - }; - if (!array) { - array = this.window; - } - for (i = 0; i < array2.length; i++) { - tmp = array2[i].split(glue1); - if (tmp.length < 2) { - tmp = [tmp, ""]; - } - key = fixStr(tmp[0]); - value = fixStr(tmp[1]); - while (key.charAt(0) === " ") { - key = key.substr(1); - } - if (key.indexOf("\0") > -1) { - key = key.substr(0, key.indexOf("\0")); - } - if (key && key.charAt(0) !== "[") { - keys = []; - bracket = 0; - for (j = 0; j < key.length; j++) { - if (key.charAt(j) === "[" && !bracket) { - bracket = j + 1; - } else if (key.charAt(j) === "]") { - if (bracket) { - if (!keys.length) { - keys.push(key.substr(0, bracket - 1)); - } - keys.push(key.substr(bracket, j - bracket)); - bracket = 0; - if (key.charAt(j + 1) !== "[") { - break; - } - } - } + /** + * Signing helpers + */ + + /** + * URL-encodes the given data + * + * @param mixed data + * + * @return mixed The encoded data + */ + + }, { + key: "_url", + value: function _url(data) { + if (/boolean|number|string/.test(typeof data === "undefined" ? "undefined" : _typeof(data))) { + return encodeURIComponent(data).replace(/!/g, "%21").replace(/'/g, "%27").replace(/\(/g, "%28").replace(/\)/g, "%29").replace(/\*/g, "%2A"); + } else { + return ""; + } } - if (!keys.length) { - keys = [key]; + + /** + * Gets the base64-encoded SHA1 hash for the given data + * + * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined + * in FIPS PUB 180-1 + * Based on version 2.1 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. + * + * @param string data The data to calculate the hash from + * + * @return string The hash + */ + + }, { + key: "_sha1", + value: function _sha1(e) { + function n(e, b) { + e[b >> 5] |= 128 << 24 - b % 32; + e[(b + 64 >> 9 << 4) + 15] = b; + for (var c = new Array(80), a = 1732584193, d = -271733879, h = -1732584194, k = 271733878, g = -1009589776, p = 0; p < e.length; p += 16) { + for (var o = a, q = d, r = h, s = k, t = g, f = 0; 80 > f; f++) { + var m = void 0; + + if (f < 16) { + m = e[p + f]; + } else { + m = c[f - 3] ^ c[f - 8] ^ c[f - 14] ^ c[f - 16]; + m = m << 1 | m >>> 31; + } + + c[f] = m; + m = l(l(a << 5 | a >>> 27, 20 > f ? d & h | ~d & k : 40 > f ? d ^ h ^ k : 60 > f ? d & h | d & k | h & k : d ^ h ^ k), l(l(g, c[f]), 20 > f ? 1518500249 : 40 > f ? 1859775393 : 60 > f ? -1894007588 : -899497514)); + g = k; + k = h; + h = d << 30 | d >>> 2; + d = a; + a = m; + } + a = l(a, o); + d = l(d, q); + h = l(h, r); + k = l(k, s); + g = l(g, t); + } + return [a, d, h, k, g]; + } + + function l(e, b) { + var c = (e & 65535) + (b & 65535); + return (e >> 16) + (b >> 16) + (c >> 16) << 16 | c & 65535; + } + + function q(e) { + for (var b = [], c = (1 << g) - 1, a = 0; a < e.length * g; a += g) { + b[a >> 5] |= (e.charCodeAt(a / g) & c) << 24 - a % 32; + } + return b; + } + + var g = 8; + + var b = this._oauth_consumer_secret + "&" + (null !== this._oauth_token_secret ? this._oauth_token_secret : ""); + if (this._oauth_consumer_secret === null) { + throw "To generate a hash, the consumer secret must be set."; + } + var c = q(b); + if (c.length > 16) { + c = n(c, b.length * g); + } + var bb = new Array(16); + for (var a = new Array(16), d = 0; d < 16; d++) { + a[d] = c[d] ^ 909522486; + bb[d] = c[d] ^ 1549556828; + } + c = n(a.concat(q(e)), 512 + e.length * g); + bb = n(bb.concat(c), 672); + b = ""; + for (g = 0; g < 4 * bb.length; g += 3) { + for (d = (bb[g >> 2] >> 8 * (3 - g % 4) & 255) << 16 | (bb[g + 1 >> 2] >> 8 * (3 - (g + 1) % 4) & 255) << 8 | bb[g + 2 >> 2] >> 8 * (3 - (g + 2) % 4) & 255, e = 0; 4 > e; e++) { + b = 8 * g + 6 * e > 32 * bb.length ? b + "=" : b + this.b64_alphabet.charAt(d >> 6 * (3 - e) & 63); + } + } + return b; } - for (j = 0; j < keys[0].length; j++) { - chr = keys[0].charAt(j); - if (chr === " " || chr === "." || chr === "[") { - keys[0] = keys[0].substr(0, j) + "_" + keys[0].substr(j + 1); - } - if (chr === "[") { - break; - } + + /* + * Gets the base64 representation for the given data + * + * http://phpjs.org + * + original by: Tyler Akins (http://rumkin.com) + * + improved by: Bayron Guevara + * + improved by: Thunder.m + * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * + bugfixed by: Pellentesque Malesuada + * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * + improved by: Rafał Kukawski (http://kukawski.pl) + * + * @param string data The data to calculate the base64 representation from + * + * @return string The base64 representation + */ + + }, { + key: "_base64_encode", + value: function _base64_encode(a) { + var d = void 0, + e = void 0, + f = void 0, + b = void 0, + g = 0, + h = 0, + i = this.b64_alphabet, + c = []; + if (!a) { + return a; + } + do { + d = a.charCodeAt(g++); + e = a.charCodeAt(g++); + f = a.charCodeAt(g++); + b = d << 16 | e << 8 | f; + d = b >> 18 & 63; + e = b >> 12 & 63; + f = b >> 6 & 63; + b &= 63; + c[h++] = i.charAt(d) + i.charAt(e) + i.charAt(f) + i.charAt(b); + } while (g < a.length); + i = c.join(""); + a = a.length % 3; + return (a ? i.slice(0, a - 3) : i) + "===".slice(a || 3); } - evalStr = "array"; - for (j = 0; j < keys.length; j++) { - key = keys[j]; - if (key !== "" && key !== " " || j === 0) { - key = "'" + key + "'"; - } else { - key = eval(evalStr + ".push([]);") - 1; - } - evalStr += "[" + key + "]"; - if (j !== keys.length - 1 && eval("typeof " + evalStr) === "undefined") { - eval(evalStr + " = [];"); - } + + /* + * Builds a HTTP query string from the given data + * + * http://phpjs.org + * + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * + improved by: Legaev Andrey + * + improved by: Michael White (http://getsprink.com) + * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * + improved by: Brett Zamir (http://brett-zamir.me) + * + revised by: stag019 + * + input by: Dreamer + * + bugfixed by: Brett Zamir (http://brett-zamir.me) + * + bugfixed by: MIO_KODUKI (http://mio-koduki.blogspot.com/) + * + * @param string data The data to concatenate + * + * @return string The HTTP query + */ + + }, { + key: "_http_build_query", + value: function _http_build_query(e, f, b) { + function g(c, a, d) { + var b = void 0, + e = []; + if (a === true) { + a = "1"; + } else if (a === false) { + a = "0"; + } + if (null !== a) { + if ((typeof a === "undefined" ? "undefined" : _typeof(a)) === "object") { + for (b in a) { + if (a.hasOwnProperty(b) && a[b] !== null) { + e.push(g.call(this, c + "[" + b + "]", a[b], d)); + } + } + return e.join(d); + } + if (typeof a !== "function") { + return this._url(c) + "=" + this._url(a); + } + throw "There was an error processing for http_build_query()."; + } else { + return ""; + } + } + + var d, + c, + h = []; + if (!b) { + b = "&"; + } + for (c in e) { + if (!e.hasOwnProperty(c)) { + continue; + } + d = e[c]; + if (f && !isNaN(c)) { + c = String(f) + c; + } + d = g.call(this, c, d, b); + if (d !== "") { + h.push(d); + } + } + return h.join(b); } - evalStr += " = '" + value + "';\n"; - eval(evalStr); - } - } - } - - /** - * Get allowed API methods, sorted by GET or POST - * Watch out for multiple-method "account/settings"! - * - * @return array $apimethods - */ - - }, { - key: "getApiMethods", - value: function getApiMethods() { - var httpmethods = { - GET: ["account/settings", "account/verify_credentials", "account_activity/all/:env_name/subscriptions", "account_activity/all/:env_name/subscriptions/list", "account_activity/all/:env_name/webhooks", "account_activity/all/webhooks", "account_activity/subscriptions/count", "account_activity/webhooks", "account_activity/webhooks/:webhook_id/subscriptions/all", "account_activity/webhooks/:webhook_id/subscriptions/all/list", "application/rate_limit_status", "blocks/ids", "blocks/list", "collections/entries", "collections/list", "collections/show", "custom_profiles/:id", "custom_profiles/list", "direct_messages/events/list", "direct_messages/events/show", "direct_messages/welcome_messages/list", "direct_messages/welcome_messages/rules/list", "direct_messages/welcome_messages/rules/show", "direct_messages/welcome_messages/show", "favorites/list", "feedback/events", "feedback/show/:id", "followers/ids", "followers/list", "friends/ids", "friends/list", "friendships/incoming", "friendships/lookup", "friendships/lookup", "friendships/no_retweets/ids", "friendships/outgoing", "friendships/show", "geo/id/:place_id", "geo/reverse_geocode", "geo/search", "help/configuration", "help/languages", "help/privacy", "help/tos", "lists/list", "lists/members", "lists/members/show", "lists/memberships", "lists/ownerships", "lists/show", "lists/statuses", "lists/subscribers", "lists/subscribers/show", "lists/subscriptions", "mutes/users/ids", "mutes/users/list", "oauth/authenticate", "oauth/authorize", "saved_searches/list", "saved_searches/show/:id", "search/tweets", "statuses/home_timeline", "statuses/mentions_timeline", "statuses/oembed", "statuses/retweeters/ids", "statuses/retweets/:id", "statuses/retweets_of_me", "statuses/sample", "statuses/show/:id", "statuses/user_timeline", "trends/available", "trends/closest", "trends/place", "users/profile_banner", "users/search", "users/show", "users/suggestions", "users/suggestions/:slug", "users/suggestions/:slug/members"], - POST: ["account/remove_profile_banner", "account/settings__post", "account/update_profile", "account/update_profile_banner", "account/update_profile_image", "account_activity/all/:env_name/subscriptions", "account_activity/all/:env_name/webhooks", "account_activity/webhooks", "account_activity/webhooks/:webhook_id/subscriptions/all", "blocks/create", "blocks/destroy", "collections/create", "collections/destroy", "collections/entries/add", "collections/entries/curate", "collections/entries/move", "collections/entries/remove", "collections/update", "custom_profiles/new", "direct_messages/events/new", "direct_messages/indicate_typing", "direct_messages/mark_read", "direct_messages/welcome_messages/new", "direct_messages/welcome_messages/rules/new", "favorites/create", "favorites/destroy", "feedback/create", "friendships/create", "friendships/destroy", "friendships/update", "lists/create", "lists/destroy", "lists/members/create", "lists/members/create_all", "lists/members/destroy", "lists/members/destroy_all", "lists/subscribers/create", "lists/subscribers/destroy", "lists/update", "media/metadata/create", "media/upload", "mutes/users/create", "mutes/users/destroy", "oauth/access_token", "oauth/invalidate_token", "oauth/request_token", "oauth2/invalidate_token", "oauth2/token", "saved_searches/create", "saved_searches/destroy/:id", "statuses/destroy/:id", "statuses/filter", "statuses/lookup", "statuses/retweet/:id", "statuses/unretweet/:id", "statuses/update", "users/lookup", "users/report_spam"] - }; - return httpmethods; - } - - /** - * Promise helpers - */ - - /** - * Get a deferred object - */ - - }, { - key: "_getDfd", - value: function _getDfd() { - if (typeof window !== "undefined") { - if (typeof window.jQuery !== "undefined" && window.jQuery.Deferred) { - return window.jQuery.Deferred(); - } - if (typeof window.Q !== "undefined" && window.Q.defer) { - return window.Q.defer(); - } - if (typeof window.RSVP !== "undefined" && window.RSVP.defer) { - return window.RSVP.defer(); - } - if (typeof window.when !== "undefined" && window.when.defer) { - return window.when.defer(); - } - } - if (typeof require !== "undefined") { - var promise_class = false; - try { - promise_class = require("jquery"); - } catch (e) {} - if (promise_class) { - return promise_class.Deferred(); - } - try { - promise_class = require("q"); - } catch (e) {} - if (!promise_class) { - try { - promise_class = require("rsvp"); - } catch (e) {} - } - if (!promise_class) { - try { - promise_class = require("when"); - } catch (e) {} - } - if (promise_class) { - try { - return promise_class.defer(); - } catch (e) {} - } - } - return false; - } - - /** - * Get a promise from the dfd object - */ - - }, { - key: "_getPromise", - value: function _getPromise(dfd) { - if (typeof dfd.promise === "function") { - return dfd.promise(); - } - return dfd.promise; // object - } - - /** - * __call() helpers - */ - - /** - * Parse given params, detect query-style params - * - * @param array|string params Parameters to parse - * - * @return array apiparams - */ - - }, { - key: "_parseApiParams", - value: function _parseApiParams(params) { - var apiparams = {}; - if ((typeof params === "undefined" ? "undefined" : _typeof(params)) === "object") { - apiparams = params; - } else { - this._parse_str(params, apiparams); //TODO - } - return apiparams; - } - - /** - * Replace null and boolean parameters with their string representations - * - * @param array apiparams Parameter array to replace in - * - * @return array apiparams - */ - - }, { - key: "_stringifyNullBoolParams", - value: function _stringifyNullBoolParams(apiparams) { - for (var key in apiparams) { - if (!apiparams.hasOwnProperty(key)) { - continue; - } - var value = apiparams[key]; - if (value === null) { - apiparams[key] = "null"; - } else if (value === true || value === false) { - apiparams[key] = value ? "true" : "false"; - } - } + /** + * Generates a (hopefully) unique random string + * + * @param int optional length The length of the string to generate + * + * @return string The random string + */ + + }, { + key: "_nonce", + value: function _nonce() { + var length = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 8; + + if (length < 1) { + throw "Invalid nonce length."; + } + var nonce = ""; + for (var i = 0; i < length; i++) { + var character = Math.floor(Math.random() * 61); + nonce += this.b64_alphabet.substring(character, character + 1); + } + return nonce; + } - return apiparams; - } - - /** - * API method mapping: Replaces _ with / character - * - * @param string fn Function called - * - * @return string API method to call - */ - - }, { - key: "_mapFnInsertSlashes", - value: function _mapFnInsertSlashes(fn) { - return fn.split("_").join("/"); - } - - /** - * API method mapping: Restore _ character in named parameters - * - * @param string method API method to call - * - * @return string API method with restored underscores - */ - - }, { - key: "_mapFnRestoreParamUnderscores", - value: function _mapFnRestoreParamUnderscores(method) { - var url_parameters_with_underscore = ["screen_name", "place_id"]; - var i = void 0, - param = void 0, - replacement_was = void 0; - for (i = 0; i < url_parameters_with_underscore.length; i++) { - param = url_parameters_with_underscore[i].toUpperCase(); - replacement_was = param.split("_").join("/"); - method = method.split(replacement_was).join(param); - } + /** + * Sort array elements by key + * + * @param array input_arr The array to sort + * + * @return array The sorted keys + */ + + }, { + key: "_ksort", + value: function _ksort(input_arr) { + var keys = [], + sorter = void 0, + k = void 0; + + sorter = function sorter(a, b) { + var a_float = parseFloat(a), + b_float = parseFloat(b), + a_numeric = a_float + "" === a, + b_numeric = b_float + "" === b; + if (a_numeric && b_numeric) { + return a_float > b_float ? 1 : a_float < b_float ? -1 : 0; + } else if (a_numeric && !b_numeric) { + return 1; + } else if (!a_numeric && b_numeric) { + return -1; + } + return a > b ? 1 : a < b ? -1 : 0; + }; + + // Make a list of key names + for (k in input_arr) { + if (input_arr.hasOwnProperty(k)) { + keys.push(k); + } + } + keys.sort(sorter); + return keys; + } - return method; - } - - /** - * Maps called PHP magic method name to Twitter API method - * - * @param string $fn Function called - * @param array $apiparams byref API parameters - * - * @return string[] (string method, string method_template) - */ - - }, { - key: "_mapFnToApiMethod", - value: function _mapFnToApiMethod(fn, apiparams) { - var method = "", - param = void 0, - i = void 0, - j = void 0; - - // replace _ by / - method = this._mapFnInsertSlashes(fn); - - // undo replacement for URL parameters - method = this._mapFnRestoreParamUnderscores(method); - - // replace AA by URL parameters - var method_template = method; - var match = method.match(/[A-Z_]{2,}/); - if (match) { - for (i = 0; i < match.length; i++) { - param = match[i]; - var param_l = param.toLowerCase(); - method_template = method_template.split(param).join(":" + param_l); - if (typeof apiparams[param_l] === "undefined") { - for (j = 0; j < 26; j++) { - method_template = method_template.split(String.fromCharCode(65 + j)).join("_" + String.fromCharCode(97 + j)); - } - throw "To call the templated method \"" + method_template + "\", specify the parameter value for \"" + param_l + "\"."; + /** + * Clone objects + * + * @param object obj The object to clone + * + * @return object clone The cloned object + */ + + }, { + key: "_clone", + value: function _clone(obj) { + var clone = {}; + for (var i in obj) { + if (_typeof(obj[i]) === "object") { + clone[i] = this._clone(obj[i]); + } else { + clone[i] = obj[i]; + } + } + return clone; } - method = method.split(param).join(apiparams[param_l]); - delete apiparams[param_l]; - } - } - // replace A-Z by _a-z - for (i = 0; i < 26; i++) { - method = method.split(String.fromCharCode(65 + i)).join("_" + String.fromCharCode(97 + i)); - method_template = method_template.split(String.fromCharCode(65 + i)).join("_" + String.fromCharCode(97 + i)); - } + /** + * Gets the XML HTTP Request object, trying to load it in various ways + * + * @return object The XMLHttpRequest object instance + */ + + }, { + key: "_getXmlRequestObject", + value: function _getXmlRequestObject() { + var xml = null; + // first, try the W3-standard object + if ((typeof window === "undefined" ? "undefined" : _typeof(window)) === "object" && window && typeof window.XMLHttpRequest !== "undefined") { + xml = new window.XMLHttpRequest(); + // then, try Titanium framework object + } else if ((typeof Ti === "undefined" ? "undefined" : _typeof(Ti)) === "object" && Ti && typeof Ti.Network.createHTTPClient !== "undefined") { + xml = Ti.Network.createHTTPClient(); + // are we in an old Internet Explorer? + } else if (typeof ActiveXObject !== "undefined") { + try { + xml = new ActiveXObject("Microsoft.XMLHTTP"); + } catch (e) { + throw "ActiveXObject object not defined."; + } + // now, consider RequireJS and/or Node.js objects + } else if (typeof require === "function") { + var XMLHttpRequest; + // look for xmlhttprequest module + try { + XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; + xml = new XMLHttpRequest(); + } catch (e1) { + // or maybe the user is using xhr2 + try { + XMLHttpRequest = require("xhr2"); + xml = new XMLHttpRequest(); + } catch (e2) { + throw "xhr2 object not defined, cancelling."; + } + } + } + return xml; + } - return [method, method_template]; - } - - /** - * Detects HTTP method to use for API call - * - * @param string method The API method to call - * @param array params The parameters to send along - * - * @return string The HTTP method that should be used - */ - - }, { - key: "_detectMethod", - value: function _detectMethod(method, params) { - if (typeof params.httpmethod !== "undefined") { - var httpmethod = params.httpmethod; - delete params.httpmethod; - return httpmethod; - } + /** + * Parse URL-style parameters into object + * + * version: 1109.2015 + * discuss at: http://phpjs.org/functions/parse_str + * + original by: Cagri Ekin + * + improved by: Michael White (http://getsprink.com) + * + tweaked by: Jack + * + bugfixed by: Onno Marsman + * + reimplemented by: stag019 + * + bugfixed by: Brett Zamir (http://brett-zamir.me) + * + bugfixed by: stag019 + * - depends on: urldecode + * + input by: Dreamer + * + bugfixed by: Brett Zamir (http://brett-zamir.me) + * % note 1: When no argument is specified, will put variables in global scope. + * + * @param string str String to parse + * @param array array to load data into + * + * @return object + */ + + }, { + key: "_parse_str", + value: function _parse_str(str, array) { + var glue1 = "=", + glue2 = "&", + array2 = String(str).replace(/^&?([\s\S]*?)&?$/, "$1").split(glue2), + i, + j, + chr, + tmp, + key, + value, + bracket, + keys, + evalStr, + fixStr = function fixStr(str) { + return decodeURIComponent(str).replace(/([\\"'])/g, "\\$1").replace(/\n/g, "\\n").replace(/\r/g, "\\r"); + }; + if (!array) { + array = this.window; + } - // multi-HTTP method endpoints - switch (method) { - case "account/settings": - case "account/login_verification_enrollment": - case "account/login_verification_request": - method = Object.keys(params).length ? method + "__post" : method; - break; - } + for (i = 0; i < array2.length; i++) { + tmp = array2[i].split(glue1); + if (tmp.length < 2) { + tmp = [tmp, ""]; + } + key = fixStr(tmp[0]); + value = fixStr(tmp[1]); + while (key.charAt(0) === " ") { + key = key.substr(1); + } + if (key.indexOf("\0") > -1) { + key = key.substr(0, key.indexOf("\0")); + } + if (key && key.charAt(0) !== "[") { + keys = []; + bracket = 0; + for (j = 0; j < key.length; j++) { + if (key.charAt(j) === "[" && !bracket) { + bracket = j + 1; + } else if (key.charAt(j) === "]") { + if (bracket) { + if (!keys.length) { + keys.push(key.substr(0, bracket - 1)); + } + keys.push(key.substr(bracket, j - bracket)); + bracket = 0; + if (key.charAt(j + 1) !== "[") { + break; + } + } + } + } + if (!keys.length) { + keys = [key]; + } + for (j = 0; j < keys[0].length; j++) { + chr = keys[0].charAt(j); + if (chr === " " || chr === "." || chr === "[") { + keys[0] = keys[0].substr(0, j) + "_" + keys[0].substr(j + 1); + } + if (chr === "[") { + break; + } + } + evalStr = "array"; + for (j = 0; j < keys.length; j++) { + key = keys[j]; + if (key !== "" && key !== " " || j === 0) { + key = "'" + key + "'"; + } else { + key = eval(evalStr + ".push([]);") - 1; + } + evalStr += "[" + key + "]"; + if (j !== keys.length - 1 && eval("typeof " + evalStr) === "undefined") { + eval(evalStr + " = [];"); + } + } + evalStr += " = '" + value + "';\n"; + eval(evalStr); + } + } + } - var apimethods = this.getApiMethods(); - for (var _httpmethod in apimethods) { - if (apimethods.hasOwnProperty(_httpmethod) && apimethods[_httpmethod].indexOf(method) > -1) { - return _httpmethod; - } - } - throw "Can't find HTTP method to use for \"" + method + "\"."; - } - - /** - * Detects if API call should use multipart/form-data - * - * @param string method The API method to call - * - * @return bool Whether the method should be sent as multipart - */ - - }, { - key: "_detectMultipart", - value: function _detectMultipart(method) { - var multiparts = [ - // Tweets - "media/upload", - - // Users - "account/update_profile_image", "account/update_profile_banner"]; - return multiparts.indexOf(method) > -1; - } - - /** - * Signature helper - * - * @param string httpmethod Usually either 'GET' or 'POST' or 'DELETE' - * @param string method The API method to call - * @param array base_params The signature base parameters - * - * @return string signature - */ - - }, { - key: "_getSignature", - value: function _getSignature(httpmethod, method, keys, base_params) { - // convert params to string - var base_string = "", - key = void 0, - value = void 0; - for (var i = 0; i < keys.length; i++) { - key = keys[i]; - value = base_params[key]; - base_string += key + "=" + this._url(value) + "&"; - } - base_string = base_string.substring(0, base_string.length - 1); - return this._sha1(httpmethod + "&" + this._url(method) + "&" + this._url(base_string)); - } - - /** - * Generates the UNIX timestamp - */ - - }, { - key: "_time", - value: function _time() { - return Math.round(new Date().getTime() / 1000); - } - - /** - * Generates an OAuth signature - * - * @param string httpmethod Usually either 'GET' or 'POST' or 'DELETE' - * @param string method The API method to call - * @param array optional params The API call parameters, associative - * - * @return string Authorization HTTP header - */ - - }, { - key: "_sign", - value: function _sign(httpmethod, method) { - var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - - if (this._oauth_consumer_key === null) { - throw "To generate a signature, the consumer key must be set."; - } - var sign_params = { - consumer_key: this._oauth_consumer_key, - version: "1.0", - timestamp: this._time(), - nonce: this._nonce(), - signature_method: "HMAC-SHA1" - }; - var sign_base_params = {}; - for (var key in sign_params) { - if (!sign_params.hasOwnProperty(key)) { - continue; - } - var value = sign_params[key]; - sign_base_params["oauth_" + key] = this._url(value); - } - if (this._oauth_token !== null) { - sign_base_params.oauth_token = this._url(this._oauth_token); - } - var oauth_params = this._clone(sign_base_params); - for (key in params) { - if (!params.hasOwnProperty(key)) { - continue; - } - sign_base_params[key] = params[key]; - } - var keys = this._ksort(sign_base_params); + /** + * Get allowed API methods, sorted by GET or POST + * Watch out for multiple-method "account/settings"! + * + * @return array $apimethods + */ + + }, { + key: "getApiMethods", + value: function getApiMethods() { + var httpmethods = { + GET: ["account/settings", "account/verify_credentials", "account_activity/all/:env_name/subscriptions", "account_activity/all/:env_name/subscriptions/list", "account_activity/all/:env_name/webhooks", "account_activity/all/webhooks", "account_activity/subscriptions/count", "account_activity/webhooks", "account_activity/webhooks/:webhook_id/subscriptions/all", "account_activity/webhooks/:webhook_id/subscriptions/all/list", "application/rate_limit_status", "blocks/ids", "blocks/list", "collections/entries", "collections/list", "collections/show", "custom_profiles/:id", "custom_profiles/list", "direct_messages/events/list", "direct_messages/events/show", "direct_messages/welcome_messages/list", "direct_messages/welcome_messages/rules/list", "direct_messages/welcome_messages/rules/show", "direct_messages/welcome_messages/show", "favorites/list", "feedback/events", "feedback/show/:id", "followers/ids", "followers/list", "friends/ids", "friends/list", "friendships/incoming", "friendships/lookup", "friendships/lookup", "friendships/no_retweets/ids", "friendships/outgoing", "friendships/show", "geo/id/:place_id", "geo/reverse_geocode", "geo/search", "help/configuration", "help/languages", "help/privacy", "help/tos", "lists/list", "lists/members", "lists/members/show", "lists/memberships", "lists/ownerships", "lists/show", "lists/statuses", "lists/subscribers", "lists/subscribers/show", "lists/subscriptions", "mutes/users/ids", "mutes/users/list", "oauth/authenticate", "oauth/authorize", "saved_searches/list", "saved_searches/show/:id", "search/tweets", "statuses/home_timeline", "statuses/mentions_timeline", "statuses/oembed", "statuses/retweeters/ids", "statuses/retweets/:id", "statuses/retweets_of_me", "statuses/sample", "statuses/show/:id", "statuses/user_timeline", "trends/available", "trends/closest", "trends/place", "users/profile_banner", "users/search", "users/show", "users/suggestions", "users/suggestions/:slug", "users/suggestions/:slug/members"], + POST: ["account/remove_profile_banner", "account/settings__post", "account/update_profile", "account/update_profile_banner", "account/update_profile_image", "account_activity/all/:env_name/subscriptions", "account_activity/all/:env_name/webhooks", "account_activity/webhooks", "account_activity/webhooks/:webhook_id/subscriptions/all", "blocks/create", "blocks/destroy", "collections/create", "collections/destroy", "collections/entries/add", "collections/entries/curate", "collections/entries/move", "collections/entries/remove", "collections/update", "custom_profiles/new", "direct_messages/events/new", "direct_messages/indicate_typing", "direct_messages/mark_read", "direct_messages/welcome_messages/new", "direct_messages/welcome_messages/rules/new", "favorites/create", "favorites/destroy", "feedback/create", "friendships/create", "friendships/destroy", "friendships/update", "lists/create", "lists/destroy", "lists/members/create", "lists/members/create_all", "lists/members/destroy", "lists/members/destroy_all", "lists/subscribers/create", "lists/subscribers/destroy", "lists/update", "media/metadata/create", "media/upload", "mutes/users/create", "mutes/users/destroy", "oauth/access_token", "oauth/invalidate_token", "oauth/request_token", "oauth2/invalidate_token", "oauth2/token", "saved_searches/create", "saved_searches/destroy/:id", "statuses/destroy/:id", "statuses/filter", "statuses/lookup", "statuses/retweet/:id", "statuses/unretweet/:id", "statuses/update", "users/lookup", "users/report_spam"] + }; + return httpmethods; + } - var signature = this._getSignature(httpmethod, method, keys, sign_base_params); + /** + * Promise helpers + */ + + /** + * Get a deferred object + */ + + }, { + key: "_getDfd", + value: function _getDfd() { + if (typeof window !== "undefined") { + if (typeof window.jQuery !== "undefined" && window.jQuery.Deferred) { + return window.jQuery.Deferred(); + } + if (typeof window.Q !== "undefined" && window.Q.defer) { + return window.Q.defer(); + } + if (typeof window.RSVP !== "undefined" && window.RSVP.defer) { + return window.RSVP.defer(); + } + if (typeof window.when !== "undefined" && window.when.defer) { + return window.when.defer(); + } + } + if (typeof require !== "undefined") { + var promise_class = false; + try { + promise_class = require("jquery"); + } catch (e) { + } + if (promise_class) { + return promise_class.Deferred(); + } + try { + promise_class = require("q"); + } catch (e) { + } + if (!promise_class) { + try { + promise_class = require("rsvp"); + } catch (e) { + } + } + if (!promise_class) { + try { + promise_class = require("when"); + } catch (e) { + } + } + if (promise_class) { + try { + return promise_class.defer(); + } catch (e) { + } + } + } + return false; + } - params = oauth_params; - params.oauth_signature = signature; - keys = this._ksort(params); - var authorization = "OAuth "; - for (var i = 0; i < keys.length; i++) { - key = keys[i]; - authorization += key + "=\"" + this._url(params[key]) + "\", "; - } - return authorization.substring(0, authorization.length - 2); - } - - /** - * Build multipart request from upload params - * - * @param string method The API method to call - * @param array params The parameters to send along - * - * @return null|string The built multipart request body - */ - - }, { - key: "_buildMultipart", - value: function _buildMultipart(method, params) { - // well, files will only work in multipart methods - if (!this._detectMultipart(method)) { - return; - } + /** + * Get a promise from the dfd object + */ - // only check specific parameters - var possible_methods = [ - // Tweets - "media/upload", - // Accounts - "account/update_profile_image", "account/update_profile_banner"]; - var possible_files = { - // Tweets - "media/upload": "media", - // Accounts - "account/update_profile_image": "image", - "account/update_profile_banner": "banner" - }; - // method might have files? - if (possible_methods.indexOf(method) === -1) { - return; - } + }, { + key: "_getPromise", + value: function _getPromise(dfd) { + if (typeof dfd.promise === "function") { + return dfd.promise(); + } + return dfd.promise; // object + } - // check for filenames - possible_files = possible_files[method].split(" "); - - var multipart_border = "--------------------" + this._nonce(); - var multipart_request = ""; - for (var key in params) { - if (!params.hasOwnProperty(key)) { - continue; - } - multipart_request += "--" + multipart_border + "\r\nContent-Disposition: form-data; name=\"" + key + "\""; - if (possible_files.indexOf(key) === -1) { - multipart_request += "\r\nContent-Transfer-Encoding: base64"; - } - multipart_request += "\r\n\r\n" + params[key] + "\r\n"; - } - multipart_request += "--" + multipart_border + "--"; - return multipart_request; - } - - /** - * Detects if API call should use media endpoint - * - * @param string method The API method to call - * - * @return bool Whether the method is defined in media API - */ - - }, { - key: "_detectMedia", - value: function _detectMedia(method) { - var medias = ["media/metadata/create", "media/upload"]; - return medias.indexOf(method) > -1; - } - - /** - * Detects if API call should use JSON body - * - * @param string method The API method to call - * - * @return bool Whether the method is defined as accepting JSON body - */ - - }, { - key: "_detectJsonBody", - value: function _detectJsonBody(method) { - var json_bodies = ["collections/entries/curate", "custom_profiles/new", "direct_messages/events/new", "direct_messages/indicate_typing", "direct_messages/mark_read", "direct_messages/welcome_messages/new", "direct_messages/welcome_messages/rules/new", "direct_messages/welcome_messages/update", "media/metadata/create"]; - return json_bodies.indexOf(method) > -1; - } - - /** - * Builds the complete API endpoint url - * - * @param string method The API method to call - * - * @return string The URL to send the request to - */ - - }, { - key: "_getEndpoint", - value: function _getEndpoint(method) { - var url = void 0; - if (method.substring(0, 22) === "oauth/invalidate_token") { - url = this._endpoint + method + ".json"; - } else if (method.substring(0, 5) === "oauth") { - url = this._endpoint_oauth + method; - } else if (this._detectMedia(method)) { - url = this._endpoint_media + method + ".json"; - } else if (method === "statuses/oembed") { - url = this._endpoint_publish + "oembed"; - } else { - url = this._endpoint + method + ".json"; - } - return url; - } - - /** - * Parses the API reply to encode it in the set return_format - * - * @param string reply The actual reply, JSON-encoded or URL-encoded - * - * @return array|object The parsed reply - */ - - }, { - key: "_parseApiReply", - value: function _parseApiReply(reply) { - if (typeof reply !== "string" || reply === "") { - return {}; - } - if (reply === "[]") { - return []; - } - var parsed = void 0; - try { - parsed = JSON.parse(reply); - } catch (e) { - parsed = {}; - // assume XML - if (reply.match(/^<\?xml/)) { - var errors = void 0; - if (errors = reply.match(/(.+)<\/errors>/)) { - parsed.errors = {}; - errors = errors[1].match(/(.+)/); - parsed.errors[error[1]] = error[2]; - } + /** + * __call() helpers + */ + + /** + * Parse given params, detect query-style params + * + * @param array|string params Parameters to parse + * + * @return array apiparams + */ + + }, { + key: "_parseApiParams", + value: function _parseApiParams(params) { + var apiparams = {}; + if ((typeof params === "undefined" ? "undefined" : _typeof(params)) === "object") { + apiparams = params; + } else { + this._parse_str(params, apiparams); //TODO + } + + return apiparams; } - return parsed; - } - // assume query format - var elements = reply.split("&"); - for (var _i = 0; _i < elements.length; _i++) { - var element = elements[_i].split("=", 2); - if (element.length > 1) { - parsed[element[0]] = decodeURIComponent(element[1]); - } else { - parsed[element[0]] = null; + + /** + * Replace null and boolean parameters with their string representations + * + * @param array apiparams Parameter array to replace in + * + * @return array apiparams + */ + + }, { + key: "_stringifyNullBoolParams", + value: function _stringifyNullBoolParams(apiparams) { + for (var key in apiparams) { + if (!apiparams.hasOwnProperty(key)) { + continue; + } + var value = apiparams[key]; + if (value === null) { + apiparams[key] = "null"; + } else if (value === true || value === false) { + apiparams[key] = value ? "true" : "false"; + } + } + + return apiparams; } - } - } - return parsed; - } - - /** - * Uncommon API methods - */ - - /** - * Gets the OAuth authenticate URL for the current request token - * - * @return object Promise - */ - - }, { - key: "oauth_authenticate", - value: function oauth_authenticate() { - var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var callback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined; - var type = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "authenticate"; - - var dfd = this._getDfd(); - if (typeof params.force_login === "undefined") { - params.force_login = null; - } - if (typeof params.screen_name === "undefined") { - params.screen_name = null; - } - if (["authenticate", "authorize"].indexOf(type) === -1) { - type = "authenticate"; - } - if (this._oauth_token === null) { - var error = "To get the " + type + " URL, the OAuth token must be set."; - if (dfd) { - dfd.reject({ error: error }); - return this._getPromise(dfd); - } - throw error; - } - var url = this._endpoint_oauth + "oauth/" + type + "?oauth_token=" + this._url(this._oauth_token); - if (params.force_login === true) { - url += "&force_login=1"; - } - if (params.screen_name !== null) { - url += "&screen_name=" + params.screen_name; - } - if (typeof callback === "function") { - callback(url); - } - if (dfd) { - dfd.resolve({ reply: url }); - return this._getPromise(dfd); - } - // no promises - return true; - } - - /** - * Gets the OAuth authorize URL for the current request token - * - * @return string The OAuth authorize URL - */ - - }, { - key: "oauth_authorize", - value: function oauth_authorize(params, callback) { - return this.oauth_authenticate(params, callback, "authorize"); - } - - /** - * Gets the OAuth bearer token - * - * @return object Promise - */ - - }, { - key: "oauth2_token", - value: function oauth2_token(callback) { - var _this2 = this; - - var dfd = this._getDfd(); - - if (this._oauth_consumer_key === null) { - var error = "To obtain a bearer token, the consumer key must be set."; - if (dfd) { - dfd.reject({ error: error }); - return this._getPromise(dfd); - } - throw error; - } - if (!dfd && typeof callback === "undefined") { - callback = function callback() {}; - } + /** + * API method mapping: Replaces _ with / character + * + * @param string fn Function called + * + * @return string API method to call + */ + + }, { + key: "_mapFnInsertSlashes", + value: function _mapFnInsertSlashes(fn) { + return fn.split("_").join("/").split(" ").join("_"); + } - var post_fields = "grant_type=client_credentials"; - var url = this._endpoint_oauth + "oauth2/token"; + /** + * API method mapping: Restore _ character in named parameters + * + * @param string method API method to call + * + * @return string API method with restored underscores + */ + + }, { + key: "_mapFnRestoreParamUnderscores", + value: function _mapFnRestoreParamUnderscores(method) { + var url_parameters_with_underscore = ["screen_name", "place_id"]; + var i = void 0, + param = void 0, + replacement_was = void 0; + for (i = 0; i < url_parameters_with_underscore.length; i++) { + param = url_parameters_with_underscore[i].toUpperCase(); + replacement_was = param.split("_").join("/"); + method = method.split(replacement_was).join(param); + } - if (this._use_proxy) { - url = url.replace(this._endpoint_base, this._endpoint_proxy); - } + return method; + } - var xml = this._getXmlRequestObject(); - if (xml === null) { - return; - } - xml.open("POST", url, true); - xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); - xml.setRequestHeader((this._use_proxy ? "X-" : "") + "Authorization", "Basic " + this._base64_encode(this._oauth_consumer_key + ":" + this._oauth_consumer_secret)); + /** + * Maps called PHP magic method name to Twitter API method + * + * @param string $fn Function called + * @param array $apiparams byref API parameters + * + * @return string[] (string method, string method_template) + */ + + }, { + key: "_mapFnToApiMethod", + value: function _mapFnToApiMethod(fn, apiparams) { + var method = "", + param = void 0, + i = void 0, + j = void 0; + + // replace _ by / + method = this._mapFnInsertSlashes(fn); + + // undo replacement for URL parameters + method = this._mapFnRestoreParamUnderscores(method); + + // replace AA by URL parameters + var method_template = method; + var match = method.match(/[A-Z_]{2,}/); + if (match) { + for (i = 0; i < match.length; i++) { + param = match[i]; + var param_l = param.toLowerCase(); + method_template = method_template.split(param).join(":" + param_l); + if (typeof apiparams[param_l] === "undefined") { + for (j = 0; j < 26; j++) { + method_template = method_template.split(String.fromCharCode(65 + j)).join("_" + String.fromCharCode(97 + j)); + } + throw "To call the templated method \"" + method_template + "\", specify the parameter value for \"" + param_l + "\"."; + } + method = method.split(param).join(apiparams[param_l]); + delete apiparams[param_l]; + } + } - xml.onreadystatechange = function () { - if (xml.readyState >= 4) { - var httpstatus = 12027; - try { - httpstatus = xml.status; - } catch (e) {} - var response = ""; - try { - response = xml.responseText; - } catch (e) {} - var reply = _this2._parseApiReply(response); - reply.httpstatus = httpstatus; - if (httpstatus === 200) { - _this2.setBearerToken(reply.access_token); + // replace A-Z by _a-z + for (i = 0; i < 26; i++) { + method = method.split(String.fromCharCode(65 + i)).join("_" + String.fromCharCode(97 + i)); + method_template = method_template.split(String.fromCharCode(65 + i)).join("_" + String.fromCharCode(97 + i)); + } + + return [method, method_template]; } - if (typeof callback === "function") { - callback(reply); + + /** + * Detects HTTP method to use for API call + * + * @param string method The API method to call + * @param array params The parameters to send along + * + * @return string The HTTP method that should be used + */ + + }, { + key: "_detectMethod", + value: function _detectMethod(method, params) { + if (typeof params.httpmethod !== "undefined") { + var httpmethod = params.httpmethod; + delete params.httpmethod; + return httpmethod; + } + + // multi-HTTP method endpoints + switch (method) { + case "account/settings": + case "account/login_verification_enrollment": + case "account/login_verification_request": + method = Object.keys(params).length ? method + "__post" : method; + break; + } + + var apimethods = this.getApiMethods(); + for (var _httpmethod in apimethods) { + if (apimethods.hasOwnProperty(_httpmethod) && apimethods[_httpmethod].indexOf(method) > -1) { + return _httpmethod; + } + } + throw "Can't find HTTP method to use for \"" + method + "\"."; } - if (dfd) { - dfd.resolve({ reply: reply }); + + /** + * Detects if API call should use multipart/form-data + * + * @param string method The API method to call + * + * @return bool Whether the method should be sent as multipart + */ + + }, { + key: "_detectMultipart", + value: function _detectMultipart(method) { + var multiparts = [ + // Tweets + "media/upload", + + // Users + "account/update_profile_image", "account/update_profile_banner"]; + return multiparts.indexOf(method) > -1; } - } - }; - // function called when an error occurs, including a timeout - xml.onerror = function (e) { - if (typeof callback === "function") { - callback(null, e); - } - if (dfd) { - dfd.reject(e); - } - }; - xml.timeout = 30000; // in milliseconds - - xml.send(post_fields); - if (dfd) { - return this._getPromise(dfd); - } - } - - /** - * Calls the API using cURL - * - * @param string httpmethod The HTTP method to use for making the request - * @param string method The API method to call - * @param array optional params The parameters to send along - * @param bool optional multipart Whether to use multipart/form-data - * @param bool optional app_only_auth Whether to use app-only bearer authentication - * @param function callback The function to call with the API call result - * - * @return mixed The API reply, encoded in the set return_format - */ - - }, { - key: "_callApi", - value: function _callApi(httpmethod, method) { - var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; - var multipart = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; - - var _this3 = this; - - var app_only_auth = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; - var callback = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : function () {}; - - var dfd = this._getDfd(); - - var url = this._getEndpoint(method), - authorization = null; - - var xml = this._getXmlRequestObject(); - if (xml === null) { - return; - } - var post_fields = void 0; - - if (httpmethod === "GET") { - var url_with_params = url; - if (JSON.stringify(params) !== "{}") { - url_with_params += "?" + this._http_build_query(params); - } - if (!app_only_auth) { - authorization = this._sign(httpmethod, url, params); - } - - if (this._use_proxy) { - url_with_params = url_with_params.replace(this._endpoint_base, this._endpoint_proxy).replace(this._endpoint_base_media, this._endpoint_proxy); - } - xml.open(httpmethod, url_with_params, true); - } else { - if (multipart) { - if (!app_only_auth) { - authorization = this._sign(httpmethod, url, {}); + + /** + * Signature helper + * + * @param string httpmethod Usually either 'GET' or 'POST' or 'DELETE' + * @param string method The API method to call + * @param array base_params The signature base parameters + * + * @return string signature + */ + + }, { + key: "_getSignature", + value: function _getSignature(httpmethod, method, keys, base_params) { + // convert params to string + var base_string = "", + key = void 0, + value = void 0; + for (var i = 0; i < keys.length; i++) { + key = keys[i]; + value = base_params[key]; + base_string += key + "=" + this._url(value) + "&"; + } + base_string = base_string.substring(0, base_string.length - 1); + return this._sha1(httpmethod + "&" + this._url(method) + "&" + this._url(base_string)); } - params = this._buildMultipart(method, params); - } else if (this._detectJsonBody(method)) { - authorization = this._sign(httpmethod, url, {}); - params = JSON.stringify(params); - } else { - if (!app_only_auth) { - authorization = this._sign(httpmethod, url, params); + + /** + * Generates the UNIX timestamp + */ + + }, { + key: "_time", + value: function _time() { + return Math.round(new Date().getTime() / 1000); } - params = this._http_build_query(params); - } - post_fields = params; - if (this._use_proxy || multipart) { - // force proxy for multipart base64 - url = url.replace(this._endpoint_base, this._endpoint_proxy).replace(this._endpoint_base_media, this._endpoint_proxy); - } - xml.open(httpmethod, url, true); - if (multipart) { - xml.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + post_fields.split("\r\n")[0].substring(2)); - } else if (this._detectJsonBody(method)) { - xml.setRequestHeader("Content-Type", "application/json"); - } else { - xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); - } - } - if (app_only_auth) { - if (this._oauth_consumer_key === null && this._oauth_bearer_token === null) { - var error = "To make an app-only auth API request, consumer key or bearer token must be set."; - if (dfd) { - dfd.reject({ error: error }); - return this._getPromise(dfd); + + /** + * Generates an OAuth signature + * + * @param string httpmethod Usually either 'GET' or 'POST' or 'DELETE' + * @param string method The API method to call + * @param array optional params The API call parameters, associative + * + * @return string Authorization HTTP header + */ + + }, { + key: "_sign", + value: function _sign(httpmethod, method) { + var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + + if (this._oauth_consumer_key === null) { + throw "To generate a signature, the consumer key must be set."; + } + var sign_params = { + consumer_key: this._oauth_consumer_key, + version: "1.0", + timestamp: this._time(), + nonce: this._nonce(), + signature_method: "HMAC-SHA1" + }; + var sign_base_params = {}; + for (var key in sign_params) { + if (!sign_params.hasOwnProperty(key)) { + continue; + } + var value = sign_params[key]; + sign_base_params["oauth_" + key] = this._url(value); + } + if (this._oauth_token !== null) { + sign_base_params.oauth_token = this._url(this._oauth_token); + } + var oauth_params = this._clone(sign_base_params); + for (key in params) { + if (!params.hasOwnProperty(key)) { + continue; + } + sign_base_params[key] = params[key]; + } + var keys = this._ksort(sign_base_params); + + var signature = this._getSignature(httpmethod, method, keys, sign_base_params); + + params = oauth_params; + params.oauth_signature = signature; + keys = this._ksort(params); + var authorization = "OAuth "; + for (var i = 0; i < keys.length; i++) { + key = keys[i]; + authorization += key + "=\"" + this._url(params[key]) + "\", "; + } + return authorization.substring(0, authorization.length - 2); } - throw error; - } - // automatically fetch bearer token, if necessary - if (this._oauth_bearer_token === null) { - if (dfd) { - return this.oauth2_token().then(function () { - return _this3._callApi(httpmethod, method, params, multipart, app_only_auth, callback); - }); + + /** + * Build multipart request from upload params + * + * @param string method The API method to call + * @param array params The parameters to send along + * + * @return null|string The built multipart request body + */ + + }, { + key: "_buildMultipart", + value: function _buildMultipart(method, params) { + // well, files will only work in multipart methods + if (!this._detectMultipart(method)) { + return; + } + + // only check specific parameters + var possible_methods = [ + // Tweets + "media/upload", + // Accounts + "account/update_profile_image", "account/update_profile_banner"]; + var possible_files = { + // Tweets + "media/upload": "media", + // Accounts + "account/update_profile_image": "image", + "account/update_profile_banner": "banner" + }; + // method might have files? + if (possible_methods.indexOf(method) === -1) { + return; + } + + // check for filenames + possible_files = possible_files[method].split(" "); + + var multipart_border = "--------------------" + this._nonce(); + var multipart_request = ""; + for (var key in params) { + if (!params.hasOwnProperty(key)) { + continue; + } + multipart_request += "--" + multipart_border + "\r\nContent-Disposition: form-data; name=\"" + key + "\""; + if (possible_files.indexOf(key) === -1) { + multipart_request += "\r\nContent-Transfer-Encoding: base64"; + } + multipart_request += "\r\n\r\n" + params[key] + "\r\n"; + } + multipart_request += "--" + multipart_border + "--"; + return multipart_request; } - this.oauth2_token(function () { - _this3._callApi(httpmethod, method, params, multipart, app_only_auth, callback); - }); - return; - } - authorization = "Bearer " + this._oauth_bearer_token; - } - if (authorization !== null) { - xml.setRequestHeader((this._use_proxy ? "X-" : "") + "Authorization", authorization); - } - xml.onreadystatechange = function () { - if (xml.readyState >= 4) { - var httpstatus = 12027; - try { - httpstatus = xml.status; - } catch (e) {} - var response = ""; - try { - response = xml.responseText; - } catch (e) {} - var reply = _this3._parseApiReply(response); - reply.httpstatus = httpstatus; - var rate = null; - if (typeof xml.getResponseHeader !== "undefined" && xml.getResponseHeader("x-rate-limit-limit") !== "") { - rate = { - limit: xml.getResponseHeader("x-rate-limit-limit"), - remaining: xml.getResponseHeader("x-rate-limit-remaining"), - reset: xml.getResponseHeader("x-rate-limit-reset") - }; + + /** + * Detects if API call should use media endpoint + * + * @param string method The API method to call + * + * @return bool Whether the method is defined in media API + */ + + }, { + key: "_detectMedia", + value: function _detectMedia(method) { + var medias = ["media/metadata/create", "media/upload"]; + return medias.indexOf(method) > -1; } - if (typeof callback === "function") { - callback(reply, rate); + + /** + * Detects if API call should use JSON body + * + * @param string method The API method to call + * + * @return bool Whether the method is defined as accepting JSON body + */ + + }, { + key: "_detectJsonBody", + value: function _detectJsonBody(method) { + var json_bodies = ["collections/entries/curate", "custom_profiles/new", "direct_messages/events/new", "direct_messages/indicate_typing", "direct_messages/mark_read", "direct_messages/welcome_messages/new", "direct_messages/welcome_messages/rules/new", "direct_messages/welcome_messages/update", "media/metadata/create"]; + return json_bodies.indexOf(method) > -1; } - if (dfd) { - dfd.resolve({ reply: reply, rate: rate }); + + /** + * Builds the complete API endpoint url + * + * @param string method The API method to call + * + * @return string The URL to send the request to + */ + + }, { + key: "_getEndpoint", + value: function _getEndpoint(method) { + var url = void 0; + if (method.substring(0, 22) === "oauth/invalidate_token") { + url = this._endpoint + method + ".json"; + } else if (method.substring(0, 5) === "oauth") { + url = this._endpoint_oauth + method; + } else if (this._detectMedia(method)) { + url = this._endpoint_media + method + ".json"; + } else if (method === "statuses/oembed") { + url = this._endpoint_publish + "oembed"; + } else { + url = this._endpoint + method + ".json"; + } + return url; } - } - }; - // function called when an error occurs, including a timeout - xml.onerror = function (e) { - if (typeof callback === "function") { - callback(null, null, e); - } - if (dfd) { - dfd.reject(e); - } - }; - xml.timeout = 30000; // in milliseconds - - xml.send(httpmethod === "GET" ? null : post_fields); - if (dfd) { - return this._getPromise(dfd); - } - return true; - } - - /** - * Main API handler working on any requests you issue - * - * @param string fn The member function you called - * @param array params The parameters you sent along - * @param function callback The callback to call with the reply - * @param bool app_only_auth Whether to use app-only auth - * - * @return object Promise - */ - - }, { - key: "__call", - value: function __call(fn) { - var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - var callback = arguments[2]; - var app_only_auth = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; - - if (typeof callback !== "function" && typeof params === "function") { - callback = params; - params = {}; - if (typeof callback === "boolean") { - app_only_auth = callback; - } - } else if (typeof callback === "undefined") { - callback = function callback() {}; - } - switch (fn) { - case "oauth_authenticate": - case "oauth_authorize": - return this[fn](params, callback); - case "oauth2_token": - return this[fn](callback); - } + /** + * Parses the API reply to encode it in the set return_format + * + * @param string reply The actual reply, JSON-encoded or URL-encoded + * + * @return array|object The parsed reply + */ + + }, { + key: "_parseApiReply", + value: function _parseApiReply(reply) { + if (typeof reply !== "string" || reply === "") { + return {}; + } + if (reply === "[]") { + return []; + } + var parsed = void 0; + try { + parsed = JSON.parse(reply); + } catch (e) { + parsed = {}; + // assume XML + if (reply.match(/^<\?xml/)) { + var errors = void 0; + if (errors = reply.match(/(.+)<\/errors>/)) { + parsed.errors = {}; + errors = errors[1].match(/(.+)/); + parsed.errors[error[1]] = error[2]; + } + } + return parsed; + } + // assume query format + var elements = reply.split("&"); + for (var _i = 0; _i < elements.length; _i++) { + var element = elements[_i].split("=", 2); + if (element.length > 1) { + parsed[element[0]] = decodeURIComponent(element[1]); + } else { + parsed[element[0]] = null; + } + } + } + return parsed; + } - // parse parameters - var apiparams = this._parseApiParams(params); + /** + * Uncommon API methods + */ + + /** + * Gets the OAuth authenticate URL for the current request token + * + * @return object Promise + */ + + }, { + key: "oauth_authenticate", + value: function oauth_authenticate() { + var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var callback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined; + var type = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "authenticate"; + + var dfd = this._getDfd(); + if (typeof params.force_login === "undefined") { + params.force_login = null; + } + if (typeof params.screen_name === "undefined") { + params.screen_name = null; + } + if (["authenticate", "authorize"].indexOf(type) === -1) { + type = "authenticate"; + } + if (this._oauth_token === null) { + var error = "To get the " + type + " URL, the OAuth token must be set."; + if (dfd) { + dfd.reject({error: error}); + return this._getPromise(dfd); + } + throw error; + } + var url = this._endpoint_oauth + "oauth/" + type + "?oauth_token=" + this._url(this._oauth_token); + if (params.force_login === true) { + url += "&force_login=1"; + } + if (params.screen_name !== null) { + url += "&screen_name=" + params.screen_name; + } + if (typeof callback === "function") { + callback(url); + } + if (dfd) { + dfd.resolve({reply: url}); + return this._getPromise(dfd); + } + // no promises + return true; + } - // stringify null and boolean parameters - apiparams = this._stringifyNullBoolParams(apiparams); + /** + * Gets the OAuth authorize URL for the current request token + * + * @return string The OAuth authorize URL + */ - // reset token when requesting a new token (causes 401 for signature error on 2nd+ requests) - if (fn === "oauth_requestToken") { - this.setToken(null, null); - } + }, { + key: "oauth_authorize", + value: function oauth_authorize(params, callback) { + return this.oauth_authenticate(params, callback, "authorize"); + } - // map function name to API method - - var _mapFnToApiMethod2 = this._mapFnToApiMethod(fn, apiparams), - _mapFnToApiMethod3 = _slicedToArray(_mapFnToApiMethod2, 2), - method = _mapFnToApiMethod3[0], - method_template = _mapFnToApiMethod3[1], - httpmethod = this._detectMethod(method_template, apiparams), - multipart = this._detectMultipart(method_template); - - return this._callApi(httpmethod, method, apiparams, multipart, app_only_auth, callback); - } - }]); - - return Codebird; - }(); - - if ((typeof module === "undefined" ? "undefined" : _typeof(module)) === "object" && module && _typeof(module.exports) === "object") { - // Expose codebird as module.exports in loaders that implement the Node - // module pattern (including browserify). Do not create the global, since - // the user will be storing it themselves locally, and globals are frowned - // upon in the Node module world. - module.exports = Codebird; - } else { - // Otherwise expose codebird to the global object as usual - if ((typeof window === "undefined" ? "undefined" : _typeof(window)) === "object" && window) { - window.Codebird = Codebird; - } + /** + * Gets the OAuth bearer token + * + * @return object Promise + */ + + }, { + key: "oauth2_token", + value: function oauth2_token(callback) { + var _this2 = this; + + var dfd = this._getDfd(); + + if (this._oauth_consumer_key === null) { + var error = "To obtain a bearer token, the consumer key must be set."; + if (dfd) { + dfd.reject({error: error}); + return this._getPromise(dfd); + } + throw error; + } + + if (!dfd && typeof callback === "undefined") { + callback = function callback() { + }; + } + + var post_fields = "grant_type=client_credentials"; + var url = this._endpoint_oauth + "oauth2/token"; + + if (this._use_proxy) { + url = url.replace(this._endpoint_base, this._endpoint_proxy); + } + + var xml = this._getXmlRequestObject(); + if (xml === null) { + return; + } + xml.open("POST", url, true); + xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xml.setRequestHeader((this._use_proxy ? "X-" : "") + "Authorization", "Basic " + this._base64_encode(this._oauth_consumer_key + ":" + this._oauth_consumer_secret)); + + xml.onreadystatechange = function () { + if (xml.readyState >= 4) { + var httpstatus = 12027; + try { + httpstatus = xml.status; + } catch (e) { + } + var response = ""; + try { + response = xml.responseText; + } catch (e) { + } + var reply = _this2._parseApiReply(response); + reply.httpstatus = httpstatus; + if (httpstatus === 200) { + _this2.setBearerToken(reply.access_token); + } + if (typeof callback === "function") { + callback(reply); + } + if (dfd) { + dfd.resolve({reply: reply}); + } + } + }; + // function called when an error occurs, including a timeout + xml.onerror = function (e) { + if (typeof callback === "function") { + callback(null, e); + } + if (dfd) { + dfd.reject(e); + } + }; + xml.timeout = 30000; // in milliseconds + + xml.send(post_fields); + if (dfd) { + return this._getPromise(dfd); + } + } + + /** + * Calls the API using cURL + * + * @param string httpmethod The HTTP method to use for making the request + * @param string method The API method to call + * @param array optional params The parameters to send along + * @param bool optional multipart Whether to use multipart/form-data + * @param bool optional app_only_auth Whether to use app-only bearer authentication + * @param function callback The function to call with the API call result + * + * @return mixed The API reply, encoded in the set return_format + */ + + }, { + key: "_callApi", + value: function _callApi(httpmethod, method) { + var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + var multipart = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; + + var _this3 = this; + + var app_only_auth = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; + var callback = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : function () { + }; + + var dfd = this._getDfd(); + + var url = this._getEndpoint(method), + authorization = null; + + var xml = this._getXmlRequestObject(); + if (xml === null) { + return; + } + var post_fields = void 0; + + if (httpmethod === "GET") { + var url_with_params = url; + if (JSON.stringify(params) !== "{}") { + url_with_params += "?" + this._http_build_query(params); + } + if (!app_only_auth) { + authorization = this._sign(httpmethod, url, params); + } + + if (this._use_proxy) { + url_with_params = url_with_params.replace(this._endpoint_base, this._endpoint_proxy).replace(this._endpoint_base_media, this._endpoint_proxy); + } + xml.open(httpmethod, url_with_params, true); + } else { + if (multipart) { + if (!app_only_auth) { + authorization = this._sign(httpmethod, url, {}); + } + params = this._buildMultipart(method, params); + } else if (this._detectJsonBody(method)) { + authorization = this._sign(httpmethod, url, {}); + params = JSON.stringify(params); + } else { + if (!app_only_auth) { + authorization = this._sign(httpmethod, url, params); + } + params = this._http_build_query(params); + } + post_fields = params; + if (this._use_proxy || multipart) { + // force proxy for multipart base64 + url = url.replace(this._endpoint_base, this._endpoint_proxy).replace(this._endpoint_base_media, this._endpoint_proxy); + } + xml.open(httpmethod, url, true); + if (multipart) { + xml.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + post_fields.split("\r\n")[0].substring(2)); + } else if (this._detectJsonBody(method)) { + xml.setRequestHeader("Content-Type", "application/json"); + } else { + xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + } + } + if (app_only_auth) { + if (this._oauth_consumer_key === null && this._oauth_bearer_token === null) { + var error = "To make an app-only auth API request, consumer key or bearer token must be set."; + if (dfd) { + dfd.reject({error: error}); + return this._getPromise(dfd); + } + throw error; + } + // automatically fetch bearer token, if necessary + if (this._oauth_bearer_token === null) { + if (dfd) { + return this.oauth2_token().then(function () { + return _this3._callApi(httpmethod, method, params, multipart, app_only_auth, callback); + }); + } + this.oauth2_token(function () { + _this3._callApi(httpmethod, method, params, multipart, app_only_auth, callback); + }); + return; + } + authorization = "Bearer " + this._oauth_bearer_token; + } + if (authorization !== null) { + xml.setRequestHeader((this._use_proxy ? "X-" : "") + "Authorization", authorization); + } + xml.onreadystatechange = function () { + if (xml.readyState >= 4) { + var httpstatus = 12027; + try { + httpstatus = xml.status; + } catch (e) { + } + var response = ""; + try { + response = xml.responseText; + } catch (e) { + } + var reply = _this3._parseApiReply(response); + reply.httpstatus = httpstatus; + var rate = null; + if (typeof xml.getResponseHeader !== "undefined" && xml.getResponseHeader("x-rate-limit-limit") !== "") { + rate = { + limit: xml.getResponseHeader("x-rate-limit-limit"), + remaining: xml.getResponseHeader("x-rate-limit-remaining"), + reset: xml.getResponseHeader("x-rate-limit-reset") + }; + } + if (typeof callback === "function") { + callback(reply, rate); + } + if (dfd) { + dfd.resolve({reply: reply, rate: rate}); + } + } + }; + // function called when an error occurs, including a timeout + xml.onerror = function (e) { + if (typeof callback === "function") { + callback(null, null, e); + } + if (dfd) { + dfd.reject(e); + } + }; + xml.timeout = 30000; // in milliseconds + + xml.send(httpmethod === "GET" ? null : post_fields); + if (dfd) { + return this._getPromise(dfd); + } + return true; + } + + /** + * Main API handler working on any requests you issue + * + * @param string fn The member function you called + * @param array params The parameters you sent along + * @param function callback The callback to call with the reply + * @param bool app_only_auth Whether to use app-only auth + * + * @return object Promise + */ + + }, { + key: "__call", + value: function __call(fn) { + var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + var callback = arguments[2]; + var app_only_auth = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; + + if (typeof callback !== "function" && typeof params === "function") { + callback = params; + params = {}; + if (typeof callback === "boolean") { + app_only_auth = callback; + } + } else if (typeof callback === "undefined") { + callback = function callback() { + }; + } + switch (fn) { + case "oauth_authenticate": + case "oauth_authorize": + return this[fn](params, callback); + + case "oauth2_token": + return this[fn](callback); + } + + // parse parameters + var apiparams = this._parseApiParams(params); + + // stringify null and boolean parameters + apiparams = this._stringifyNullBoolParams(apiparams); + + // reset token when requesting a new token (causes 401 for signature error on 2nd+ requests) + if (fn === "oauth_requestToken") { + this.setToken(null, null); + } + + // map function name to API method + + var _mapFnToApiMethod2 = this._mapFnToApiMethod(fn, apiparams), + _mapFnToApiMethod3 = _slicedToArray(_mapFnToApiMethod2, 2), + method = _mapFnToApiMethod3[0], + method_template = _mapFnToApiMethod3[1], + httpmethod = this._detectMethod(method_template, apiparams), + multipart = this._detectMultipart(method_template); + + return this._callApi(httpmethod, method, apiparams, multipart, app_only_auth, callback); + } + }]); - // Register as a named AMD module, since codebird can be concatenated with other - // files that may use define, but not via a proper concatenation script that - // understands anonymous AMD modules. A named AMD is safest and most robust - // way to register. Lowercase codebird is used because AMD module names are - // derived from file names, and codebird is normally delivered in a lowercase - // file name. Do this after creating the global so that if an AMD module wants - // to call noConflict to hide this version of codebird, it will work. - if (typeof define === "function" && define.amd) { - define("codebird", [], function () { return Codebird; - }); + }(); + + if ((typeof module === "undefined" ? "undefined" : _typeof(module)) === "object" && module && _typeof(module.exports) === "object") { + // Expose codebird as module.exports in loaders that implement the Node + // module pattern (including browserify). Do not create the global, since + // the user will be storing it themselves locally, and globals are frowned + // upon in the Node module world. + module.exports = Codebird; + } else { + // Otherwise expose codebird to the global object as usual + if ((typeof window === "undefined" ? "undefined" : _typeof(window)) === "object" && window) { + window.Codebird = Codebird; + } + + // Register as a named AMD module, since codebird can be concatenated with other + // files that may use define, but not via a proper concatenation script that + // understands anonymous AMD modules. A named AMD is safest and most robust + // way to register. Lowercase codebird is used because AMD module names are + // derived from file names, and codebird is normally delivered in a lowercase + // file name. Do this after creating the global so that if an AMD module wants + // to call noConflict to hide this version of codebird, it will work. + if (typeof define === "function" && define.amd) { + define("codebird", [], function () { + return Codebird; + }); + } } - } })();