/** * A Twitter library in JavaScript * * @package codebird * @version 2.5.0 * @author Jublo Solutions * @copyright 2010-2014 Jublo Solutions * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 * @link https://github.com/jublonet/codebird-php */ /* jshint curly: true, eqeqeq: true, latedef: true, quotmark: double, undef: true, unused: true, trailing: true, laxbreak: true */ /* global window, document, navigator, console, Ti, ActiveXObject, module, define, require */ (function (undefined) { "use strict"; /** * Array.indexOf polyfill */ if (! Array.prototype.indexOf) { Array.prototype.indexOf = function (obj, start) { for (var i = (start || 0); i < this.length; i++) { if (this[i] === obj) { return i; } } return -1; }; } /** * A Twitter library in JavaScript * * @package codebird * @subpackage codebird-js */ /* jshint -W098 */ var Codebird = function () { /* jshint +W098 */ /** * The OAuth consumer key of your registered app */ var _oauth_consumer_key = null; /** * The corresponding consumer secret */ var _oauth_consumer_secret = null; /** * The app-only bearer token. Used to authorize app-only requests */ var _oauth_bearer_token = null; /** * The API endpoint base to use */ var _endpoint_base = "https://api.twitter.com/"; /** * The media API endpoint base to use */ var _endpoint_base_media = "https://upload.twitter.com/"; /** * The API endpoint to use */ var _endpoint = _endpoint_base + "1.1/"; /** * The media API endpoint to use */ var _endpoint_media = _endpoint_base_media + "1.1/"; /** * The API endpoint base to use */ var _endpoint_oauth = _endpoint_base; /** * API proxy endpoint */ var _endpoint_proxy = "https://api.jublo.net/codebird/"; /** * The API endpoint to use for old requests */ var _endpoint_old = _endpoint_base + "1/"; /** * Use JSONP for GET requests in IE7-9 */ var _use_jsonp = (typeof navigator !== "undefined" && typeof navigator.userAgent !== "undefined" && (navigator.userAgent.indexOf("Trident/4") > -1 || navigator.userAgent.indexOf("Trident/5") > -1 || navigator.userAgent.indexOf("MSIE 7.0") > -1 ) ); /** * Whether to access the API via a proxy that is allowed by CORS * Assume that CORS is only necessary in browsers */ var _use_proxy = (typeof navigator !== "undefined" && typeof navigator.userAgent !== "undefined" ); /** * The Request or access token. Used to sign requests */ var _oauth_token = null; /** * The corresponding request or access token secret */ var _oauth_token_secret = null; /** * The current Codebird version */ var _version = "2.5.0"; /** * Sets the OAuth consumer key and secret (App key) * * @param string key OAuth consumer key * @param string secret OAuth consumer secret * * @return void */ var setConsumerKey = function (key, secret) { _oauth_consumer_key = key; _oauth_consumer_secret = secret; }; /** * Sets the OAuth2 app-only auth bearer token * * @param string token OAuth2 bearer token * * @return void */ var setBearerToken = function (token) { _oauth_bearer_token = token; }; /** * Gets the current Codebird version * * @return string The version number */ var getVersion = function () { return _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 */ var setToken = function (token, secret) { _oauth_token = token; _oauth_token_secret = secret; }; /** * Enables or disables CORS proxy * * @param bool use_proxy Whether to use CORS proxy or not * * @return void */ var setUseProxy = function (use_proxy) { _use_proxy = !! use_proxy; }; /** * Sets custom CORS proxy server * * @param string proxy Address of proxy server to use * * @return void */ var setProxy = function (proxy) { // add trailing slash if missing if (! proxy.match(/\/$/)) { proxy += "/"; } _endpoint_proxy = proxy; }; /** * 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 */ var _parse_str = function (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 (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; } } } } 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; } } /* jshint -W061 */ 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); /* jshint +W061 */ } } }; /** * 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 mixed The API reply encoded in the set return_format */ var __call = function (fn, params, callback, app_only_auth) { if (typeof params === "undefined") { params = {}; } if (typeof app_only_auth === "undefined") { app_only_auth = 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 () {}; } switch (fn) { case "oauth_authenticate": case "oauth_authorize": return this[fn](params, callback); case "oauth2_token": return this[fn](callback); } // reset token when requesting a new token (causes 401 for signature error on 2nd+ requests) if (fn === "oauth_requestToken") { setToken(null, null); } // parse parameters var apiparams = {}; if (typeof params === "object") { apiparams = params; } else { _parse_str(params, apiparams); //TODO } // map function name to API method var method = ""; var param, i, j; // replace _ by / var path = fn.split("_"); for (i = 0; i < path.length; i++) { if (i > 0) { method += "/"; } method += path[i]; } // undo replacement for URL parameters var url_parameters_with_underscore = ["screen_name", "place_id"]; for (i = 0; i < url_parameters_with_underscore.length; i++) { param = url_parameters_with_underscore[i].toUpperCase(); var replacement_was = param.split("_").join("/"); method = method.split(replacement_was).join(param); } // 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)); } console.warn("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]; } } // 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)); } var httpmethod = _detectMethod(method_template, apiparams); var multipart = _detectMultipart(method_template); var internal = _detectInternal(method_template); return _callApi( httpmethod, method, apiparams, multipart, app_only_auth, internal, callback ); }; /** * Gets the OAuth authenticate URL for the current request token * * @return string The OAuth authenticate URL */ var oauth_authenticate = function (params, callback) { if (typeof params.force_login === "undefined") { params.force_login = null; } if (typeof params.screen_name === "undefined") { params.screen_name = null; } if (_oauth_token === null) { console.warn("To get the authenticate URL, the OAuth token must be set."); } var url = _endpoint_oauth + "oauth/authenticate?oauth_token=" + _url(_oauth_token); if (params.force_login === true) { url += "?force_login=1"; if (params.screen_name !== null) { url += "&screen_name=" + params.screen_name; } } callback(url); return true; }; /** * Gets the OAuth authorize URL for the current request token * * @return string The OAuth authorize URL */ var oauth_authorize = function (params, callback) { if (typeof params.force_login === "undefined") { params.force_login = null; } if (typeof params.screen_name === "undefined") { params.screen_name = null; } if (_oauth_token === null) { console.warn("To get the authorize URL, the OAuth token must be set."); } var url = _endpoint_oauth + "oauth/authorize?oauth_token=" + _url(_oauth_token); if (params.force_login === true) { url += "?force_login=1"; if (params.screen_name !== null) { url += "&screen_name=" + params.screen_name; } } callback(url); return true; }; /** * Gets the OAuth bearer token * * @return string The OAuth bearer token */ var oauth2_token = function (callback) { if (_oauth_consumer_key === null) { console.warn("To obtain a bearer token, the consumer key must be set."); } if (typeof callback === "undefined") { callback = function () {}; } var post_fields = "grant_type=client_credentials"; var url = _endpoint_oauth + "oauth2/token"; if (_use_proxy) { url = url.replace( _endpoint_base, _endpoint_proxy ); } var xml = _getXmlRequestObject(); if (xml === null) { return; } xml.open("POST", url, true); xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xml.setRequestHeader( (_use_proxy ? "X-" : "") + "Authorization", "Basic " + _base64_encode(_oauth_consumer_key + ":" + _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 = _parseApiReply(response); reply.httpstatus = httpstatus; if (httpstatus === 200) { setBearerToken(reply.access_token); } callback(reply); } }; xml.send(post_fields); }; /** * Signing helpers */ /** * URL-encodes the given data * * @param mixed data * * @return mixed The encoded data */ var _url = function (data) { if ((/boolean|number|string/).test(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 */ var _sha1 = function () { 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; 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; return function (e) { var b = _oauth_consumer_secret + "&" + (null !== _oauth_token_secret ? _oauth_token_secret : ""); if (_oauth_consumer_secret === null) { console.warn("To generate a hash, the consumer secret must be set."); } var c = q(b); if (c.length > 16) { c = n(c, b.length * g); } b = new Array(16); for (var a = new Array(16), d = 0; d < 16; d++) { a[d] = c[d] ^ 909522486; b[d] = c[d] ^ 1549556828; } c = n(a.concat(q(e)), 512 + e.length * g); b = n(b.concat(c), 672); c = ""; for (a = 0; a < 4 * b.length; a += 3) { for (d = (b[a >> 2] >> 8 * (3 - a % 4) & 255) << 16 | (b[a + 1 >> 2] >> 8 * (3 - (a + 1) % 4) & 255) << 8 | b[a + 2 >> 2] >> 8 * (3 - (a + 2) % 4) & 255, e = 0; 4 > e; e++) { c = 8 * a + 6 * e > 32 * b.length ? c + "=" : c + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" .charAt(d >> 6 * (3 - e) & 63); } } return c; }; }(); /* * 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 */ var _base64_encode = function (a) { var d, e, f, b, g = 0, h = 0, i = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", 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); c = c.join(""); a = a.length % 3; return (a ? c.slice(0, a - 3) : c) + "===".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 */ var _http_build_query = function (e, f, b) { function g(c, a, d) { var b, e = []; if (a === true) { a = "1"; } else if (a === false) { a = "0"; } if (null !== a) { if (typeof a === "object") { for (b in a) { if (a[b] !== null) { e.push(g(c + "[" + b + "]", a[b], d)); } } return e.join(d); } if (typeof a !== "function") { return _url(c) + "=" + _url(a); } console.warn("There was an error processing for http_build_query()."); } else { return ""; } } var d, c, h = []; if (! b) { b = "&"; } for (c in e) { d = e[c]; if (f && ! isNaN(c)) { c = String(f) + c; } d = g(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 */ var _nonce = function (length) { if (typeof length === "undefined") { length = 8; } if (length < 1) { console.warn("Invalid nonce length."); } var nonce = ""; for (var i = 0; i < length; i++) { var character = Math.floor(Math.random() * 61); nonce += "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz".substring(character, character + 1); } return nonce; }; /** * Sort array elements by key * * @param array input_arr The array to sort * * @return array The sorted keys */ var _ksort = function (input_arr) { var keys = [], sorter, k; sorter = function (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 */ var _clone = function (obj) { var clone = {}; for (var i in obj) { if (typeof(obj[i]) === "object") { clone[i] = _clone(obj[i]); } else { clone[i] = obj[i]; } } return clone; }; /** * 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 * @param bool optional append_to_get Whether to append the OAuth params to GET * * @return string Authorization HTTP header */ var _sign = function (httpmethod, method, params, append_to_get) { if (typeof params === "undefined") { params = {}; } if (typeof append_to_get === "undefined") { append_to_get = false; } if (_oauth_consumer_key === null) { console.warn("To generate a signature, the consumer key must be set."); } var sign_params = { consumer_key: _oauth_consumer_key, version: "1.0", timestamp: Math.round(new Date().getTime() / 1000), nonce: _nonce(), signature_method: "HMAC-SHA1" }; var sign_base_params = {}; var value; for (var key in sign_params) { value = sign_params[key]; sign_base_params["oauth_" + key] = _url(value); } if (_oauth_token !== null) { sign_base_params.oauth_token = _url(_oauth_token); } var oauth_params = _clone(sign_base_params); for (key in params) { value = params[key]; sign_base_params[key] = value; } var keys = _ksort(sign_base_params); var sign_base_string = ""; for (var i = 0; i < keys.length; i++) { key = keys[i]; value = sign_base_params[key]; sign_base_string += key + "=" + _url(value) + "&"; } sign_base_string = sign_base_string.substring(0, sign_base_string.length - 1); var signature = _sha1(httpmethod + "&" + _url(method) + "&" + _url(sign_base_string)); params = append_to_get ? sign_base_params : oauth_params; params.oauth_signature = signature; keys = _ksort(params); var authorization = ""; if (append_to_get) { for(i = 0; i < keys.length; i++) { key = keys[i]; value = params[key]; authorization += key + "=" + _url(value) + "&"; } return authorization.substring(0, authorization.length - 1); } authorization = "OAuth "; for (i = 0; i < keys.length; i++) { key = keys[i]; value = params[key]; authorization += key + "=\"" + _url(value) + "\", "; } return authorization.substring(0, authorization.length - 2); }; /** * 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 */ var _detectMethod = function (method, params) { // multi-HTTP method endpoints switch (method) { case "account/settings": case "account/login_verification_enrollment": case "account/login_verification_request": method = params.length ? method + "__post" : method; break; } var httpmethods = {}; httpmethods.GET = [ // Timelines "statuses/mentions_timeline", "statuses/user_timeline", "statuses/home_timeline", "statuses/retweets_of_me", // Tweets "statuses/retweets/:id", "statuses/show/:id", "statuses/oembed", "statuses/retweeters/ids", // Search "search/tweets", // Direct Messages "direct_messages", "direct_messages/sent", "direct_messages/show", // Friends & Followers "friendships/no_retweets/ids", "friends/ids", "followers/ids", "friendships/lookup", "friendships/incoming", "friendships/outgoing", "friendships/show", "friends/list", "followers/list", "friendships/lookup", // Users "account/settings", "account/verify_credentials", "blocks/list", "blocks/ids", "users/lookup", "users/show", "users/search", "users/contributees", "users/contributors", "users/profile_banner", "mutes/users/ids", "mutes/users/list", // Suggested Users "users/suggestions/:slug", "users/suggestions", "users/suggestions/:slug/members", // Favorites "favorites/list", // Lists "lists/list", "lists/statuses", "lists/memberships", "lists/subscribers", "lists/subscribers/show", "lists/members/show", "lists/members", "lists/show", "lists/subscriptions", "lists/ownerships", // Saved searches "saved_searches/list", "saved_searches/show/:id", // Places & Geo "geo/id/:place_id", "geo/reverse_geocode", "geo/search", "geo/similar_places", // Trends "trends/place", "trends/available", "trends/closest", // OAuth "oauth/authenticate", "oauth/authorize", // Help "help/configuration", "help/languages", "help/privacy", "help/tos", "application/rate_limit_status", // Tweets "statuses/lookup", // Internal "users/recommendations", "account/push_destinations/device", "activity/about_me", "activity/by_friends", "statuses/media_timeline", "timeline/home", "help/experiments", "search/typeahead", "search/universal", "discover/universal", "conversation/show", "statuses/:id/activity/summary", "account/login_verification_enrollment", "account/login_verification_request", "prompts/suggest", "beta/timelines/custom/list", "beta/timelines/timeline", "beta/timelines/custom/show" ]; httpmethods.POST = [ // Tweets "statuses/destroy/:id", "statuses/update", "statuses/retweet/:id", "statuses/update_with_media", "media/upload", // Direct Messages "direct_messages/destroy", "direct_messages/new", // Friends & Followers "friendships/create", "friendships/destroy", "friendships/update", // Users "account/settings__post", "account/update_delivery_device", "account/update_profile", "account/update_profile_background_image", "account/update_profile_colors", "account/update_profile_image", "blocks/create", "blocks/destroy", "account/update_profile_banner", "account/remove_profile_banner", "mutes/users/create", "mutes/users/destroy", // Favorites "favorites/destroy", "favorites/create", // Lists "lists/members/destroy", "lists/subscribers/create", "lists/subscribers/destroy", "lists/members/create_all", "lists/members/create", "lists/destroy", "lists/update", "lists/create", "lists/members/destroy_all", // Saved Searches "saved_searches/create", "saved_searches/destroy/:id", // Spam Reporting "users/report_spam", // OAuth "oauth/access_token", "oauth/request_token", "oauth2/token", "oauth2/invalidate_token", // Internal "direct_messages/read", "account/login_verification_enrollment__post", "push_destinations/enable_login_verification", "account/login_verification_request__post", "beta/timelines/custom/create", "beta/timelines/custom/update", "beta/timelines/custom/destroy", "beta/timelines/custom/add", "beta/timelines/custom/remove" ]; for (var httpmethod in httpmethods) { if (httpmethods[httpmethod].indexOf(method) > -1) { return httpmethod; } } console.warn("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 */ var _detectMultipart = function (method) { var multiparts = [ // Tweets "statuses/update_with_media", // Users "account/update_profile_background_image", "account/update_profile_image", "account/update_profile_banner" ]; return multiparts.indexOf(method) > -1; }; /** * 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 */ var _buildMultipart = function (method, params) { // well, files will only work in multipart methods if (! _detectMultipart(method)) { return; } // only check specific parameters var possible_methods = [ // Tweets "statuses/update_with_media", // Accounts "account/update_profile_background_image", "account/update_profile_image", "account/update_profile_banner" ]; var possible_files = { // Tweets "statuses/update_with_media": "media[]", // Accounts "account/update_profile_background_image": "image", "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 = "--------------------" + _nonce(); var multipart_request = ""; for (var key in params) { multipart_request += "--" + multipart_border + "\r\n" + "Content-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 is internal * * @param string method The API method to call * * @return bool Whether the method is defined in internal API */ var _detectInternal = function (method) { var internals = [ "users/recommendations" ]; return internals.join(" ").indexOf(method) > -1; }; /** * 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 */ var _detectMedia = function (method) { var medias = [ "media/upload" ]; return medias.join(" ").indexOf(method) > -1; }; /** * Detects if API call should use old endpoint * * @param string method The API method to call * * @return bool Whether the method is defined in old API */ var _detectOld = function (method) { var olds = [ "account/push_destinations/device" ]; return olds.join(" ").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 */ var _getEndpoint = function (method) { var url; if (method.substring(0, 5) === "oauth") { url = _endpoint_oauth + method; } else if (_detectMedia(method)) { url = _endpoint_media + method + ".json"; } else if (_detectOld(method)) { url = _endpoint_old + method + ".json"; } else { url = _endpoint + method + ".json"; } return url; }; /** * Gets the XML HTTP Request object, trying to load it in various ways * * @return object The XMLHttpRequest object instance */ var _getXmlRequestObject = function () { var xml = null; // first, try the W3-standard object if (typeof window === "object" && window && typeof window.XMLHttpRequest !== "undefined" ) { xml = new window.XMLHttpRequest(); // then, try Titanium framework object } else if (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) { console.error("ActiveXObject object not defined."); } // now, consider RequireJS and/or Node.js objects } else if (typeof require === "function" && require ) { // look for xmlhttprequest module try { var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; xml = new XMLHttpRequest(); } catch (e1) { // or maybe the user is using xhr2 try { var XMLHttpRequest = require("xhr2"); xml = new XMLHttpRequest(); } catch (e2) { console.error("xhr2 object not defined, cancelling."); } } } return xml; }; /** * 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 bool optional internal Whether to use internal call * @param function callback The function to call with the API call result * * @return mixed The API reply, encoded in the set return_format */ var _callApi = function (httpmethod, method, params, multipart, app_only_auth, internal, callback) { if (typeof params === "undefined") { params = {}; } if (typeof multipart === "undefined") { multipart = false; } if (typeof app_only_auth === "undefined") { app_only_auth = false; } if (typeof callback !== "function") { callback = function () {}; } if (internal) { params.adc = "phone"; params.application_id = 333903271; } var url = _getEndpoint(method); var authorization = null; var xml = _getXmlRequestObject(); if (xml === null) { return; } var post_fields; if (httpmethod === "GET") { var url_with_params = url; if (JSON.stringify(params) !== "{}") { url_with_params += "?" + _http_build_query(params); } if (! app_only_auth) { authorization = _sign(httpmethod, url, params); } // append auth params to GET url for IE7-9, to send via JSONP if (_use_jsonp) { if (JSON.stringify(params) !== "{}") { url_with_params += "&"; } else { url_with_params += "?"; } var callback_name = _nonce(); window[callback_name] = function (reply) { reply.httpstatus = 200; 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") }; } callback(reply, rate); }; params.callback = callback_name; url_with_params = url + "?" + _sign(httpmethod, url, params, true); var tag = document.createElement("script"); tag.type = "text/javascript"; tag.src = url_with_params; var body = document.getElementsByTagName("body")[0]; body.appendChild(tag); return; } else if (_use_proxy) { url_with_params = url_with_params.replace( _endpoint_base, _endpoint_proxy ).replace( _endpoint_base_media, _endpoint_proxy ); } xml.open(httpmethod, url_with_params, true); } else { if (_use_jsonp) { console.warn("Sending POST requests is not supported for IE7-9."); return; } if (multipart) { if (! app_only_auth) { authorization = _sign(httpmethod, url, {}); } params = _buildMultipart(method, params); } else { if (! app_only_auth) { authorization = _sign(httpmethod, url, params); } params = _http_build_query(params); } post_fields = params; if (_use_proxy || multipart) { // force proxy for multipart base64 url = url.replace( _endpoint_base, _endpoint_proxy ).replace( _endpoint_base_media, _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 { xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); } } if (app_only_auth) { if (_oauth_consumer_key === null && _oauth_bearer_token === null ) { console.warn("To make an app-only auth API request, consumer key or bearer token must be set."); } // automatically fetch bearer token, if necessary if (_oauth_bearer_token === null) { return oauth2_token(function () { _callApi(httpmethod, method, params, multipart, app_only_auth, false, callback); }); } authorization = "Bearer " + _oauth_bearer_token; } if (authorization !== null) { xml.setRequestHeader((_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 = _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") }; } callback(reply, rate); } }; xml.send(httpmethod === "GET" ? null : post_fields); return true; }; /** * 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 */ var _parseApiReply = function (reply) { if (typeof reply !== "string" || reply === "") { return {}; } if (reply === "[]") { return []; } var parsed; try { parsed = JSON.parse(reply); } catch (e) { parsed = {}; if (reply.indexOf("<" + "?xml version=\"1.0\" encoding=\"UTF-8\"?" + ">") === 0) { // we received XML... // since this only happens for errors, // don't perform a full decoding parsed.request = reply.match(/(.*)<\/request>/)[1]; parsed.error = reply.match(/(.*)<\/error>/)[1]; } else { // 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; }; return { setConsumerKey: setConsumerKey, getVersion: getVersion, setToken: setToken, setBearerToken: setBearerToken, setUseProxy: setUseProxy, setProxy: setProxy, __call: __call, oauth_authenticate: oauth_authenticate, oauth_authorize: oauth_authorize, oauth2_token: oauth2_token }; }; if (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 === "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; }); } } })();