Skip to content

Instantly share code, notes, and snippets.

@viezel
Last active June 15, 2020 19:13
Show Gist options
  • Save viezel/5781083 to your computer and use it in GitHub Desktop.
Save viezel/5781083 to your computer and use it in GitHub Desktop.
Codebird for Appcelerator Titanium. Using the Twitter API 1.1
// This is an example of use.
// Here we use the new Bearer Token thats make it possible to get tweets without user login
// More info on Bearer here: https://dev.twitter.com/docs/auth/application-only-auth
// Full Codebird API is here: https://github.com/mynetx/codebird-js
var Codebird = require("codebird");
var cb = new Codebird();
cb.setConsumerKey('CONSUMER_KEY', 'CONSUMER_SECRET_KEY');
var bearerToken = Ti.App.Properties.getString('TwitterBearerToken', null);
if(bearerToken == null){
cb.__call(
'oauth2_token',
{},
function (reply) {
var bearer_token = reply.access_token;
cb.setBearerToken(bearer_token);
Ti.App.Properties.setString('TwitterBearerToken', bearer_token);
fetchTwitter();
}
);
} else {
Ti.API.info("We do have a bearer token...");
cb.setBearerToken(bearerToken);
fetchTwitter();
}
function fetchTwitter(){
cb.__call(
'search_tweets',
"q="+Ti.Network.encodeURIComponent("#awesome"),
function (reply) {
// ...
Ti.API.info(reply);
},
true // this parameter required
);
}
/**
* A Twitter library in JavaScript
* https://github.com/mynetx/codebird-js
*
* @package codebird
* @version 2.4.0-dev
* @author J.M. <me@mynetx.net>
* @copyright 2010-2013 J.M. <me@mynetx.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Array.indexOf polyfill
*/
if (! Array.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 JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
* in FIPS PUB 180-1
* 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.
*/
var hexcase = 0;
var b64pad = "";
var chrsz = 8;
function hex_sha1(s) {
return binb2hex(core_sha1(str2binb(s), s.length * chrsz));
}
function b64_sha1(s) {
return binb2b64(core_sha1(str2binb(s), s.length * chrsz));
}
function str_sha1(s) {
return binb2str(core_sha1(str2binb(s), s.length * chrsz));
}
function hex_hmac_sha1(key, data) {
return binb2hex(core_hmac_sha1(key, data));
}
function b64_hmac_sha1(key, data) {
return binb2b64(core_hmac_sha1(key, data));
}
function str_hmac_sha1(key, data) {
return binb2str(core_hmac_sha1(key, data));
}
function sha1_vm_test() {
return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d";
}
function core_sha1(x, len) {
x[len >> 5] |= 0x80 << (24 - len % 32);
x[((len + 64 >> 9) << 4) + 15] = len;
var w = Array(80);
var a = 1732584193;
var b = -271733879;
var c = -1732584194;
var d = 271733878;
var e = -1009589776;
for (var i = 0; i < x.length; i += 16) {
var olda = a;
var oldb = b;
var oldc = c;
var oldd = d;
var olde = e;
for (var j = 0; j < 80; j++) {
if (j < 16)
w[j] = x[i + j];
else
w[j] = rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1);
var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)), safe_add(safe_add(e, w[j]), sha1_kt(j)));
e = d;
d = c;
c = rol(b, 30);
b = a;
a = t;
}
a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
e = safe_add(e, olde);
}
return Array(a, b, c, d, e);
}
function sha1_ft(t, b, c, d) {
if (t < 20)
return (b & c) | ((~b) & d);
if (t < 40)
return b ^ c ^ d;
if (t < 60)
return (b & c) | (b & d) | (c & d);
return b ^ c ^ d;
}
function sha1_kt(t) {
return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : (t < 60) ? -1894007588 : -899497514;
}
function core_hmac_sha1(key, data) {
var bkey = str2binb(key);
if (bkey.length > 16)
bkey = core_sha1(bkey, key.length * chrsz);
var ipad = Array(16), opad = Array(16);
for (var i = 0; i < 16; i++) {
ipad[i] = bkey[i] ^ 0x36363636;
opad[i] = bkey[i] ^ 0x5C5C5C5C;
}
var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz);
return core_sha1(opad.concat(hash), 512 + 160);
}
function safe_add(x, y) {
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
function rol(num, cnt) {
return (num << cnt) | (num >>> (32 - cnt));
}
function str2binb(str) {
var bin = Array();
var mask = (1 << chrsz) - 1;
for (var i = 0; i < str.length * chrsz; i += chrsz)
bin[i >> 5] |= (str.charCodeAt(i / chrsz) & mask) << (24 - i % 32);
return bin;
}
function binb2str(bin) {
var str = "";
var mask = (1 << chrsz) - 1;
for (var i = 0; i < bin.length * 32; i += chrsz)
str += String.fromCharCode((bin[i >> 5] >>> (24 - i % 32)) & mask);
return str;
}
function binb2hex(binarray) {
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
var str = "";
for (var i = 0; i < binarray.length * 4; i++) {
str += hex_tab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8 + 4)) & 0xF) + hex_tab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8 )) & 0xF);
}
return str;
}
function binb2b64(binarray) {
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var str = "";
for (var i = 0; i < binarray.length * 4; i += 3) {
var triplet = (((binarray[i >> 2] >> 8 * (3 - i % 4)) & 0xFF) << 16) | (((binarray[i + 1 >> 2] >> 8 * (3 - (i + 1) % 4)) & 0xFF) << 8 ) | ((binarray[i + 2 >> 2] >> 8 * (3 - (i + 2) % 4)) & 0xFF);
for (var j = 0; j < 4; j++) {
if (i * 8 + j * 6 > binarray.length * 32)
str += b64pad;
else
str += tab.charAt((triplet >> 6 * (3 - j)) & 0x3F);
}
}
return str;
}
/**
* A Twitter library in JavaScript
*
* @package codebird
* @subpackage codebird-js
*/
var Codebird = function () {
/**
* 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 API endpoint to use
*/
var _endpoint = _endpoint_base + '1.1/';
/**
* The API endpoint to use for OAuth requests
*/
var _endpoint_oauth = _endpoint_base;
/**
* API proxy endpoint
*/
var _endpoint_proxy = 'https://api.jublo.net/codebird/';
/**
* Use JSONP for GET requests in IE7-9
*/
var _use_jsonp = false;
/**
* Whether to access the API via a proxy that is allowed by CORS
*/
var _use_proxy = false;
/**
* 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.4.0-dev';
/**
* 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;
};
/**
* Parse URL-style parameters into object
*
* @param string str String to parse
* @param array array to load data into
*
* @return object
*/
function parse_str(str, array) {
// Parses GET/POST/COOKIE data and sets global variables
//
// 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.
// * example 1: var arr = {};
// * example 1: parse_str('first=foo&second=bar', arr);
// * results 1: arr == { first: 'foo', second: 'bar' }
// * example 2: var arr = {}; // * example 2: parse_str('str_a=Jack+and+Jill+didn%27t+see+the+well.', arr);
// * results 2: arr == { str_a: "Jack and Jill didn't see the well." }
var glue1 = '=',
glue2 = '&',
array2 = String(str).replace(/^&?([\s\S]*?)&?$/, '$1').split(glue2),
i, j, chr, tmp, key, value, bracket, keys, evalStr, that = this,
fixStr = function (str) {
return unescape(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;
}
}
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);
}
}
}
/**
* 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') {
var params = {};
}
if (typeof app_only_auth == 'undefined') {
var app_only_auth = false;
}
if (typeof callback != 'function' && typeof params == 'function') {
callback = params;
params = {};
if (typeof callback == 'bool') {
app_only_auth = callback;
}
} else if (typeof callback == 'undefined') {
var callback = function (reply) {};
}
switch (fn) {
case "oauth_authenticate":
case "oauth_authorize":
return this[fn](params, callback);
break;
case "oauth2_token":
return this[fn](callback);
}
// parse parameters
var apiparams = {};
if (typeof params == 'object') {
apiparams = params;
} else {
parse_str(params, apiparams); //TODO
}
// map function name to API method
var method = '';
// replace _ by /
var path = fn.split('_');
for (var 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'];
for (i = 0; i < url_parameters_with_underscore.length; i++) {
var 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 = [];
if (match = method.match(/[A-Z_]{2,}/)) {
for (var i = 0; i < match.length; i++) {
var 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);
return _callApi(
httpmethod,
method,
method_template,
apiparams,
multipart,
app_only_auth,
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") {
var callback = function (reply) {};
}
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;
xml = Ti.Network.createHTTPClient();
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 reply = _parseApiReply("oauth2/token", xml.responseText);
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 (typeof data == 'array') {
return array_map([ // TODO
this, '_url'], data);
} else 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
*
* @param string data The data to calculate the hash from
*
* @return string The hash
*/
var _sha1 = function (data) {
if (_oauth_consumer_secret == null) {
console.warn('To generate a hash, the consumer secret must be set.');
}
if (typeof b64_hmac_sha1 != 'function') {
console.warn('To generate a hash, the Javascript SHA1.js must be available.');
}
b64pad = '=';
return b64_hmac_sha1(_oauth_consumer_secret + '&' + (_oauth_token_secret != null ? _oauth_token_secret : ''), data);
};
var base64_encode = function (data) {
// http://kevin.vanzonneveld.net
// + 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)
// * example 1: base64_encode('Kevin van Zonneveld');
// * returns 1: 'S2V2aW4gdmFuIFpvbm5ldmVsZA=='
// mozilla has this native
// - but breaks in 2.0.0.12!
//if (typeof this.window['btoa'] == 'function') {
// return btoa(data);
//}
var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
ac = 0,
enc = "",
tmp_arr = [];
if (! data) {
return data;
}
do { // pack three octets into four hexets
o1 = data.charCodeAt(i++);
o2 = data.charCodeAt(i++);
o3 = data.charCodeAt(i++);
bits = o1 << 16 | o2 << 8 | o3;
h1 = bits >> 18 & 0x3f;
h2 = bits >> 12 & 0x3f;
h3 = bits >> 6 & 0x3f;
h4 = bits & 0x3f;
// use hexets to index into b64, and append result to encoded string
tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
} while (i < data.length);
enc = tmp_arr.join('');
var r = data.length % 3;
return (r ? enc.slice(0, r - 3) : enc) + '==='.slice(r || 3);
};
var http_build_query = function (formdata, numeric_prefix, arg_separator) {
// http://kevin.vanzonneveld.net
// + 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/)
// % note 1: If the value is null, key and value is skipped in http_build_query of PHP. But, phpjs is not.
var value, key, tmp = [],
that = http_build_query;
var _http_build_query_helper = function (key, val, arg_separator) {
var k, tmp = [];
if (val === true) {
val = "1";
} else if (val === false) {
val = "0";
}
if (val != null) {
if(typeof(val) === "object") {
for (k in val) {
if (val[k] != null) {
tmp.push(_http_build_query_helper(key + "[" + k + "]", val[k], arg_separator));
}
}
return tmp.join(arg_separator);
} else if (typeof(val) !== "function") {
return _url(key) + "=" + _url(val);
} else {
throw new Error('There was an error processing for http_build_query().');
}
} else {
return '';
}
};
if (!arg_separator) {
arg_separator = "&";
}
for (key in formdata) {
value = formdata[key];
if (numeric_prefix && !isNaN(key)) {
key = String(numeric_prefix) + key;
}
var query=_http_build_query_helper(key, value, arg_separator);
if(query != '') {
tmp.push(query);
}
}
return tmp.join(arg_separator);
};
/**
* 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') {
var 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;
};
var _ksort = function (inputArr) {
// http://kevin.vanzonneveld.net
// + original by: GeekFG (http://geekfg.blogspot.com)
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + improved by: Brett Zamir (http://brett-zamir.me)
var tmp_arr = {},
keys = [],
sorter, i, k, that = this,
strictForIn = false,
populateArr = {};
sorter = function (a, b) {
var aFloat = parseFloat(a),
bFloat = parseFloat(b),
aNumeric = aFloat + '' === a,
bNumeric = bFloat + '' === b;
if (aNumeric && bNumeric) {
return aFloat > bFloat ? 1 : aFloat < bFloat ? -1 : 0;
} else if (aNumeric && !bNumeric) {
return 1;
} else if (!aNumeric && bNumeric) {
return -1;
}
return a > b ? 1 : a < b ? -1 : 0;
};
// Make a list of key names
for (k in inputArr) {
if (inputArr.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') {
var params = {};
}
if (typeof append_to_get == "undefined") {
var 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 = {};
for (var key in sign_params) {
var value = sign_params[key];
sign_base_params['oauth_' + key] = _url(value);
}
if (_oauth_token != null) {
sign_base_params['oauth_token'] = _url(_oauth_token);
}
oauth_params = _clone(sign_base_params);
for (var key in params) {
var value = params[key];
sign_base_params[key] = _url(value);
}
var keys = _ksort(sign_base_params);
var sign_base_string = '';
for (var i=0;i<keys.length;i++) {
var key = keys[i];
var value = sign_base_params[key];
sign_base_string += key + '=' + 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;
if (append_to_get) {
var authorization = '';
for(var key in params) {
var value = params[key];
authorization += key + "=" + _url(value) + "&";
}
return authorization.substring(0, authorization.length - 1);
}
var authorization = 'OAuth ';
for (var key in params) {
var 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':
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',
// 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',
// Users
'account/settings',
'account/verify_credentials',
'blocks/list',
'blocks/ids',
'users/lookup',
'users/show',
'users/search',
'users/contributees',
'users/contributors',
'users/profile_banner',
// 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',
// 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'
];
httpmethods['POST'] = [
// Tweets
'statuses/destroy/:id',
'statuses/update',
'statuses/retweet/:id',
'statuses/update_with_media',
// 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',
// 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',
// Places & Geo
'geo/place',
// Spam Reporting
'users/report_spam',
// OAuth
'oauth/access_token',
'oauth/request_token',
'oauth2/token',
'oauth2/invalidate_token'
];
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;
};
/**
* Builds the complete API endpoint url
*
* @param string method The API method to call
* @param string method_template The API method template to call
*
* @return string The URL to send the request to
*/
var _getEndpoint = function (method, method_template) {
if (method.substring(0, 5) == 'oauth') {
var url = _endpoint_oauth + method;
} else {
var url = _endpoint + method + '.json';
}
return url;
};
/**
* 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 string method_template The templated 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
*/
var _callApi = function (httpmethod, method, method_template, params, multipart, app_only_auth, callback) {
if (typeof params == 'undefined') {
var params = {};
}
if (typeof multipart == 'undefined') {
var multipart = false;
}
if (typeof app_only_auth == 'undefined') {
var app_only_auth = false;
}
if (typeof callback != 'function') {
var callback = function (reply) {};
}
var url = _getEndpoint(method, method_template);
var authorization = null;
var xml = Ti.Network.createHTTPClient();
if (httpmethod == 'GET') {
var url_with_params = url;
if (JSON.stringify(params) != "{}") {
url_with_params += '?' + http_build_query(params);
}
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;
callback(reply);
};
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
);
}
xml.open(httpmethod, url_with_params, true);
} else {
if (_use_jsonp) {
console.warn('Sending POST requests is not supported for IE7-9.');
return;
}
authorization = _sign(httpmethod, url, {});
if (! multipart) {
authorization = _sign(httpmethod, url, params);
params = http_build_query(params);
}
post_fields = params;
if (_use_proxy) {
url = url.replace(
_endpoint_base,
_endpoint_proxy
);
}
xml.open(httpmethod, url, true);
if (multipart) {
xml.setRequestHeader("Content-Type", "multipart/form-data");
} else {
xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
}
}
if (app_only_auth) {
if (_oauth_consumer_key == null) {
console.warn('To make an app-only auth API request, the consumer key must be set.');
}
// automatically fetch bearer token, if necessary
if (_oauth_bearer_token == null) {
return oauth2_token(function (reply) {
_callApi(httpmethod, method, method_template, params, multipart, app_only_auth, 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) {}
if(!httpstatus){
httpstatus = 12027;
}
var reply = _parseApiReply(method_template, xml.responseText);
if(!reply){
callback(null);
}
reply.httpstatus = httpstatus;
callback(reply);
}
};
xml.send(httpmethod == "GET" ? null : post_fields);
return true;
};
/**
* Parses the API reply to encode it in the set return_format
*
* @param string method The method that has been called
* @param string reply The actual reply, JSON-encoded or URL-encoded
*
* @return array|object The parsed reply
*/
var _parseApiReply = function (method, reply) {
if (reply == '[]' || reply == null) {
return [];
}
var parsed = false;
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>(.*)<\/request>/)[1];
parsed["error"] = reply.match(/<error>(.*)<\/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]] = unescape(element[1]);
} else {
parsed[element[0]] = null;
}
}
}
}
return parsed;
};
return {
setConsumerKey: setConsumerKey,
getVersion: getVersion,
setToken: setToken,
setBearerToken: setBearerToken,
setUseProxy: setUseProxy,
__call: __call,
oauth_authenticate: oauth_authenticate,
oauth_authorize: oauth_authorize,
oauth2_token: oauth2_token
};
};
module.exports = Codebird;
@fit4him
Copy link

