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. <[email protected]>
* @copyright 2010-2013 J.M. <[email protected]>
*
* 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;
@Rogichi
Copy link

Rogichi commented Jun 26, 2013

Hi :D this is so helpful, how do i publish a tweet?

@iantearle
Copy link

Hi,

How would you handle this situation?

$.registerBtnTwitter.addEventListener('click', function() {
cb.__call(
"oauth_requestToken",
{oauth_callback: "oob"},
function (reply) {
// stores it
cb.setToken(reply.oauth_token, reply.oauth_token_secret);

        // gets the authorize screen URL
        cb.__call(
            "oauth_authorize",
            {},
            function (auth_url) {
                window.codebird_auth = window.open(auth_url); // Obvs this isnt going to work...
            }
        );
    }
);

});

Have you done a method of this already?

@FokkeZB
Copy link

FokkeZB commented Jul 1, 2013

Please note that you'll have to deal with rate limiting. The search for example, is limited to 450 calls per 15 minutes. Depending on the number of app users, this can be a problem.

https://dev.twitter.com/docs/rate-limiting/1.1/limits

@Rogichi
Copy link

Rogichi commented Jul 1, 2013

Hi, I solved my problem and I want to share. https://gist.github.com/Rogichi/5905010

@devinharrison
Copy link

Hello! I'm making an app and I ran into a problem. I recently used birdhouse.js to authenticate my apps connection to twitter. What I'm trying to do is to simply "link" my user's account with their twitter account. The problem I seem to have is that birdhouse is deprecated and even making changes to URL's just isn't working. What I need to know is, will this library enable me to pull my user twitter handle from their account? If so, how would they log in or can they? It looks like this library wants to bypass user login. Is it possible to include user login and once done pull their handle?

@iantearle
Copy link

@Rogichi You are my hero. Though I had to fix it to actually save the tokens. Will comment on your gist

@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