1/** 2 * A Twitter library in JavaScript 3 * 4 * @package codebird 5 * @version 2.5.0 6 * @author Jublo Solutions <[email protected]> 7 * @copyright 2010-2014 Jublo Solutions <[email protected]> 8 * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 9 * @link https://github.com/jublonet/codebird-php 10 */ 11 12/* jshint curly: true, 13 eqeqeq: true, 14 latedef: true, 15 quotmark: double, 16 undef: true, 17 unused: true, 18 trailing: true, 19 laxbreak: true */ 20/* global window, 21 document, 22 navigator, 23 console, 24 Ti, 25 ActiveXObject, 26 module, 27 define, 28 require */ 29(function (undefined) { 30"use strict"; 31 32/** 33 * Array.indexOf polyfill 34 */ 35if (! Array.prototype.indexOf) { 36 Array.prototype.indexOf = function (obj, start) { 37 for (var i = (start || 0); i < this.length; i++) { 38 if (this[i] === obj) { 39 return i; 40 } 41 } 42 return -1; 43 }; 44} 45 46/** 47 * A Twitter library in JavaScript 48 * 49 * @package codebird 50 * @subpackage codebird-js 51 */ 52/* jshint -W098 */ 53var Codebird = function () { 54/* jshint +W098 */ 55 56 /** 57 * The OAuth consumer key of your registered app 58 */ 59 var _oauth_consumer_key = null; 60 61 /** 62 * The corresponding consumer secret 63 */ 64 var _oauth_consumer_secret = null; 65 66 /** 67 * The app-only bearer token. Used to authorize app-only requests 68 */ 69 var _oauth_bearer_token = null; 70 71 /** 72 * The API endpoint base to use 73 */ 74 var _endpoint_base = "https://api.twitter.com/"; 75 76 /** 77 * The media API endpoint base to use 78 */ 79 var _endpoint_base_media = "https://upload.twitter.com/"; 80 81 /** 82 * The API endpoint to use 83 */ 84 var _endpoint = _endpoint_base + "1.1/"; 85 86 /** 87 * The media API endpoint to use 88 */ 89 var _endpoint_media = _endpoint_base_media + "1.1/"; 90 91 /** 92 * The API endpoint base to use 93 */ 94 var _endpoint_oauth = _endpoint_base; 95 96 /** 97 * API proxy endpoint 98 */ 99 var _endpoint_proxy = "https://api.jublo.net/codebird/"; 100 101 /** 102 * The API endpoint to use for old requests 103 */ 104 var _endpoint_old = _endpoint_base + "1/"; 105 106 /** 107 * Use JSONP for GET requests in IE7-9 108 */ 109 var _use_jsonp = (typeof navigator !== "undefined" 110 && typeof navigator.userAgent !== "undefined" 111 && (navigator.userAgent.indexOf("Trident/4") > -1 112 || navigator.userAgent.indexOf("Trident/5") > -1 113 || navigator.userAgent.indexOf("MSIE 7.0") > -1 114 ) 115 ); 116 117 /** 118 * Whether to access the API via a proxy that is allowed by CORS 119 * Assume that CORS is only necessary in browsers 120 */ 121 var _use_proxy = (typeof navigator !== "undefined" 122 && typeof navigator.userAgent !== "undefined" 123 ); 124 125 /** 126 * The Request or access token. Used to sign requests 127 */ 128 var _oauth_token = null; 129 130 /** 131 * The corresponding request or access token secret 132 */ 133 var _oauth_token_secret = null; 134 135 /** 136 * The current Codebird version 137 */ 138 var _version = "2.5.0"; 139 140 /** 141 * Sets the OAuth consumer key and secret (App key) 142 * 143 * @param string key OAuth consumer key 144 * @param string secret OAuth consumer secret 145 * 146 * @return void 147 */ 148 var setConsumerKey = function (key, secret) { 149 _oauth_consumer_key = key; 150 _oauth_consumer_secret = secret; 151 }; 152 153 /** 154 * Sets the OAuth2 app-only auth bearer token 155 * 156 * @param string token OAuth2 bearer token 157 * 158 * @return void 159 */ 160 var setBearerToken = function (token) { 161 _oauth_bearer_token = token; 162 }; 163 164 /** 165 * Gets the current Codebird version 166 * 167 * @return string The version number 168 */ 169 var getVersion = function () { 170 return _version; 171 }; 172 173 /** 174 * Sets the OAuth request or access token and secret (User key) 175 * 176 * @param string token OAuth request or access token 177 * @param string secret OAuth request or access token secret 178 * 179 * @return void 180 */ 181 var setToken = function (token, secret) { 182 _oauth_token = token; 183 _oauth_token_secret = secret;
184 }; 185 186 /** 187 * Enables or disables CORS proxy 188 * 189 * @param bool use_proxy Whether to use CORS proxy or not 190 * 191 * @return void 192 */ 193 var setUseProxy = function (use_proxy) { 194 _use_proxy = !! use_proxy; 195 }; 196 197 /** 198 * Sets custom CORS proxy server 199 * 200 * @param string proxy Address of proxy server to use 201 * 202 * @return void 203 */ 204 var setProxy = function (proxy) { 205 // add trailing slash if missing 206 if (! proxy.match(/\/$/)) { 207 proxy += "/"; 208 } 209 _endpoint_proxy = proxy; 210 }; 211 212 /** 213 * Parse URL-style parameters into object 214 * 215 * version: 1109.2015 216 * discuss at: http://phpjs.org/functions/parse_str 217 * + original by: Cagri Ekin 218 * + improved by: Michael White (http://getsprink.com) 219 * + tweaked by: Jack 220 * + bugfixed by: Onno Marsman 221 * + reimplemented by: stag019 222 * + bugfixed by: Brett Zamir (http://brett-zamir.me) 223 * + bugfixed by: stag019 224 * - depends on: urldecode 225 * + input by: Dreamer 226 * + bugfixed by: Brett Zamir (http://brett-zamir.me) 227 * % note 1: When no argument is specified, will put variables in global scope. 228 * 229 * @param string str String to parse 230 * @param array array to load data into 231 * 232 * @return object 233 */ 234 var _parse_str = function (str, array) { 235 var glue1 = "=", 236 glue2 = "&", 237 array2 = String(str).replace(/^&?([\s\S]*?)&?$/, "$1").split(glue2), 238 i, j, chr, tmp, key, value, bracket, keys, evalStr, 239 fixStr = function (str) { 240 return decodeURIComponent(str).replace(/([\\"'])/g, "\\$1").replace(/\n/g, "\\n").replace(/\r/g, "\\r"); 241 }; 242 if (! array) { 243 array = this.window; 244 } 245 246 for (i = 0; i < array2.length; i++) { 247 tmp = array2[i].split(glue1); 248 if (tmp.length < 2) { 249 tmp = [tmp, ""]; 250 } 251 key = fixStr(tmp[0]); 252 value = fixStr(tmp[1]); 253 while (key.charAt(0) === " ") { 254 key = key.substr(1); 255 } 256 if (key.indexOf("\0") !== -1) { 257 key = key.substr(0, key.indexOf("\0")); 258 } 259 if (key && key.charAt(0) !== "[") { 260 keys = []; 261 bracket = 0; 262 for (j = 0; j < key.length; j++) { 263 if (key.charAt(j) === "[" && !bracket) { 264 bracket = j + 1; 265 } else if (key.charAt(j) === "]") { 266 if (bracket) { 267 if (!keys.length) { 268 keys.push(key.substr(0, bracket - 1)); 269 } 270 keys.push(key.substr(bracket, j - bracket)); 271 bracket = 0; 272 if (key.charAt(j + 1) !== "[") { 273 break; 274 } 275 } 276 } 277 } 278 if (!keys.length) { 279 keys = [key]; 280 } 281 for (j = 0; j < keys[0].length; j++) { 282 chr = keys[0].charAt(j); 283 if (chr === " " || chr === "." || chr === "[") { 284 keys[0] = keys[0].substr(0, j) + "_" + keys[0].substr(j + 1); 285 } 286 if (chr === "[") { 287 break; 288 } 289 } 290 /* jshint -W061 */ 291 evalStr = "array"; 292 for (j = 0; j < keys.length; j++) { 293 key = keys[j]; 294 if ((key !== "" && key !== " ") || j === 0) { 295 key = "'" + key + "'"; 296 } else { 297 key = eval(evalStr + ".push([]);") - 1; 298 } 299 evalStr += "[" + key + "]"; 300 if (j !== keys.length - 1 && eval("typeof " + evalStr) === "undefined") { 301 eval(evalStr + " = [];"); 302 } 303 } 304 evalStr += " = '" + value + "';\n"; 305 eval(evalStr); 306 /* jshint +W061 */ 307 } 308 } 309 }; 310 311 /** 312 * Main API handler working on any requests you issue 313 * 314 * @param string fn The member function you called 315 * @param array params The parameters you sent along 316 * @param function callback The callback to call with the reply 317 * @param bool app_only_auth Whether to use app-only auth 318 * 319 * @return mixed The API reply encoded in the set return_format 320 */ 321 322 var __call = function (fn, params, callback, app_only_auth) { 323 if (typeof params === "undefined") { 324 params = {}; 325 } 326 if (typeof app_only_auth === "undefined") { 327 app_only_auth = false;
328 } 329 if (typeof callback !== "function" && typeof params === "function") { 330 callback = params; 331 params = {}; 332 if (typeof callback === "boolean") { 333 app_only_auth = callback; 334 } 335 } else if (typeof callback === "undefined") { 336 callback = function () {}; 337 } 338 switch (fn) { 339 case "oauth_authenticate": 340 case "oauth_authorize": 341 return this[fn](params, callback); 342 343 case "oauth2_token": 344 return this[fn](callback); 345 } 346 // reset token when requesting a new token (causes 401 for signature error on 2nd+ requests) 347 if (fn === "oauth_requestToken") { 348 setToken(null, null); 349 } 350 // parse parameters 351 var apiparams = {}; 352 if (typeof params === "object") { 353 apiparams = params; 354 } else { 355 _parse_str(params, apiparams); //TODO 356 } 357 358 // map function name to API method 359 var method = ""; 360 var param, i, j; 361 362 // replace _ by / 363 var path = fn.split("_"); 364 for (i = 0; i < path.length; i++) { 365 if (i > 0) { 366 method += "/"; 367 } 368 method += path[i]; 369 } 370 371 // undo replacement for URL parameters 372 var url_parameters_with_underscore = ["screen_name", "place_id"]; 373 for (i = 0; i < url_parameters_with_underscore.length; i++) { 374 param = url_parameters_with_underscore[i].toUpperCase(); 375 var replacement_was = param.split("_").join("/"); 376 method = method.split(replacement_was).join(param); 377 } 378 379 // replace AA by URL parameters 380 var method_template = method; 381 var match = method.match(/[A-Z_]{2,}/); 382 if (match) { 383 for (i = 0; i < match.length; i++) { 384 param = match[i]; 385 var param_l = param.toLowerCase(); 386 method_template = method_template.split(param).join(":" + param_l); 387 if (typeof apiparams[param_l] === "undefined") { 388 for (j = 0; j < 26; j++) { 389 method_template = method_template.split(String.fromCharCode(65 + j)).join("_" + String.fromCharCode(97 + j)); 390 } 391 console.warn("To call the templated method \"" + method_template + "\", specify the parameter value for \"" + param_l + "\"."); 392 } 393 method = method.split(param).join(apiparams[param_l]); 394 delete apiparams[param_l]; 395 } 396 } 397 398 // replace A-Z by _a-z 399 for (i = 0; i < 26; i++) { 400 method = method.split(String.fromCharCode(65 + i)).join("_" + String.fromCharCode(97 + i)); 401 method_template = method_template.split(String.fromCharCode(65 + i)).join("_" + String.fromCharCode(97 + i)); 402 } 403 404 var httpmethod = _detectMethod(method_template, apiparams); 405 var multipart = _detectMultipart(method_template); 406 var internal = _detectInternal(method_template); 407 408 return _callApi( 409 httpmethod, 410 method, 411 apiparams, 412 multipart, 413 app_only_auth, 414 internal, 415 callback 416 ); 417 }; 418 419 /** 420 * Gets the OAuth authenticate URL for the current request token 421 * 422 * @return string The OAuth authenticate URL 423 */ 424 var oauth_authenticate = function (params, callback) { 425 if (typeof params.force_login === "undefined") { 426 params.force_login = null; 427 } 428 if (typeof params.screen_name === "undefined") { 429 params.screen_name = null; 430 } 431 if (_oauth_token === null) { 432 console.warn("To get the authenticate URL, the OAuth token must be set."); 433 } 434 var url = _endpoint_oauth + "oauth/authenticate?oauth_token=" + _url(_oauth_token); 435 if (params.force_login === true) { 436 url += "?force_login=1"; 437 if (params.screen_name !== null) { 438 url += "&screen_name=" + params.screen_name; 439 } 440 } 441 callback(url); 442 return true; 443 }; 444 445 /** 446 * Gets the OAuth authorize URL for the current request token 447 * 448 * @return string The OAuth authorize URL 449 */ 450 var oauth_authorize = function (params, callback) { 451 if (typeof params.force_login === "undefined") { 452 params.force_login = null; 453 } 454 if (typeof params.screen_name === "undefined") { 455 params.screen_name = null; 456 } 457 if (_oauth_token === null) { 458 console.warn("To get the authorize URL, the OAuth token must be set."); 459 } 460 var url = _endpoint_oauth + "oauth/authorize?oauth_token=" + _url(_oauth_token); 461 if (params.force_login === true) { 462 url += "?force_login=1"; 463 if (params.screen_name !== null) { 464 url += "&screen_name=" + params.screen_name; 465 } 466 } 467 callback(url); 468 return true; 469 }; 470 471 /** 472 * Gets the OAuth bearer token 473 * 474 * @return string The OAuth bearer token 475 */ 476 477 var oauth2_token = function (callback) { 478 if (_oauth_consumer_key === null) { 479 console.warn("To obtain a bearer token, the consumer key must be set."); 480 } 481 482 if (typeof callback === "undefined") { 483 callback = function () {}; 484 } 485 486 var post_fields = "grant_type=client_credentials"; 487 var url = _endpoint_oauth + "oauth2/token"; 488 489 if (_use_proxy) { 490 url = url.replace( 491 _endpoint_base, 492 _endpoint_proxy 493 ); 494 } 495 496 var xml = _getXmlRequestObject(); 497 if (xml === null) { 498 return; 499 } 500 xml.open("POST", url, true); 501 xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); 502 xml.setRequestHeader( 503 (_use_proxy ? "X-" : "") + "Authorization", 504 "Basic " + _base64_encode(_oauth_consumer_key + ":" + _oauth_consumer_secret) 505 ); 506 507 xml.onreadystatechange = function () { 508 if (xml.readyState >= 4) { 509 var httpstatus = 12027; 510 try { 511 httpstatus = xml.status; 512 } catch (e) {} 513 var response = ""; 514 try { 515 response = xml.responseText; 516 } catch (e) {} 517 var reply = _parseApiReply(response); 518 reply.httpstatus = httpstatus; 519 if (httpstatus === 200) { 520 setBearerToken(reply.access_token); 521 } 522 callback(reply); 523 } 524 }; 525 xml.send(post_fields); 526 527 }; 528 529 /** 530 * Signing helpers 531 */ 532 533 /** 534 * URL-encodes the given data 535 * 536 * @param mixed data 537 * 538 * @return mixed The encoded data 539 */ 540 var _url = function (data) { 541 if ((/boolean|number|string/).test(typeof data)) { 542 return encodeURIComponent(data).replace(/!/g, "%21").replace(/'/g, "%27").replace(/\(/g, "%28").replace(/\)/g, "%29").replace(/\*/g, "%2A"); 543 } else { 544 return ""; 545 } 546 }; 547 548 /** 549 * Gets the base64-encoded SHA1 hash for the given data 550 * 551 * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined 552 * in FIPS PUB 180-1 553 * Based on version 2.1 Copyright Paul Johnston 2000 - 2002. 554 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet 555 * Distributed under the BSD License 556 * See http://pajhome.org.uk/crypt/md5 for details. 557 * 558 * @param string data The data to calculate the hash from 559 * 560 * @return string The hash 561 */ 562 var _sha1 = function () { 563 function n(e, b) { 564 e[b >> 5] |= 128 << 24 - b % 32; 565 e[(b + 64 >> 9 << 4) + 15] = b; 566 for (var c = new Array(80), a = 1732584193, d = -271733879, h = -1732584194, 567 k = 271733878, g = -1009589776, p = 0; p < e.length; p += 16) { 568 for (var o = a, q = d, r = h, s = k, t = g, f = 0; 80 > f; f++) { 569 var m; 570 571 if (f < 16) { 572 m = e[p + f]; 573 } else { 574 m = c[f - 3] ^ c[f - 8] ^ c[f - 14] ^ c[f - 16]; 575 m = m << 1 | m >>> 31; 576 } 577 578 c[f] = m; 579 m = l(l(a << 5 | a >>> 27, 20 > f ? d & h | ~d & k : 40 > f ? d ^ 580 h ^ k : 60 > f ? d & h | d & k | h & k : d ^ h ^ k), l( 581 l(g, c[f]), 20 > f ? 1518500249 : 40 > f ? 1859775393 : 582 60 > f ? -1894007588 : -899497514)); 583 g = k; 584 k = h; 585 h = d << 30 | d >>> 2; 586 d = a; 587 a = m; 588 } 589 a = l(a, o); 590 d = l(d, q); 591 h = l(h, r); 592 k = l(k, s); 593 g = l(g, t); 594 } 595 return [a, d, h, k, g]; 596 } 597 598 function l(e, b) { 599 var c = (e & 65535) + (b & 65535); 600 return (e >> 16) + (b >> 16) + (c >> 16) << 16 | c & 65535; 601 } 602 603 function q(e) { 604 for (var b = [], c = (1 << g) - 1, a = 0; a < e.length * g; a += g) { 605 b[a >> 5] |= (e.charCodeAt(a / g) & c) << 24 - a % 32; 606 } 607 return b; 608 } 609 var g = 8; 610 return function (e) { 611 var b = _oauth_consumer_secret + "&" + (null !== _oauth_token_secret ? 612 _oauth_token_secret : ""); 613 if (_oauth_consumer_secret === null) { 614 console.warn("To generate a hash, the consumer secret must be set."); 615 } 616 var c = q(b); 617 if (c.length > 16) { 618 c = n(c, b.length * g); 619 } 620 b = new Array(16); 621 for (var a = new Array(16), d = 0; d < 16; d++) { 622 a[d] = c[d] ^ 909522486; 623 b[d] = c[d] ^ 1549556828; 624 } 625 c = n(a.concat(q(e)), 512 + e.length * g); 626 b = n(b.concat(c), 672); 627 c = ""; 628 for (a = 0; a < 4 * b.length; a += 3) { 629 for (d = (b[a >> 2] >> 8 * (3 - a % 4) & 255) << 16 | (b[a + 1 >> 2] >> 630 8 * (3 - (a + 1) % 4) & 255) << 8 | b[a + 2 >> 2] >> 8 * (3 - 631 (a + 2) % 4) & 255, e = 0; 4 > e; e++) { 632 c = 8 * a + 6 * e > 32 * b.length ? c + "=" : c + 633 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
633+/" 634 .charAt(d >> 6 * (3 - e) & 63); 635 } 636 } 637 return c; 638 }; 639 }(); 640 641 /* 642 * Gets the base64 representation for the given data 643 * 644 * http://phpjs.org 645 * + original by: Tyler Akins (http://rumkin.com) 646 * + improved by: Bayron Guevara 647 * + improved by: Thunder.m 648 * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) 649 * + bugfixed by: Pellentesque Malesuada 650 * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) 651 * + improved by: Rafał Kukawski (http://kukawski.pl) 652 * 653 * @param string data The data to calculate the base64 representation from 654 * 655 * @return string The base64 representation 656 */ 657 var _base64_encode = function (a) { 658 var d, e, f, b, g = 0, 659 h = 0, 660 i = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", 661 c = []; 662 if (!a) { 663 return a; 664 } 665 do { 666 d = a.charCodeAt(g++); 667 e = a.charCodeAt(g++); 668 f = a.charCodeAt(g++); 669 b = d << 16 | e << 8 | f; 670 d = b >> 18 & 63; 671 e = b >> 12 & 63; 672 f = b >> 6 & 63; 673 b &= 63; 674 c[h++] = i.charAt(d) + i.charAt(e) + i.charAt(f) + i.charAt(b); 675 } while (g < a.length); 676 c = c.join(""); 677 a = a.length % 3; 678 return (a ? c.slice(0, a - 3) : c) + "===".slice(a || 3); 679 }; 680 681 /* 682 * Builds a HTTP query string from the given data 683 * 684 * http://phpjs.org 685 * + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) 686 * + improved by: Legaev Andrey 687 * + improved by: Michael White (http://getsprink.com) 688 * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) 689 * + improved by: Brett Zamir (http://brett-zamir.me) 690 * + revised by: stag019 691 * + input by: Dreamer 692 * + bugfixed by: Brett Zamir (http://brett-zamir.me) 693 * + bugfixed by: MIO_KODUKI (http://mio-koduki.blogspot.com/) 694 * 695 * @param string data The data to concatenate 696 * 697 * @return string The HTTP query 698 */ 699 var _http_build_query = function (e, f, b) { 700 function g(c, a, d) { 701 var b, e = []; 702 if (a === true) { 703 a = "1"; 704 } else if (a === false) { 705 a = "0"; 706 } 707 if (null !== a) { 708 if (typeof a === "object") { 709 for (b in a) { 710 if (a[b] !== null) { 711 e.push(g(c + "[" + b + "]", a[b], d)); 712 } 713 } 714 return e.join(d); 715 } 716 if (typeof a !== "function") { 717 return _url(c) + "=" + _url(a); 718 } 719 console.warn("There was an error processing for http_build_query()."); 720 } else { 721 return ""; 722 } 723 } 724 var d, c, h = []; 725 if (! b) { 726 b = "&"; 727 } 728 for (c in e) { 729 d = e[c]; 730 if (f && ! isNaN(c)) { 731 c = String(f) + c; 732 } 733 d = g(c, d, b); 734 if (d !== "") { 735 h.push(d); 736 } 737 } 738 return h.join(b); 739 }; 740 741 /** 742 * Generates a (hopefully) unique random string 743 * 744 * @param int optional length The length of the string to generate 745 * 746 * @return string The random string 747 */ 748 var _nonce = function (length) { 749 if (typeof length === "undefined") { 750 length = 8; 751 } 752 if (length < 1) { 753 console.warn("Invalid nonce length."); 754 } 755 var nonce = ""; 756 for (var i = 0; i < length; i++) { 757 var character = Math.floor(Math.random() * 61); 758 nonce += "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz".substring(character, character + 1); 759 } 760 return nonce; 761 }; 762 763 /** 764 * Sort array elements by key 765 * 766 * @param array input_arr The array to sort 767 * 768 * @return array The sorted keys 769 */ 770 var _ksort = function (input_arr) { 771 var keys = [], sorter, k; 772 773 sorter = function (a, b) { 774 var a_float = parseFloat(a), 775 b_float = parseFloat(b), 776 a_numeric = a_float + "" === a, 777 b_numeric = b_float + "" === b; 778 if (a_numeric && b_numeric) { 779 return a_float > b_float ? 1 : a_float < b_float ? -1 : 0; 780 } else if (a_numeric && !b_numeric) { 781 return 1; 782 } else if (!a_numeric && b_numeric) { 783 return -1; 784 } 785 return a > b ? 1 : a < b ? -1 : 0; 786 }; 787 788 // Make a list of key names 789 for (k in input_arr) { 790 if (input_arr.hasOwnProperty(k)) { 791 keys.push(k); 792 } 793 } 794 keys.sort(sorter); 795 return keys; 796 }; 797 798 /** 799 * Clone objects 800 * 801 * @param object obj The object to clone 802 * 803 * @return object clone The cloned object 804 */ 805 var _clone = function (obj) { 806 var clone = {}; 807 for (var i in obj) { 808 if (typeof(obj[i]) === "object") { 809 clone[i] = _clone(obj[i]); 810 } else { 811 clone[i] = obj[i]; 812 } 813 } 814 return clone; 815 }; 816 817 /** 818 * Generates an OAuth signature 819 * 820 * @param string httpmethod Usually either 'GET' or 'POST' or 'DELETE' 821 * @param string method The API method to call 822 * @param array optional params The API call parameters, associative 823 * @param bool optional append_to_get Whether to append the OAuth params to GET 824 * 825 * @return string Authorization HTTP header 826 */ 827 var _sign = function (httpmethod, method, params, append_to_get) { 828 if (typeof params === "undefined") { 829 params = {}; 830 } 831 if (typeof append_to_get === "undefined") { 832 append_to_get = false;
833 } 834 if (_oauth_consumer_key === null) { 835 console.warn("To generate a signature, the consumer key must be set."); 836 } 837 var sign_params = { 838 consumer_key: _oauth_consumer_key, 839 version: "1.0", 840 timestamp: Math.round(new Date().getTime() / 1000), 841 nonce: _nonce(), 842 signature_method: "HMAC-SHA1" 843 }; 844 var sign_base_params = {}; 845 var value; 846 for (var key in sign_params) { 847 value = sign_params[key]; 848 sign_base_params["oauth_" + key] = _url(value); 849 } 850 if (_oauth_token !== null) { 851 sign_base_params.oauth_token = _url(_oauth_token); 852 } 853 var oauth_params = _clone(sign_base_params); 854 for (key in params) { 855 value = params[key]; 856 sign_base_params[key] = value; 857 } 858 var keys = _ksort(sign_base_params); 859 var sign_base_string = ""; 860 for (var i = 0; i < keys.length; i++) { 861 key = keys[i]; 862 value = sign_base_params[key]; 863 sign_base_string += key + "=" + _url(value) + "&"; 864 } 865 sign_base_string = sign_base_string.substring(0, sign_base_string.length - 1); 866 var signature = _sha1(httpmethod + "&" + _url(method) + "&" + _url(sign_base_string)); 867 868 params = append_to_get ? sign_base_params : oauth_params; 869 params.oauth_signature = signature; 870 keys = _ksort(params); 871 var authorization = ""; 872 if (append_to_get) { 873 for(i = 0; i < keys.length; i++) { 874 key = keys[i]; 875 value = params[key]; 876 authorization += key + "=" + _url(value) + "&"; 877 } 878 return authorization.substring(0, authorization.length - 1); 879 } 880 authorization = "OAuth "; 881 for (i = 0; i < keys.length; i++) { 882 key = keys[i]; 883 value = params[key]; 884 authorization += key + "=\"" + _url(value) + "\", "; 885 } 886 return authorization.substring(0, authorization.length - 2); 887 }; 888 889 /** 890 * Detects HTTP method to use for API call 891 * 892 * @param string method The API method to call 893 * @param array params The parameters to send along 894 * 895 * @return string The HTTP method that should be used 896 */ 897 var _detectMethod = function (method, params) { 898 // multi-HTTP method endpoints 899 switch (method) { 900 case "account/settings": 901 case "account/login_verification_enrollment": 902 case "account/login_verification_request": 903 method = params.length ? method + "__post" : method; 904 break; 905 } 906 907 var httpmethods = {}; 908 httpmethods.GET = [ 909 // Timelines 910 "statuses/mentions_timeline", 911 "statuses/user_timeline", 912 "statuses/home_timeline", 913 "statuses/retweets_of_me", 914 915 // Tweets 916 "statuses/retweets/:id", 917 "statuses/show/:id", 918 "statuses/oembed", 919 "statuses/retweeters/ids", 920 921 // Search 922 "search/tweets", 923 924 // Direct Messages 925 "direct_messages", 926 "direct_messages/sent", 927 "direct_messages/show", 928 929 // Friends & Followers 930 "friendships/no_retweets/ids", 931 "friends/ids", 932 "followers/ids", 933 "friendships/lookup", 934 "friendships/incoming", 935 "friendships/outgoing", 936 "friendships/show", 937 "friends/list", 938 "followers/list", 939 "friendships/lookup", 940 941 // Users 942 "account/settings", 943 "account/verify_credentials", 944 "blocks/list", 945 "blocks/ids", 946 "users/lookup", 947 "users/show", 948 "users/search", 949 "users/contributees", 950 "users/contributors", 951 "users/profile_banner", 952 "mutes/users/ids", 953 "mutes/users/list", 954 955 // Suggested Users 956 "users/suggestions/:slug", 957 "users/suggestions", 958 "users/suggestions/:slug/members", 959 960 // Favorites 961 "favorites/list", 962 963 // Lists 964 "lists/list", 965 "lists/statuses", 966 "lists/memberships", 967 "lists/subscribers", 968 "lists/subscribers/show", 969 "lists/members/show", 970 "lists/members", 971 "lists/show", 972 "lists/subscriptions", 973 "lists/ownerships", 974 975 // Saved searches 976 "saved_searches/list", 977 "saved_searches/show/:id", 978 979 // Places & Geo 980 "geo/id/:place_id", 981 "geo/reverse_geocode", 982 "geo/search", 983 "geo/similar_places", 984 985 // Trends 986 "trends/place", 987 "trends/available", 988 "trends/closest", 989 990 // OAuth 991 "oauth/authenticate", 992 "oauth/authorize", 993 994 // Help 995 "help/configuration", 996 "help/languages", 997 "help/privacy", 998 "help/tos", 999 "application/rate_limit_status", 1000 1001 // Tweets 1002 "statuses/lookup", 1003 1004 // Internal 1005 "users/recommendations", 1006 "account/push_destinations/device", 1007 "activity/about_me", 1008 "activity/by_friends", 1009 "statuses/media_timeline", 1010 "timeline/home", 1011 "help/experiments", 1012 "search/typeahead", 1013 "search/universal", 1014 "discover/universal", 1015 "conversation/show", 1016 "statuses/:id/activity/summary", 1017 "account/login_verification_enrollment", 1018 "account/login_verification_request", 1019 "prompts/suggest", 1020 1021 "beta/timelines/custom/list", 1022 "beta/timelines/timeline", 1023 "beta/timelines/custom/show" 1024 ]; 1025 httpmethods.POST = [ 1026 // Tweets 1027 "statuses/destroy/:id", 1028 "statuses/update", 1029 "statuses/retweet/:id", 1030 "statuses/update_with_media", 1031 "media/upload", 1032 1033 // Direct Messages 1034 "direct_messages/destroy", 1035 "direct_messages/new", 1036 1037 // Friends & Followers 1038 "friendships/create", 1039 "friendships/destroy", 1040 "friendships/update", 1041 1042 // Users 1043 "account/settings__post", 1044 "account/update_delivery_device", 1045 "account/update_profile", 1046 "account/update_profile_background_image", 1047 "account/update_profile_colors", 1048 "account/update_profile_image", 1049 "blocks/create", 1050 "blocks/destroy", 1051 "account/update_profile_banner", 1052 "account/remove_profile_banner", 1053 "mutes/users/create", 1054 "mutes/users/destroy", 1055 1056 // Favorites 1057 "favorites/destroy", 1058 "favorites/create", 1059 1060 // Lists 1061 "lists/members/destroy", 1062 "lists/subscribers/create", 1063 "lists/subscribers/destroy", 1064 "lists/members/create_all", 1065 "lists/members/create", 1066 "lists/destroy", 1067 "lists/update", 1068 "lists/create", 1069 "lists/members/destroy_all", 1070 1071 // Saved Searches 1072 "saved_searches/create", 1073 "saved_searches/destroy/:id", 1074 1075 // Spam Reporting 1076 "users/report_spam", 1077 1078 // OAuth 1079 "oauth/access_token", 1080 "oauth/request_token", 1081 "oauth2/token", 1082 "oauth2/invalidate_token", 1083 1084 // Internal 1085 "direct_messages/read", 1086 "account/login_verification_enrollment__post", 1087 "push_destinations/enable_login_verification", 1088 "account/login_verification_request__post", 1089 1090 "beta/timelines/custom/create", 1091 "beta/timelines/custom/update", 1092 "beta/timelines/custom/destroy", 1093 "beta/timelines/custom/add", 1094 "beta/timelines/custom/remove" 1095 ]; 1096 for (var httpmethod in httpmethods) { 1097 if (httpmethods[httpmethod].indexOf(method) > -1) { 1098 return httpmethod; 1099 } 1100 } 1101 console.warn("Can't find HTTP method to use for \"" + method + "\"."); 1102 }; 1103 1104 /** 1105 * Detects if API call should use multipart/form-data 1106 * 1107 * @param string method The API method to call 1108 * 1109 * @return bool Whether the method should be sent as multipart 1110 */ 1111 var _detectMultipart = function (method) { 1112 var multiparts = [ 1113 // Tweets 1114 "statuses/update_with_media", 1115 1116 // Users 1117 "account/update_profile_background_image", 1118 "account/update_profile_image", 1119 "account/update_profile_banner" 1120 ]; 1121 return multiparts.indexOf(method) > -1; 1122 }; 1123 1124 /** 1125 * Build multipart request from upload params 1126 * 1127 * @param string method The API method to call
1128 * @param array params The parameters to send along 1129 * 1130 * @return null|string The built multipart request body 1131 */ 1132 var _buildMultipart = function (method, params) { 1133 // well, files will only work in multipart methods 1134 if (! _detectMultipart(method)) { 1135 return; 1136 } 1137 1138 // only check specific parameters 1139 var possible_methods = [ 1140 // Tweets 1141 "statuses/update_with_media", 1142 // Accounts 1143 "account/update_profile_background_image", 1144 "account/update_profile_image", 1145 "account/update_profile_banner" 1146 ]; 1147 var possible_files = { 1148 // Tweets 1149 "statuses/update_with_media": "media[]", 1150 // Accounts 1151 "account/update_profile_background_image": "image", 1152 "account/update_profile_image": "image", 1153 "account/update_profile_banner": "banner" 1154 }; 1155 // method might have files? 1156 if (possible_methods.indexOf(method) === -1) { 1157 return; 1158 } 1159 1160 // check for filenames 1161 possible_files = possible_files[method].split(" "); 1162 1163 var multipart_border = "--------------------" + _nonce(); 1164 var multipart_request = ""; 1165 for (var key in params) { 1166 multipart_request += 1167 "--" + multipart_border + "\r\n" 1168 + "Content-Disposition: form-data; name=\"" + key + "\""; 1169 if (possible_files.indexOf(key) > -1) { 1170 multipart_request += 1171 "\r\nContent-Transfer-Encoding: base64"; 1172 } 1173 multipart_request += 1174 "\r\n\r\n" + params[key] + "\r\n"; 1175 } 1176 multipart_request += "--" + multipart_border + "--"; 1177 return multipart_request; 1178 }; 1179 1180 /** 1181 * Detects if API call is internal 1182 * 1183 * @param string method The API method to call 1184 * 1185 * @return bool Whether the method is defined in internal API 1186 */ 1187 var _detectInternal = function (method) { 1188 var internals = [ 1189 "users/recommendations" 1190 ]; 1191 return internals.join(" ").indexOf(method) > -1; 1192 }; 1193 1194 /** 1195 * Detects if API call should use media endpoint 1196 * 1197 * @param string method The API method to call 1198 * 1199 * @return bool Whether the method is defined in media API 1200 */ 1201 var _detectMedia = function (method) { 1202 var medias = [ 1203 "media/upload" 1204 ]; 1205 return medias.join(" ").indexOf(method) > -1; 1206 }; 1207 1208 /** 1209 * Detects if API call should use old endpoint 1210 * 1211 * @param string method The API method to call 1212 * 1213 * @return bool Whether the method is defined in old API 1214 */ 1215 var _detectOld = function (method) { 1216 var olds = [ 1217 "account/push_destinations/device" 1218 ]; 1219 return olds.join(" ").indexOf(method) > -1; 1220 }; 1221 1222 /** 1223 * Builds the complete API endpoint url 1224 * 1225 * @param string method The API method to call 1226 * 1227 * @return string The URL to send the request to 1228 */ 1229 var _getEndpoint = function (method) { 1230 var url; 1231 if (method.substring(0, 5) === "oauth") { 1232 url = _endpoint_oauth + method; 1233 } else if (_detectMedia(method)) { 1234 url = _endpoint_media + method + ".json"; 1235 } else if (_detectOld(method)) { 1236 url = _endpoint_old + method + ".json"; 1237 } else { 1238 url = _endpoint + method + ".json"; 1239 } 1240 return url; 1241 }; 1242 1243 /** 1244 * Gets the XML HTTP Request object, trying to load it in various ways 1245 * 1246 * @return object The XMLHttpRequest object instance 1247 */ 1248 var _getXmlRequestObject = function () { 1249 var xml = null; 1250 // first, try the W3-standard object 1251 if (typeof window === "object" 1252 && window 1253 && typeof window.XMLHttpRequest !== "undefined" 1254 ) { 1255 xml = new window.XMLHttpRequest(); 1256 // then, try Titanium framework object 1257 } else if (typeof Ti === "object" 1258 && Ti 1259 && typeof Ti.Network.createHTTPClient !== "undefined" 1260 ) { 1261 xml = Ti.Network.createHTTPClient(); 1262 // are we in an old Internet Explorer? 1263 } else if (typeof ActiveXObject !== "undefined" 1264 ) { 1265 try { 1266 xml = new ActiveXObject("Microsoft.XMLHTTP"); 1267 } catch (e) { 1268 console.error("ActiveXObject object not defined."); 1269 } 1270 // now, consider RequireJS and/or Node.js objects 1271 } else if (typeof require === "function" 1272 && require 1273 ) { 1274 // look for xmlhttprequest module 1275 try { 1276 var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; 1277 xml = new XMLHttpRequest(); 1278 } catch (e1) { 1279 // or maybe the user is using xhr2 1280 try { 1281 var XMLHttpRequest = require("xhr2"); 1282 xml = new XMLHttpRequest(); 1283 } catch (e2) { 1284 console.error("xhr2 object not defined, cancelling."); 1285 } 1286 } 1287 } 1288 return xml; 1289 }; 1290 1291 /** 1292 * Calls the API using cURL 1293 * 1294 * @param string httpmethod The HTTP method to use for making the request 1295 * @param string method The API method to call
1296 * @param array optional params The parameters to send along 1297 * @param bool optional multipart Whether to use multipart/form-data 1298 * @param bool optional app_only_auth Whether to use app-only bearer authentication 1299 * @param bool optional internal Whether to use internal call 1300 * @param function callback The function to call with the API call result 1301 * 1302 * @return mixed The API reply, encoded in the set return_format 1303 */ 1304 1305 var _callApi = function (httpmethod, method, params, multipart, app_only_auth, internal, callback) { 1306 if (typeof params === "undefined") { 1307 params = {}; 1308 } 1309 if (typeof multipart === "undefined") { 1310 multipart = false; 1311 } 1312 if (typeof app_only_auth === "undefined") { 1313 app_only_auth = false; 1314 } 1315 if (typeof callback !== "function") { 1316 callback = function () {}; 1317 } 1318 if (internal) { 1319 params.adc = "phone"; 1320 params.application_id = 333903271; 1321 } 1322 1323 var url = _getEndpoint(method); 1324 var authorization = null; 1325 1326 var xml = _getXmlRequestObject(); 1327 if (xml === null) { 1328 return; 1329 } 1330 var post_fields; 1331 1332 if (httpmethod === "GET") { 1333 var url_with_params = url; 1334 if (JSON.stringify(params) !== "{}") { 1335 url_with_params += "?" + _http_build_query(params); 1336 } 1337 if (! app_only_auth) { 1338 authorization = _sign(httpmethod, url, params); 1339 } 1340 1341 // append auth params to GET url for IE7-9, to send via JSONP 1342 if (_use_jsonp) { 1343 if (JSON.stringify(params) !== "{}") { 1344 url_with_params += "&"; 1345 } else { 1346 url_with_params += "?"; 1347 } 1348 var callback_name = _nonce(); 1349 window[callback_name] = function (reply) { 1350 reply.httpstatus = 200; 1351 1352 var rate = null; 1353 if (typeof xml.getResponseHeader !== "undefined" 1354 && xml.getResponseHeader("x-rate-limit-limit") !== "" 1355 ) { 1356 rate = { 1357 limit: xml.getResponseHeader("x-rate-limit-limit"), 1358 remaining: xml.getResponseHeader("x-rate-limit-remaining"), 1359 reset: xml.getResponseHeader("x-rate-limit-reset") 1360 }; 1361 } 1362 callback(reply, rate); 1363 }; 1364 params.callback = callback_name; 1365 url_with_params = url + "?" + _sign(httpmethod, url, params, true); 1366 var tag = document.createElement("script"); 1367 tag.type = "text/javascript"; 1368 tag.src = url_with_params; 1369 var body = document.getElementsByTagName("body")[0]; 1370 body.appendChild(tag); 1371 return; 1372 1373 } else if (_use_proxy) { 1374 url_with_params = url_with_params.replace( 1375 _endpoint_base, 1376 _endpoint_proxy 1377 ).replace( 1378 _endpoint_base_media, 1379 _endpoint_proxy 1380 ); 1381 } 1382 xml.open(httpmethod, url_with_params, true); 1383 } else { 1384 if (_use_jsonp) { 1385 console.warn("Sending POST requests is not supported for IE7-9."); 1386 return; 1387 } 1388 if (multipart) { 1389 if (! app_only_auth) { 1390 authorization = _sign(httpmethod, url, {}); 1391 } 1392 params = _buildMultipart(method, params); 1393 } else { 1394 if (! app_only_auth) { 1395 authorization = _sign(httpmethod, url, params); 1396 } 1397 params = _http_build_query(params); 1398 } 1399 post_fields = params; 1400 if (_use_proxy || multipart) { // force proxy for multipart base64 1401 url = url.replace( 1402 _endpoint_base, 1403 _endpoint_proxy 1404 ).replace( 1405 _endpoint_base_media, 1406 _endpoint_proxy 1407 ); 1408 } 1409 xml.open(httpmethod, url, true); 1410 if (multipart) { 1411 xml.setRequestHeader("Content-Type", "multipart/form-data; boundary=" 1412 + post_fields.split("\r\n")[0].substring(2)); 1413 } else { 1414 xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); 1415 } 1416 } 1417 if (app_only_auth) { 1418 if (_oauth_consumer_key === null 1419 && _oauth_bearer_token === null 1420 ) { 1421 console.warn("To make an app-only auth API request, consumer key or bearer token must be set."); 1422 } 1423 // automatically fetch bearer token, if necessary 1424 if (_oauth_bearer_token === null) { 1425 return oauth2_token(function () { 1426 _callApi(httpmethod, method, params, multipart, app_only_auth, false, callback); 1427 }); 1428 } 1429 authorization = "Bearer " + _oauth_bearer_token; 1430 } 1431 if (authorization !== null) { 1432 xml.setRequestHeader((_use_proxy ? "X-" : "") + "Authorization", authorization); 1433 } 1434 xml.onreadystatechange = function () { 1435 if (xml.readyState >= 4) { 1436 var httpstatus = 12027; 1437 try { 1438 httpstatus = xml.status; 1439 } catch (e) {} 1440 var response = ""; 1441 try { 1442 response = xml.responseText; 1443 } catch (e) {} 1444 var reply = _parseApiReply(response); 1445 reply.httpstatus = httpstatus; 1446 var rate = null; 1447 if (typeof xml.getResponseHeader !== "undefined" 1448 && xml.getResponseHeader("x-rate-limit-limit") !== "" 1449 ) { 1450 rate = { 1451 limit: xml.getResponseHeader("x-rate-limit-limit"), 1452 remaining: xml.getResponseHeader("x-rate-limit-remaining"), 1453 reset: xml.getResponseHeader("x-rate-limit-reset") 1454 }; 1455 } 1456 callback(reply, rate); 1457 } 1458 }; 1459 xml.send(httpmethod === "GET" ? null : post_fields); 1460 return true; 1461 }; 1462 1463 /** 1464 * Parses the API reply to encode it in the set return_format 1465 * 1466 * @param string reply The actual reply, JSON-encoded or URL-encoded 1467 * 1468 * @return array|object The parsed reply 1469 */ 1470 var _parseApiReply = function (reply) { 1471 if (typeof reply !== "string" || reply === "") { 1472 return {}; 1473 } 1474 if (reply === "[]") { 1475 return []; 1476 } 1477 var parsed; 1478 try { 1479 parsed = JSON.parse(reply); 1480 } catch (e) { 1481 parsed = {}; 1482 if (reply.indexOf("<" + "?xml version=\"1.0\" encoding=\"UTF-8\"?" + ">") === 0) { 1483 // we received XML... 1484 // since this only happens for errors, 1485 // don't perform a full decoding 1486 parsed.request = reply.match(/<request>(.*)<\/request>/)[1]; 1487 parsed.error = reply.match(/<error>(.*)<\/error>/)[1]; 1488 } else { 1489 // assume query format 1490 var elements = reply.split("&"); 1491 for (var i = 0; i < elements.length; i++) { 1492 var element = elements[i].split("=", 2); 1493 if (element.length > 1) { 1494 parsed[element[0]] = decodeURIComponent(element[1]); 1495 } else { 1496 parsed[element[0]] = null; 1497 } 1498 } 1499 } 1500 } 1501 return parsed; 1502 }; 1503 1504 return { 1505 setConsumerKey: setConsumerKey, 1506 getVersion: getVersion, 1507 setToken: setToken, 1508 setBearerToken: setBearerToken, 1509 setUseProxy: setUseProxy, 1510 setProxy: setProxy,
1511 __call: __call, 1512 oauth_authenticate: oauth_authenticate, 1513 oauth_authorize: oauth_authorize, 1514 oauth2_token: oauth2_token 1515 }; 1516}; 1517 1518if (typeof module === "object" 1519 && module 1520 && typeof module.exports === "object" 1521) { 1522 // Expose codebird as module.exports in loaders that implement the Node 1523 // module pattern (including browserify). Do not create the global, since 1524 // the user will be storing it themselves locally, and globals are frowned 1525 // upon in the Node module world. 1526 module.exports = Codebird; 1527} else { 1528 // Otherwise expose codebird to the global object as usual 1529 if (typeof window === "object" 1530 && window) { 1531 window.Codebird = Codebird; 1532 } 1533 1534 // Register as a named AMD module, since codebird can be concatenated with other 1535 // files that may use define, but not via a proper concatenation script that 1536 // understands anonymous AMD modules. A named AMD is safest and most robust 1537 // way to register. Lowercase codebird is used because AMD module names are 1538 // derived from file names, and codebird is normally delivered in a lowercase 1539 // file name. Do this after creating the global so that if an AMD module wants 1540 // to call noConflict to hide this version of codebird, it will work. 1541 if (typeof define === "function" && define.amd) { 1542 define("codebird", [], function () { return Codebird; }); 1543 } 1544} 1545 1546})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.