fit4him commented Jul 10, 2013

Would love to get codebird implemented. I get the following error when running code below: Any ideas?

Can't find HTTP method to use for "statuses/user/timeline".
[WARN] : 2013-07-10 11:18:53.085 LFCMobileAlloy[95028:21407] 95028: CFNetwork internal error (0xc01a:/SourceCache/CFNetwork_Sim/CFNetwork-609.1.4/HTTP/HTTPRequestParserClient.h:28)
[WARN] : 2013-07-10 11:18:53.087 LFCMobileAlloy[95028:21407] [WARN] Unable to securely connect to api.twitter.com with the latest TLS. Trying again with TLS1.0. It is highly suggested that the server be updated to the latest TLS support.
[WARN] : 2013-07-10 11:18:53.173 LFCMobileAlloy[95028:21407] 95028: CFNetwork internal error (0xc01a:/SourceCache/CFNetwork_Sim/CFNetwork-609.1.4/HTTP/HTTPRequestParserClient.h:28)

cb.__call(
'statuses/user_timeline',
"purplepastor",
function (tweets) {

    for (var c=0;c<tweets.length;c++){

        var tweet = tweets[c].text;
        alert(c);
    }
    }

);

@nixv2
Copy link

nixv2 commented Jul 17, 2013

@fit4him i managed to get the user timeline doing some editing to the codebird.js file.
in line 472 to 480 you got this:

    var method = '';
    // replace _ by /
    var path = fn.split('_');
    for (var i = 0; i < path.length; i++) {
        if (i > 0) {
            method += '/';
        }
        method += path[i];
    }

here they replace _ by / which makes a wrong call to twitter's api, you need: statuses/user_timeline and you got statuses/user/timeline thats why you are getting that erro. so i changed it to this:

    var method = '';
    if (fn == 'statuses/user_timeline') {
        method = fn;
    }else{
        // replace _ by /
        var path = fn.split('_');
        for (var i = 0; i < path.length; i++) {
            if (i > 0) {
                method += '/';
            }
            method += path[i];
        }
    }

so this is how i managed to get de users time line... i didnt read all the file so am not shure if there is a better way to do this. so good luck and i hope this help you.

@roseanne123
Copy link

@nivx2 Thanks very much for your helpful comment. The code below worked when the cb__call was

'statuses/user_timeline'
But when I changed it to

'statuses/mentions_timeline'
I received the following error:

Uncaught Error: undefined method not supported.

Source: xml.send(httpmethod == "GET"? null: post_fields);
The source is in the codebird.js file.
Have you or has anyone else experienced this issue?

function fetchTwitter(){
var data = [];

cb.__call(
    'statuses/user_timeline',
    "screen_name=ClassicHits4FM",
    function (reply) {
        // ...
        Ti.API.info("newest and Example that should work just reply------------------------------"+ reply);
        Ti.API.info("newest and Example that may work, reply's text ------------------------------"+ reply[0].text);
        Ti.API.info("newest and Example that may work, reply's text ------------------------------"+ reply[0].user.profile_image_url);

)

@bcooke
Copy link

bcooke commented Jul 15, 2014

Anyone have any luck using the search query params?

This is not returning what I'd expect (5 tweets using 'rpp') :

function getTweets() {

var params = {
    q: 'test',
    rpp: '5',
};

cb.__call(
    'search_tweets',
    params,
    function (reply) { ... },
    true

);

The query for 'test' works, but it returns more than 5 tweets.

@ricardoalcocer
Copy link

@bcooke, according to the docs rpp was replaced by count.

@sobytes
Copy link

sobytes commented Dec 16, 2014

Hi All,

I am trying to run a retweet with.

cb.__call('statuses_retweet', {
                id : id
            }, function(reply) {
                Ti.API.info(reply);
            });

But i keep getting this error message i can see the call in codebird.js

[WARN] :   Can't find HTTP method to use for "statuses/retweet".
[WARN] :   Assertion failed: (self.method != nil), function -[APSHTTPRequest send], file /Users/vduggal/ForkedRepos/APSHTTPClient/APSHTTPClient/APSHTTPRequest.m, line 66.
-- End simulator log ---------------------------------------------------------

Can anyone help with this please.

Thanks

@pphan
Copy link

pphan commented May 12, 2015

Greeting All!
After login with my twitter credential, I saw 7 digits pin to complete authorization process. How can I solve it? Actually, goto authorizeUICallback function but it don't have any value from this line

var val = window.evalJS('window.document.querySelector('kbd[aria-labelledby="code-desc"] > code').innerHTML');

When val is null, never go to this code at below

setTweet();
accessToken = reply.oauth_token;
accessTokenSecret=reply.oauth_token_secret;
saveAccessToken('twitter');

Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment