1 /*jslint 
  2 browser: true,
  3 nomen: false,
  4 debug: true,
  5 forin: true,
  6 plusplus: false,
  7 regexp: false,
  8 sub: true,
  9 undef: true,
 10 white: false,
 11 onevar: false 
 12  */
 13 var sc, jQuery, Mojo, use_palmhost_proxy;
 14 
 15 /**
 16  * @depends ../helpers/string.js 
 17  * @depends ../helpers/datetime.js 
 18  * @depends ../helpers/event.js 
 19  * @depends ../helpers/json.js 
 20  * @depends ../helpers/sys.js 
 21  */
 22 
 23 
 24 /**
 25  * various constant definitions
 26  */
 27 /**
 28  * @constant 
 29  */
 30 var SPAZCORE_SECTION_FRIENDS = 'friends';
 31 /**
 32  * @constant 
 33  */
 34 var SPAZCORE_SECTION_HOME = 'home';
 35 /**
 36  * @constant 
 37  */
 38 var SPAZCORE_SECTION_REPLIES = 'replies';
 39 /**
 40  * @constant 
 41  */
 42 var SPAZCORE_SECTION_DMS = 'dms';
 43 /**
 44  * @constant 
 45  */
 46 var SPAZCORE_SECTION_FAVORITES = 'favorites';
 47 /**
 48  * @constant 
 49  */
 50 var SPAZCORE_SECTION_COMBINED = 'combined';
 51 /**
 52  * @constant 
 53  */
 54 var SPAZCORE_SECTION_PUBLIC = 'public';
 55 /**
 56  * @constant 
 57  */
 58 var SPAZCORE_SECTION_SEARCH = 'search';
 59 /**
 60  * @constant 
 61  */
 62 var SPAZCORE_SECTION_USER = 'user-timeline';
 63 /**
 64  * @constant 
 65  */
 66 var SPAZCORE_SECTION_FRIENDLIST = 'friendslist';
 67 /**
 68  * @constant 
 69  */
 70 var SPAZCORE_SECTION_FOLLOWERSLIST = 'followerslist';
 71 /**
 72  * @constant 
 73  */
 74 var SPAZCORE_SECTION_USERLISTS = 'userlists';
 75 
 76 /**
 77  * @constant 
 78  */
 79 var SPAZCORE_SERVICE_TWITTER = 'twitter';
 80 /**
 81  * @constant 
 82  */
 83 var SPAZCORE_SERVICE_IDENTICA = 'identi.ca';
 84 /**
 85  * @constant 
 86  */
 87 var SPAZCORE_SERVICE_WORDPRESS_TWITTER = 'wordpress-twitter';
 88 /**
 89  * @constant 
 90  */
 91 var SPAZCORE_SERVICE_TUMBLR_TWITTER = 'tumblr-twitter';
 92 /**
 93  * @constant 
 94  */
 95 var SPAZCORE_SERVICE_CUSTOM = 'custom';
 96 /**
 97  * @constant 
 98  */
 99 var SPAZCORE_SERVICEURL_TWITTER = 'https://api.twitter.com/1/';
100 /**
101  * @constant 
102  */
103 var SPAZCORE_SERVICEURL_IDENTICA = 'https://identi.ca/api/';
104 /**
105  * @constant 
106  */
107 var SPAZCORE_SERVICEURL_WORDPRESS_TWITTER = 'https://twitter-api.wordpress.com/';
108 /**
109  * @constant 
110  */
111 var SPAZCORE_SERVICEURL_TUMBLR_TWITTER = 'http://www.tumblr.com/';
112 
113 
114 
115 /**
116  * A Twitter API library for Javascript
117  * 
118  * 
119  * jQuery events raised by this library
120  * 
121  * <ul>
122  *   <li>'spaztwit_ajax_error'</li>
123  *   <li>'new_public_timeline_data' (data)</li>
124  *   <li>'new_friends_timeline_data' (data)</li>
125  *   <li>'error_friends_timeline_data' (data)</li>
126  *   <li>'new_replies_timeline_data' (data)</li>
127  *   <li>'error_replies_timeline_data' (data)</li>
128  *   <li>'new_dms_timeline_data' (data)</li>
129  *   <li>'error_dms_timeline_data' (data)</li>
130  *   <li>'new_combined_timeline_data' (data)</li>
131  *   <li>'error_combined_timeline_data' (data)</li>
132  *   <li>'new_favorites_timeline_data' (data)</li>
133  *   <li>'error_favorites_timeline_data' (data)</li>
134  *   <li>'verify_credentials_succeeded' (data)</li>
135  *   <li>'verify_credentials_failed' (data)</li>
136  *   <li>'update_succeeded' (data)</li>
137  *   <li>'update_failed' (data)</li>
138  *   <li>'get_user_succeeded' (data)</li>
139  *   <li>'get_user_failed' (data)</li>
140  *   <li>'get_one_status_succeeded' (data)</li>
141  *   <li>'get_one_status_failed' (data)</li>
142  *   <li>'new_search_timeline_data' (data)</li>
143  *   <li>'error_search_timeline_data' (data)</li>
144  *   <li>'new_trends_data' (data)</li>
145  *   <li>'error_trends_data' (data)</li>
146  *   <li>'new_saved_searches_data' (data)</li>
147  *   <li>'error_saved_searches_data' (data)</li>
148  *   <li>'create_saved_search_succeeded' (data)</li>
149  *   <li>'create_saved_search_failed' (data)</li>
150  *   <li>'destroy_saved_search_succeeded' (data)</li>
151  *   <li>'destroy_saved_search_failed' (data)</li>
152  *   <li>'create_favorite_succeeded'</li>
153  *   <li>'create_favorite_failed'</li>
154  *   <li>'destroy_favorite_succeeded'</li>
155  *   <li>'destroy_favorite_failed'</li>
156  *   <li>'create_friendship_succeeded'</li>
157  *   <li>'create_friendship_failed'</li>
158  *   <li>'destroy_friendship_succeeded'</li>
159  *   <li>'destroy_friendship_failed'</li>
160  *   <li>'create_block_succeeded'</li>
161  *   <li>'create_block_failed'</li>
162  *   <li>'destroy_block_succeeded'</li>
163  *   <li>'destroy_block_failed'</li>
164  *   <li>'follow_succeeded'</li>
165  *   <li>'follow_failed'</li>
166  *   <li>'unfollow_succeeded'</li>
167  *   <li>'unfollow_failed'</li>
168  *   <li>'ratelimit_status_succeeded'</li>
169  *   <li>'ratelimit_status_failed'</li>
170  *   <li>'destroy_status_succeeded'</li>
171  *   <li>'destroy_status_failed'</li>
172  *   <li>'destroy_dm_succeeded'</li>
173  *   <li>'destroy_dm_failed'</li>
174  * </ul>
175  * 
176  * @param {Object} opts various options
177  * @param {Object} [opts.auth] SpazAuth object
178  * @param {String} [opts.event_mode] The event mode to use ('jquery' or 'DOM'). Defaults to 'DOM'
179  * @param {Object} [opts.event_target] the DOM element to target the event on. Defaults to document
180  * @param {Number} [opts.timeout] length of time, in seconds, to timeout
181  * @class SpazTwit
182  * @constructor
183 */
184 function SpazTwit(opts) {
185 	
186 	this.opts = sch.defaults({
187 		auth:         null,
188 		username:     null,
189 		event_mode:   'DOM',
190 		event_target: document,
191 		timeout:      this.DEFAULT_TIMEOUT
192 	}, opts);
193 	
194 	
195 	this.auth                = this.opts.auth;
196 	
197 	this.setSource('SpazCore');
198 	
199 	this.initializeData();
200 	
201 	this.initializeCombinedTracker();
202 	
203 	/*
204 		Cache for one-shot users and posts. Not sure what we'll do with it yet
205 	*/
206 	this.cache = {
207 		users:{},
208 		posts:{}
209 	};
210 	
211 	this.me = {};
212 	
213 
214 	this.setBaseURL(SPAZCORE_SERVICEURL_TWITTER);
215 
216 	/**
217 	 * remap dump calls as appropriate 
218 	 */
219 	if (sc && sc.helpers && sc.helpers.dump) {
220 		window.dump = sc.helpers.dump;
221 	} else { // do nothing!
222 		var dump = function(input) {
223 			return;
224 		};
225 	}
226 }
227 
228 /**
229  * the default timeout value (60 seconds) 
230  */
231 SpazTwit.prototype.DEFAULT_TIMEOUT = 1000*60;
232 
233 
234 
235 /**
236  * retrieves the last status id retrieved for a given section
237  * @param {string} section  use one of the defined constants (ex. SPAZCORE_SECTION_HOME)
238  * @return {integer} the last id retrieved for this section
239  */
240 SpazTwit.prototype.getLastId   = function(section) {
241 	return this.data[section].lastid;
242 };
243 
244 /**
245  * sets the last status id retrieved for a given section
246  * @param {string} section  use one of the defined constants (ex. SPAZCORE_SECTION_HOME)
247  * @param {integer} id  the new last id retrieved for this section
248  */
249 SpazTwit.prototype.setLastId   = function(section, id) {
250 	this.data[section].lastid = parseInt(id, 10);
251 };
252 
253 
254 SpazTwit.prototype.initializeData = function() {
255 	/*
256 		this is where we store timeline data and settings for persistence
257 	*/
258 	this.data = {};
259 	this.data[SPAZCORE_SECTION_HOME] = {
260 		'lastid':   1,
261 		'items':   [],
262 		'newitems':[],
263 		'max':200,
264 		'min_age':5*60
265 	};
266 	this.data[SPAZCORE_SECTION_FRIENDS] = {
267 		'lastid':   1,
268 		'items':   [],
269 		'newitems':[],
270 		'max':200,
271 		'min_age':5*60
272 	};
273 	this.data[SPAZCORE_SECTION_REPLIES] = {
274 		'lastid':   1,
275 		'items':   [],
276 		'newitems':[],
277 		'max':50,
278 		'min_age':5*60
279 	};
280 	this.data[SPAZCORE_SECTION_DMS] = {
281 		'lastid':   1,
282 		'items':   [],
283 		'newitems':[],
284 		'max':50,
285 		'min_age':5*60
286 	};
287 	this.data[SPAZCORE_SECTION_FAVORITES] = {
288 		'lastid':   1,
289 		'items':   [],
290 		'newitems':[],
291 		'max':100,
292 		'min_age':5*60
293 	};
294 	this.data[SPAZCORE_SECTION_COMBINED] = {
295 		'items':   [],
296 		'newitems':[],
297 		'updates' :[],
298 		'max':400,
299 		'min_age':5*60
300 	};
301 	this.data[SPAZCORE_SECTION_FRIENDLIST] = {
302 		'items':   [],
303 		'newitems':[],
304 		'max':500,
305 		'min_age':5*60
306 	};
307 	this.data[SPAZCORE_SECTION_FOLLOWERSLIST] = {
308 		'items':   [],
309 		'newitems':[],
310 		'max':500,
311 		'min_age':5*60
312 	};
313 	this.data[SPAZCORE_SECTION_SEARCH] = {
314 		'lastid':  0, // search api prefers 0, will freak out on "1"
315 		'items':   [],
316 		'newitems':[],
317 		'lastresultdata':{},
318 		'max':200,
319 		'min_age':30
320 	};
321 	this.data[SPAZCORE_SECTION_USERLISTS] = {
322 		'items':   [],
323 		'newitems':[],
324 		'max':500,
325 		'min_age':5*60
326 	};
327 	// this.data.byid = {};
328 };
329 
330 
331 /**
332  * resets the combined_finished progress tracker 
333  */
334 SpazTwit.prototype.initializeCombinedTracker = function() {
335 	this.combined_finished = {};
336 	this.combined_finished[SPAZCORE_SECTION_HOME] = false;
337 	this.combined_finished[SPAZCORE_SECTION_REPLIES] = false;
338 	this.combined_finished[SPAZCORE_SECTION_DMS] = false;
339 	
340 	this.combined_errors = [];
341 };
342 
343 /**
344  * Checks to see if the combined timeline is finished 
345  * @return {boolean}
346  */
347 SpazTwit.prototype.combinedTimelineFinished = function() {
348 	for (var i in this.combined_finished) {
349 		if (!this.combined_finished[i]) {
350 			return false;
351 		}
352 	}
353 	return true;
354 };
355 
356 /**
357  * Checks to see if the combined timeline is finished 
358  * @return {boolean}
359  */
360 SpazTwit.prototype.combinedTimelineHasErrors = function() {
361 	if (this.combined_errors.length > 0) {
362 		return true;
363 	} else {
364 		return false;
365 	}
366 };
367 
368 /**
369  * Checks to see if the combined timeline contains sent updates
370  * @return {boolean}
371  */
372 SpazTwit.prototype.combinedTimelineHasUpdates = function() {
373 	return this.data[SPAZCORE_SECTION_COMBINED].updates.length > 0;
374 };
375 
376 /**
377  * Adds ids of array of statuses to updates
378  */
379 SpazTwit.prototype.combinedTimelineAddUpdates = function(items) {
380 	if (items.id) {
381 		items = [items];
382 	}
383 	var i;
384 	for (i in items) {
385 		this.data[SPAZCORE_SECTION_COMBINED].updates.push(items[i].id);
386 	}
387 };
388 
389 /**
390  * Removes the update items from combined newitems
391  */
392 SpazTwit.prototype.combinedNewItemsRemoveUpdates = function() {
393 	if (!this.combinedTimelineHasUpdates()) {
394 		return;
395 	}
396 	var data = this.data[SPAZCORE_SECTION_COMBINED],
397 		iStr = ':' + data.updates.join(':') + ':',
398 		news = data.newitems,
399 		keep = [],
400 		i;
401 
402 	for (i in news) {
403 		if (!RegExp(':' + news[i].id + ':').test(iStr)) {
404 			keep.push(news[i]);
405 		}
406 	}
407 	data.newitems = keep;
408 	data.updates  = [];
409 };
410 
411 
412 /**
413  * sets the base URL
414  * @param {string} newurl
415  */
416 SpazTwit.prototype.setBaseURL= function(newurl) {
417 	
418 	var lastchar = newurl.charAt(newurl.length -1);
419 	if (lastchar !== '/') {
420 		newurl = newurl + '/';
421 	}
422 	
423 	this.baseurl = newurl;
424 };
425 
426 
427 /**
428  * sets the base URL by the service type
429  * @param {string} service  see SPAZCORE_SERVICE_* 
430  */
431 SpazTwit.prototype.setBaseURLByService= function(service) {
432 	
433 	var baseurl = '';
434 	
435 	switch (service) {
436 		case SPAZCORE_SERVICE_TWITTER:
437 			baseurl = SPAZCORE_SERVICEURL_TWITTER;
438 			break;
439 		case SPAZCORE_SERVICE_IDENTICA:
440 			baseurl = SPAZCORE_SERVICEURL_IDENTICA;
441 			break;
442 		case SPAZCORE_SERVICE_WORDPRESS_TWITTER:
443 			baseurl = SPAZCORE_SERVICEURL_WORDPRESS_TWITTER;
444 			break;
445 		case SPAZCORE_SERVICE_TUMBLR_TWITTER:
446 			baseurl = SPAZCORE_SERVICEURL_TUMBLR_TWITTER;
447 			break;
448 		default:
449 			baseurl = SPAZCORE_SERVICEURL_TWITTER;
450 			break;
451 	}
452 	
453 	this.baseurl = baseurl;
454 };
455 
456 
457 SpazTwit.prototype.setCredentials = function(auth_obj) {
458 	this.auth = auth_obj;
459 	this.username = this.auth.username;
460 };
461 
462 
463 /**
464  * set the source string we will pass on updates
465  * 
466  * @param {string} new_source 
467  */
468 SpazTwit.prototype.setSource = function(new_source) {
469 	this.source = new_source;
470 };
471 
472 
473 
474 /*
475  * given a key, it returns the URL (baseurl+API method path)
476  * @param {string} key the key for the URL
477  * @param {array|object} urldata data to included in the URL as GET data
478 */
479 SpazTwit.prototype.getAPIURL = function(key, urldata) {
480 	var urls = {};
481 
482 
483 
484     // Timeline URLs
485 	urls.public_timeline    = "statuses/public_timeline.json";
486 	urls.friends_timeline   = "statuses/friends_timeline.json";
487 	urls.home_timeline		= "statuses/home_timeline.json";
488 	urls.user_timeline      = "statuses/user_timeline.json";
489 	urls.replies_timeline   = "statuses/replies.json";
490 	urls.show				= "statuses/show/{{ID}}.json";
491 	urls.favorites          = "favorites.json";
492 	urls.user_favorites     = "favorites/{{ID}}.json"; // use this to retrieve favs of a user other than yourself
493 	urls.dm_timeline        = "direct_messages.json";
494 	urls.dm_sent            = "direct_messages/sent.json";
495 	urls.friendslist        = "statuses/friends.json";
496 	urls.followerslist      = "statuses/followers.json";
497 	urls.show_user			= "users/show/{{ID}}.json";
498 	urls.featuredlist       = "statuses/featured.json";
499 
500 	// Action URLs
501 	urls.update           	= "statuses/update.json";
502 	urls.destroy_status   	= "statuses/destroy/{{ID}}.json";
503 	urls.dm_new             = "direct_messages/new.json";
504 	urls.dm_destroy         = "direct_messages/destroy/{{ID}}.json";
505 	urls.friendship_create  = "friendships/create/{{ID}}.json";
506 	urls.friendship_destroy	= "friendships/destroy/{{ID}}.json";
507 	urls.friendship_show	= "friendships/show.json";
508 	urls.friendship_incoming	= "friendships/incoming.json";
509 	urls.friendship_outgoing	= "friendships/outgoing.json";
510 	urls.graph_friends		= "friends/ids.json";
511 	urls.graph_followers	= "followers/ids.json";
512 	urls.block_create		= "blocks/create/{{ID}}.json";
513 	urls.block_destroy		= "blocks/destroy/{{ID}}.json";
514 	urls.follow             = "notifications/follow/{{ID}}.json";
515 	urls.unfollow			= "notifications/leave/{{ID}}.json";
516 	urls.favorites_create 	= "favorites/create/{{ID}}.json";
517 	urls.favorites_destroy	= "favorites/destroy/{{ID}}.json";
518 	urls.saved_searches_create 	= "saved_searches/create.json";
519 	urls.saved_searches_destroy	= "saved_searches/destroy/{{ID}}.json";
520 	urls.verify_credentials = "account/verify_credentials.json";
521 	urls.ratelimit_status   = "account/rate_limit_status.json";
522 	urls.update_profile		= "account/update_profile.json";
523 	urls.saved_searches		= "saved_searches.json";
524 	urls.report_spam		= "report_spam.json";
525 
526     // User lists URLs
527 	urls.lists              = "{{USER}}/lists.json";
528 	urls.lists_list         = "{{USER}}/lists/{{SLUG}}.json";
529 	urls.lists_memberships  = "{{USER}}/lists/memberships.json";
530 	urls.lists_timeline     = "{{USER}}/lists/{{SLUG}}/statuses.json";
531 	urls.lists_members      = "{{USER}}/{{SLUG}}/members.json";
532 	urls.lists_check_member = "{{USER}}/{{SLUG}}/members/{{ID}}.json";
533 	urls.lists_subscribers  = "{{USER}}/{{SLUG}}/subscribers.json";
534 	urls.lists_check_subscriber = "{{USER}}/{{SLUG}}/subscribers/{{ID}}.json";
535 	urls.lists_subscriptions = "{{USER}}/lists/subscriptions.json";
536 
537 	//trends
538 	urls.trends				= "trends.json";
539 	urls.trends_current		= "trends/current.json";
540 	urls.trends_daily		= "trends/daily.json";
541 	urls.trends_weekly		= "trends/weekly.json";
542 	
543 	//retweet
544 	urls.retweet			= "statuses/retweet/{{ID}}.json";
545 	urls.retweets			= "statuses/retweets/{{ID}}.json";
546 	urls.retweeted_by_me	= "statuses/retweeted_by_me.json";
547 	urls.retweeted_to_me	= "statuses/retweeted_to_me.json";
548 	urls.retweets_of_me		= "statuses/retweets_of_me.json";
549 
550 	// search
551 	if (this.baseurl === SPAZCORE_SERVICEURL_TWITTER) {
552 		urls.search				= "http://search.twitter.com/search.json";
553 	} else {
554 		urls.search				= "search.json";
555 	}
556 
557 	// misc
558 	urls.test 			  	= "help/test.json";
559 	urls.downtime_schedule	= "help/downtime_schedule.json";
560 
561 	
562 	if (urls[key].indexOf('{{ID}}') > -1) {
563 		if (typeof(urldata) === 'string') {
564 			urls[key] = urls[key].replace('{{ID}}', urldata);
565 		} else if (urldata && typeof(urldata) === 'object') {
566 			urls[key] = urls[key].replace('{{ID}}', urldata.id);
567 		}
568 		
569 	}
570 
571     // Token replacement for user lists
572     if (urls[key].indexOf('{{USER}}') > - 1) {
573         if (urldata && typeof(urldata) === 'object') {
574             urls[key] = urls[key].replace('{{USER}}', urldata.user);
575         }
576     }
577 
578     if (urls[key].indexOf('{{SLUG}}') > -1) {
579         if (urldata && typeof(urldata) === 'object') {
580             urls[key] = urls[key].replace('{{SLUG}}', urldata.slug);
581         }
582     }
583 
584     if (urls[key]) {
585 	
586 		if (urldata && typeof urldata !== "string") {
587 			urldata = '?'+jQuery.param(urldata);
588 		} else {
589 			urldata = '';
590 		}
591 		
592 		if (this.baseurl === SPAZCORE_SERVICEURL_TWITTER && (key === 'search')) {
593 			return this._postProcessURL(urls[key] + urldata);
594 		} else {
595 			return this._postProcessURL(this.baseurl + urls[key] + urldata);
596 		}
597         
598     } else {
599         return false;
600     }
601 
602 
603 };
604 
605 
606 
607 
608 /*
609  * Verify authentication credentials. 
610 */
611 SpazTwit.prototype.verifyCredentials = function(onSuccess, onFailure) {
612 	var url = this.getAPIURL('verify_credentials');
613 	
614 	var opts = {
615 		'url':url,
616 		'process_callback': this._processAuthenticatedUser,
617 		'success_event_type':'verify_credentials_succeeded',
618 		'failure_event_type':'verify_credentials_failed',
619 		'success_callback':onSuccess,
620 		'failure_callback':onFailure,
621 		'method':'GET'
622 	};
623 
624 	/*
625 		Perform a request and get true or false back
626 	*/
627 	var xhr = this._callMethod(opts);
628 	
629 };
630 
631 /**
632  * This takes data retrieved from the verifyCredentials method and stores it
633  * in this.me. it then fires off the event specified in finished_event
634  * 
635  * @param {object} data the data returned by a successful call to the verifyCredentials API method
636  * @param {string} finished_event the type of event to fire 
637  * @private
638  */
639 SpazTwit.prototype._processAuthenticatedUser = function(data, opts) {
640 	this.me = data;
641 	this.initializeData();
642 	
643 	if (opts.success_callback) {
644 		opts.success_callback(this.me);
645 	}
646 	this.triggerEvent(opts.success_event_type, this.me);
647 	
648 };
649 
650 
651 /**
652  * Initiates retrieval of the public timeline. 
653  */
654 SpazTwit.prototype.getPublicTimeline = function(onSuccess, onFailure) {
655 	var url = this.getAPIURL('public_timeline');
656 	
657 	var xhr = this._getTimeline({
658 		'url':url,
659 		'success_callback':onSuccess,
660 		'failure_callback':onFailure,
661 		'success_event_type': 'new_public_timeline_data'
662 	});
663 };
664 
665 
666 /**
667  * Initiates retrieval of the home timeline (all the people you are following)
668  * 
669  * @param {integer} since_id default is 1
670  * @param {integer} count default is 200 
671  * @param {integer} page default is null (ignored if null)
672  */
673 SpazTwit.prototype.getHomeTimeline = function(since_id, count, page, processing_opts, onSuccess, onFailure) {
674 	
675 	if (!page) { page = null;}
676 	if (!count) { count = 50;}
677 	if (!since_id) {
678 		if (this.data[SPAZCORE_SECTION_HOME].lastid && this.data[SPAZCORE_SECTION_HOME].lastid > 1) {
679 			since_id = this.data[SPAZCORE_SECTION_HOME].lastid;
680 		} else {
681 			since_id = 1;
682 		}
683 	}
684 	
685 	if (!processing_opts) {
686 		processing_opts = {};
687 	}
688 	
689 	if (processing_opts.combined) {
690 		processing_opts.section = SPAZCORE_SECTION_HOME;
691 	}
692 	
693 	var data = {};
694 	if (since_id < -1) {
695 		data['max_id'] = Math.abs(since_id);
696 	} else {
697 		data['since_id'] = since_id;
698 	}
699 	data['count']	 = count;
700 	if (page) {
701 		data['page'] = page;
702 	}
703 	
704 	
705 	var url = this.getAPIURL('home_timeline', data);
706 	this._getTimeline({
707 		'url':url,
708 		'process_callback'	: this._processHomeTimeline,
709 		'success_callback':onSuccess,
710 		'failure_callback':onFailure,
711 		'success_event_type': 'new_home_timeline_data',
712 		'failure_event_type': 'error_home_timeline_data',
713 		'processing_opts':processing_opts
714 	});
715 };
716 
717 /**
718  * @private
719  */
720 SpazTwit.prototype._processHomeTimeline = function(ret_items, opts, processing_opts) {
721 	sc.helpers.dump('Processing '+ret_items.length+' items returned from home method');
722 	this._processTimeline(SPAZCORE_SECTION_HOME, ret_items, opts, processing_opts);
723 };
724 
725 
726 
727 /**
728  * Initiates retrieval of the friends timeline (all the people you are following)
729  * 
730  * @param {integer} since_id default is 1
731  * @param {integer} count default is 200 
732  * @param {integer} page default is null (ignored if null)
733  */
734 SpazTwit.prototype.getFriendsTimeline = function(since_id, count, page, processing_opts, onSuccess, onFailure) {
735 	
736 	if (!page) { page = null;}
737 	if (!count) { count = 50;}
738 	if (!since_id) {
739 		if (this.data[SPAZCORE_SECTION_FRIENDS].lastid && this.data[SPAZCORE_SECTION_FRIENDS].lastid > 1) {
740 			since_id = this.data[SPAZCORE_SECTION_FRIENDS].lastid;
741 		} else {
742 			since_id = 1;
743 		}
744 	}
745 	
746 	if (!processing_opts) {
747 		processing_opts = {};
748 	}
749 	
750 	if (processing_opts.combined) {
751 		processing_opts.section = SPAZCORE_SECTION_FRIENDS;
752 	}
753 	
754 	var data = {};
755 	data['since_id'] = since_id;
756 	data['count']	 = count;
757 	if (page) {
758 		data['page'] = page;
759 	}
760 	
761 	
762 	var url = this.getAPIURL('friends_timeline', data);
763 	this._getTimeline({
764 		'url':url,
765 		'process_callback'	: this._processFriendsTimeline,
766 		'success_callback':onSuccess,
767 		'failure_callback':onFailure,
768 		'success_event_type': 'new_friends_timeline_data',
769 		'failure_event_type': 'error_friends_timeline_data',
770 		'processing_opts':processing_opts
771 	});
772 };
773 
774 /**
775  * @private
776  */
777 SpazTwit.prototype._processFriendsTimeline = function(ret_items, opts, processing_opts) {
778 	sc.helpers.dump('Processing '+ret_items.length+' items returned from friends method');
779 	this._processTimeline(SPAZCORE_SECTION_FRIENDS, ret_items, opts, processing_opts);
780 };
781 
782 
783 /**
784  *  
785  */
786 SpazTwit.prototype.getReplies = function(since_id, count, page, processing_opts, onSuccess, onFailure) {	
787 	if (!page) { page = null;}
788 	if (!count) { count = null;}
789 	if (!since_id) {
790 		if (this.data[SPAZCORE_SECTION_REPLIES].lastid && this.data[SPAZCORE_SECTION_REPLIES].lastid > 1) {
791 			since_id = this.data[SPAZCORE_SECTION_REPLIES].lastid;
792 		} else {
793 			since_id = 1;
794 		}
795 	}
796 	
797 	if (!processing_opts) {
798 		processing_opts = {};
799 	}
800 	
801 	if (processing_opts.combined) {
802 		processing_opts.section = SPAZCORE_SECTION_REPLIES;
803 	}
804 	
805 	
806 	var data = {};
807 	if (since_id < -1) {
808 		data['max_id'] = Math.abs(since_id);
809 	} else {
810 		data['since_id'] = since_id;
811 	}
812 	if (page) {
813 		data['page'] = page;
814 	}
815 	if (count) {
816 		data['count'] = count;
817 	}
818 	
819 	var url = this.getAPIURL('replies_timeline', data);
820 	this._getTimeline({
821 		'url':url,
822 		'process_callback'	: this._processRepliesTimeline,
823 		'success_callback':onSuccess,
824 		'failure_callback':onFailure,
825 		'success_event_type': 'new_replies_timeline_data',
826 		'failure_event_type': 'error_replies_timeline_data',
827 		'processing_opts':processing_opts
828 	});
829 
830 };
831 
832 
833 /**
834  * @private
835  */
836 SpazTwit.prototype._processRepliesTimeline = function(ret_items, opts, processing_opts) {
837 	sc.helpers.dump('Processing '+ret_items.length+' items returned from replies method');
838 	this._processTimeline(SPAZCORE_SECTION_REPLIES, ret_items, opts, processing_opts);
839 };
840 
841 /**
842  *  
843  */
844 SpazTwit.prototype.getDirectMessages = function(since_id, count, page, processing_opts, onSuccess, onFailure) {
845 	if (!page) { page = null;}
846 	if (!count) { count = null;}
847 	if (!since_id) {
848 		if (this.data[SPAZCORE_SECTION_DMS].lastid && this.data[SPAZCORE_SECTION_DMS].lastid > 1) {
849 			since_id = this.data[SPAZCORE_SECTION_DMS].lastid;
850 		} else {
851 			since_id = 1;
852 		}
853 	}
854 	
855 	if (!processing_opts) {
856 		processing_opts = {};
857 	}
858 	
859 	if (processing_opts.combined) {
860 		processing_opts.section = SPAZCORE_SECTION_DMS;
861 	}
862 	
863 	var data = {};
864 	if (since_id < -1) {
865 		data['max_id'] = Math.abs(since_id);
866 	} else {
867 		data['since_id'] = since_id;
868 	}
869 	if (page) {
870 		data['page'] = page;
871 	}
872 	if (count) {
873 		data['count'] = count;
874 	}
875 	
876 	var url = this.getAPIURL('dm_timeline', data);
877 	this._getTimeline({
878 		'url':url,
879 		'process_callback'	: this._processDMTimeline,
880 		'success_callback':onSuccess,
881 		'failure_callback':onFailure,
882 		'success_event_type': 'new_dms_timeline_data',
883 		'failure_event_type': 'error_dms_timeline_data',
884 		'processing_opts':processing_opts		
885 	});
886 	
887 };
888 
889 /**
890  * @private
891  */
892 SpazTwit.prototype._processDMTimeline = function(ret_items, opts, processing_opts) {
893 	sc.helpers.dump('Processing '+ret_items.length+' items returned from DM method');
894 	this._processTimeline(SPAZCORE_SECTION_DMS, ret_items, opts, processing_opts);
895 };
896 
897 /**
898  *  
899  */
900 SpazTwit.prototype.getFavorites = function(page, processing_opts, onSuccess, onFailure) {	
901 	if (!page) { page = null;}
902 	if (!processing_opts) {
903 		processing_opts = {};
904 	}
905 	
906 	var data = {};
907 	if (page) {
908 		data['page'] = page;
909 	}
910 	
911 	var url = this.getAPIURL('favorites', data);
912 
913 	this._getTimeline({
914 		'url':url,
915 		'process_callback'	: this._processFavoritesTimeline,
916 		'success_callback':onSuccess,
917 		'failure_callback':onFailure,
918 		'success_event_type': 'new_favorites_timeline_data',
919 		'failure_event_type': 'error_favorites_timeline_data',
920 		'processing_opts':processing_opts
921 	});
922 
923 };
924 /**
925  * @private
926  */
927 SpazTwit.prototype._processFavoritesTimeline = function(ret_items, opts, processing_opts) {
928 	this._processTimeline(SPAZCORE_SECTION_FAVORITES, ret_items, opts, processing_opts);
929 };
930 
931 
932 
933 SpazTwit.prototype.getSent = function(since_id, count, page, onSuccess, onFailure) {}; // auth user's sent statuses
934 SpazTwit.prototype.getSentDirectMessages = function(since_id, page, onSuccess, onFailure) {};
935 
936 SpazTwit.prototype.getUserTimeline = function(id, count, page, onSuccess, onFailure) {
937 
938 	var opts = sch.defaults({
939 		'id': id,
940 		'since_id': null,
941 		'count': count || 10,
942 		'page': page || null,
943 		'onSuccess': onSuccess,
944 		'onFailure': onFailure
945 	}, id);
946 
947 	if (!opts.id || 'object' === typeof opts.id) {
948 		return;
949 	}
950 
951 	var data = {};
952 	data['id']    = opts.id;
953 	data['count'] = opts.count;
954 	if (opts.since_id) {
955 		if (opts.since_id < -1) {
956 			data['max_id'] = Math.abs(opts.since_id);
957 		} else {
958 			data['since_id'] = opts.since_id;
959 		}
960 	}
961 	if (opts.page) {
962 		data['page'] = opts.page;
963 	}
964 	
965 	
966 	var url = this.getAPIURL('user_timeline', data);
967 	
968 	this._getTimeline({
969 		'url':url,
970 		'process_callback'	: this._processUserTimeline,
971 		'success_callback':opts.onSuccess,
972 		'failure_callback':opts.onFailure,
973 		'success_event_type': 'new_user_timeline_data',
974 		'failure_event_type': 'error_user_timeline_data'
975 	});
976 }; // given user's sent statuses
977 
978 
979 /**
980  * @private
981  */
982 SpazTwit.prototype._processUserTimeline = function(ret_items, opts, processing_opts) {
983 	this._processTimeline(SPAZCORE_SECTION_USER, ret_items, opts, processing_opts);
984 };
985 
986 
987 
988 /**
989  * this retrieves three different timelines. the event "new_combined_timeline_data"
990  * does not fire until ALL async ajax calls are made 
991  * 
992  * 
993  */
994 SpazTwit.prototype.getCombinedTimeline = function(com_opts, onSuccess, onFailure) {
995 	var home_count, friends_count, replies_count, dm_count, home_since, friends_since, dm_since, replies_since = null;
996 
997 	var opts = {
998 		'combined':true
999 	};
1000 	
1001 	if (com_opts) {
1002 		if (com_opts.friends_count) {
1003 			friends_count = com_opts.friends_count;
1004 		}
1005 		if (com_opts.home_count) {
1006 			home_count = com_opts.home_count;
1007 		}
1008 		if (com_opts.replies_count) {
1009 			replies_count = com_opts.replies_count; // this is not used yet
1010 		}
1011 		if (com_opts.dm_count) {
1012 			dm_count = com_opts.dm_count; // this is not used yet
1013 		}
1014 		if (com_opts.home_since) {
1015 			home_since = com_opts.home_since;
1016 		}
1017 		if (com_opts.friends_since) {
1018 			friends_since = com_opts.friend_since;
1019 		}
1020 		if (com_opts.replies_since) {
1021 			replies_since = com_opts.replies_since;
1022 		}
1023 		if (com_opts.dm_since) {
1024 			dm_since = com_opts.dm_since;
1025 		}
1026 		
1027 		/*
1028 			we might still only pass in friends_* opts, so we translate those to home_*
1029 		*/
1030 		if (!home_count) { home_count = friends_count; }
1031 		if (!home_since) { home_since = friends_since; }
1032 		
1033 		if (com_opts.force) {
1034 			opts.force = true;
1035 		}
1036 	}
1037 	
1038 	this.getHomeTimeline(home_since, home_count, null, opts, onSuccess, onFailure);
1039 	this.getReplies(replies_since, replies_count, null, opts, onSuccess, onFailure);
1040 	this.getDirectMessages(dm_since, dm_count, null, opts, onSuccess, onFailure);
1041 };
1042 
1043 
1044 
1045 SpazTwit.prototype.search = function(query, since_id, results_per_page, page, lang, geocode, onSuccess, onFailure) {
1046 	if (!page) { page = 1;}
1047 	// if (!since_id) {
1048 	// 	if (this.data[SPAZCORE_SECTION_SEARCH].lastid && this.data[SPAZCORE_SECTION_SEARCH].lastid > 1) {
1049 	// 		since_id = this.data[SPAZCORE_SECTION_SEARCH].lastid;
1050 	// 	} else {
1051 	// 		since_id = 1;
1052 	// 	}
1053 	// }
1054 	if (!results_per_page) {
1055 		results_per_page = 50;
1056 	}
1057 	
1058 	
1059 	var data = {};
1060 	data['q']        = query;
1061 	data['rpp']      = results_per_page;
1062 	// data['since_id'] = since_id;
1063 	data['page']     = page;
1064 	if (lang) {
1065 		data['lang'] = lang;
1066 	}
1067 	if (geocode) {
1068 		data['geocode'] = geocode;
1069 	}
1070 	
1071 	var url = this.getAPIURL('search', data);
1072 	this._getTimeline({
1073 		'url':url,
1074 		'process_callback'	: this._processSearchTimeline,
1075 		'success_callback':onSuccess,
1076 		'failure_callback':onFailure,
1077 		'success_event_type': 'new_search_timeline_data',
1078 		'failure_event_type': 'error_search_timeline_data'
1079 	});
1080 	
1081 };
1082 
1083 /**
1084  * @private
1085  */
1086 SpazTwit.prototype._processSearchTimeline = function(search_result, opts, processing_opts) {	
1087 	/*
1088 		Search is different enough that we need to break it out and 
1089 		write a custom alternative to _processTimeline
1090 	*/
1091 	if (!processing_opts) { processing_opts = {}; }
1092 
1093 	/*
1094 		reset .newitems data properties
1095 	*/
1096 	this.data[SPAZCORE_SECTION_SEARCH].newitems = [];
1097 	
1098 	/*
1099 		put these results in the lastresultdata property
1100 	*/
1101 	this.data[SPAZCORE_SECTION_SEARCH].lastresultdata = search_result;
1102 	
1103 	/*
1104 		grab the array of items
1105 	*/
1106 	var ret_items = search_result.results;
1107 
1108 	if (ret_items.length > 0){
1109 		/*
1110 			we process each item, adding some attributes and generally making it cool
1111 		*/
1112 		for (var k=0; k<ret_items.length; k++) {
1113 			ret_items[k] = this._processSearchItem(ret_items[k], SPAZCORE_SECTION_SEARCH);
1114 		}
1115 
1116 		/*
1117 			sort items
1118 		*/
1119 		ret_items.sort(this._sortItemsAscending);
1120 					
1121 		/*
1122 			set lastid
1123 		*/ 
1124 		var lastid = ret_items[ret_items.length-1].id;
1125 		this.data[SPAZCORE_SECTION_SEARCH].lastid = lastid;
1126 		sc.helpers.dump('this.data['+SPAZCORE_SECTION_SEARCH+'].lastid:'+this.data[SPAZCORE_SECTION_SEARCH].lastid);
1127 
1128 		/*
1129 			add new items to data.newitems array
1130 		*/
1131 		this.data[SPAZCORE_SECTION_SEARCH].newitems = ret_items;
1132 
1133 		/*
1134 			concat new items onto data.items array
1135 		*/
1136 		this.data[SPAZCORE_SECTION_SEARCH].items = this.data[SPAZCORE_SECTION_SEARCH].items.concat(this.data[SPAZCORE_SECTION_SEARCH].newitems);
1137 		
1138 		this.data[SPAZCORE_SECTION_SEARCH].items = this.removeDuplicates(this.data[SPAZCORE_SECTION_SEARCH].items);
1139 		sch.debug('NOT removing extras from search -- we don\'t do that anymore');
1140 		// this.data[SPAZCORE_SECTION_SEARCH].items = this.removeExtraElements(this.data[SPAZCORE_SECTION_SEARCH].items, this.data[SPAZCORE_SECTION_SEARCH].max);
1141 
1142 
1143 		var search_info = {
1144 			'since_id'         : search_result.since_id,
1145 			'max_id'           : search_result.max_id,
1146 			'refresh_url'      : search_result.refresh_url,
1147 			'results_per_page' : search_result.results_per_page,
1148 			'next_page'        : search_result.next_page,
1149 			'completed_in'     : search_result.completed_in,
1150 			'page'             : search_result.page,
1151 			'query'            : search_result.query
1152 		};
1153 		
1154 		if (opts.success_callback) {
1155 			opts.success_callback(this.data[SPAZCORE_SECTION_SEARCH].newitems, search_info);
1156 		}
1157 		this.triggerEvent(opts.success_event_type, [this.data[SPAZCORE_SECTION_SEARCH].newitems, search_info]);
1158 		
1159 
1160 
1161 	} else { // no new items, but we should fire off success anyway
1162 		
1163 		if (opts.success_callback) {
1164 			opts.success_callback(null, []);
1165 		}
1166 		this.triggerEvent(opts.success_event_type, []);
1167 	}
1168 	
1169 };
1170 
1171 
1172 
1173 SpazTwit.prototype._processSearchItem = function(item, section_name) {
1174 	
1175 	item.SC_timeline_from = section_name;
1176 	if (this.username) {
1177 		item.SC_user_received_by = this.username;
1178 	}
1179 	// sc.helpers.dump(item);
1180 	
1181 	item.SC_is_search = true;
1182 
1183 	/*
1184 		add unix timestamp .SC_created_at_unixtime for easier date comparison
1185 	*/
1186 	if (!item.SC_created_at_unixtime) {
1187 		item.SC_created_at_unixtime = sc.helpers.httpTimeToInt(item.created_at);
1188 	}
1189 	
1190 	/*
1191 		add raw text .SC_raw_text for unmodified text
1192 	*/
1193 	if (!item.SC_text_raw) {
1194 		item.SC_text_raw = item.text;
1195 	}
1196 	
1197 	/*
1198 		add "in_reply_to_screen_name" if it does not exist
1199 	*/
1200 	if (!item.in_reply_to_screen_name && item.in_reply_to_user_id) {
1201 		/**
1202 		 * @todo get this from the Spaz code 
1203 		 */
1204 	}
1205 	
1206 	/*
1207 		add .SC_retrieved_unixtime
1208 	*/
1209 	if (!item.SC_retrieved_unixtime) {
1210 		item.SC_retrieved_unixtime = sc.helpers.getTimeAsInt();
1211 	}
1212 	
1213 	/*
1214 		normalize so we have as much user data in this object as possible
1215 	*/
1216 	item.user = {
1217 		'profile_image_url':item.profile_image_url,
1218 		'screen_name':item.from_user,
1219 		'id':item.from_user_id
1220 	};
1221 	
1222 	/*
1223 		The source info here is encoded differently
1224 	*/
1225 	item.source = sc.helpers.fromHTMLSpecialChars(item.source);
1226 	
1227 	
1228 	return item;
1229 };
1230 
1231 
1232 SpazTwit.prototype.getTrends = function(onSuccess, onFailure) {
1233 	var url = this.getAPIURL('trends');
1234 	this._getTimeline({
1235 		'url':url,
1236 		'process_callback'	: this._processTrends,
1237 		'success_callback':onSuccess,
1238 		'failure_callback':onFailure,
1239 		'success_event_type': 'new_trends_data',
1240 		'failure_event_type': 'error_trends_data'
1241 	});
1242 };
1243 
1244 
1245 /**
1246  * @private
1247  */
1248 SpazTwit.prototype._processTrends = function(trends_result, opts, processing_opts) {
1249 
1250 	if (!processing_opts) { processing_opts = {}; }
1251 	
1252 	/*
1253 		grab the array of items
1254 	*/
1255 	var ret_items = trends_result.trends;
1256 
1257 	if (ret_items.length > 0) {
1258 
1259 		for (var k=0; k<ret_items.length; k++) {
1260 			ret_items[k].searchterm = ret_items[k].name;
1261 			if ( /\s+/.test(ret_items[k].searchterm)) { // if there is whitespace, wrap in quotes
1262 				ret_items[k].searchterm = '"'+ret_items[k].searchterm+'"';
1263 			}
1264 		}
1265 		// jQuery().trigger(finished_event, [ret_items]);
1266 		
1267 		if (opts.success_callback) {
1268 			opts.success_callback(ret_items);
1269 		}
1270 		this.triggerEvent(opts.success_event_type, ret_items);
1271 		
1272 	}
1273 };
1274 
1275 
1276 /**
1277  * this is a general wrapper for timeline methods
1278  * @param {obj} opts a set of options for this method 
1279  * @private
1280  */
1281 SpazTwit.prototype._getTimeline = function(opts) {
1282 	
1283 	opts = sch.defaults({
1284 		'method':'GET',
1285 		'timeout':this.DEFAULT_TIMEOUT,
1286 		'url':null,
1287 		'data':null,
1288 		'process_callback':null,
1289 		'processing_opts':null,
1290 		'success_event_type':null,
1291 		'failure_event_type':null,
1292 		'success_callback':null,
1293 		'failure_callback':null
1294 	}, opts);
1295 	
1296 	/*
1297 		for closure references
1298 	*/
1299 	var stwit = this;
1300 	
1301 	var xhr = jQuery.ajax({
1302 		'timeout' :opts.timeout,
1303         'complete':function(xhr, msg){
1304             sc.helpers.dump(opts.url + ' complete:'+msg);
1305 			if (msg === 'timeout') {
1306 				// jQuery().trigger(opts.failure_event_type, [{'url':opts.url, 'xhr':xhr, 'msg':msg}]);
1307 				stwit.triggerEvent(opts.failure_event_type, {'url':opts.url, 'xhr':xhr, 'msg':msg});				
1308 			}
1309         },
1310         'error':function(xhr, msg, exc) {
1311 			sc.helpers.dump(opts.url + ' error:"'+msg+'"');
1312 			if (msg.toLowerCase().indexOf('timeout') !== -1) {
1313 				stwit.triggerEvent(document, opts.failure_event_type, {'url':opts.url, 'xhr':null, 'msg':msg});
1314 			} else if (xhr) {
1315 				if (!xhr.readyState < 4) {
1316 					sc.helpers.dump("Error:"+xhr.status+" from "+opts['url']);
1317 					if (xhr.responseText) {
1318 						try {
1319 							var data = sc.helpers.deJSON(xhr.responseText);
1320 						} catch(e) {
1321 							sc.helpers.dump(e.name + ":" + e.message);
1322 							data = xhr.responseText;
1323 						}
1324 					}
1325 				}
1326 				if (opts.failure_callback) {
1327 					opts.failure_callback(xhr, msg, exc);
1328 				}
1329 				if (opts.failure_event_type) {
1330 					sc.helpers.dump("opts.failure_event_type:"+opts.failure_event_type);
1331 					// jQuery().trigger(opts.failure_event_type, [{'url':opts.url, 'xhr':xhr, 'msg':msg}]);
1332 					stwit.triggerEvent(opts.failure_event_type, {'url':opts.url, 'xhr':xhr, 'msg':msg});
1333 					
1334 				}
1335 	
1336 	        } else {
1337                 sc.helpers.dump("Error:Unknown from "+opts['url']);
1338 				if (opts.failure_callback) {
1339 					opts.failure_callback(null, msg, exc);
1340 				}
1341 				if (opts.failure_event_type) {
1342 					// jQuery().trigger(opts.failure_event_type, [{'url':opts.url, 'xhr':null, 'msg':'Unknown Error'}]);
1343 					stwit.triggerEvent(opts.failure_event_type, {'url':opts.url, 'xhr':xhr, 'msg':'Unknown Error'});
1344 					
1345 				}
1346             }
1347 			// jQuery().trigger('spaztwit_ajax_error', [{'url':opts.url, 'xhr':xhr, 'msg':msg}]);
1348 			stwit.triggerEvent('spaztwit_ajax_error', {'url':opts.url, 'xhr':xhr, 'msg':msg});
1349 			
1350 			if (opts.processing_opts && opts.processing_opts.combined) {
1351 				sc.helpers.dump('adding to combined processing errors');
1352 				stwit.combined_errors.push( {'url':opts.url, 'xhr':xhr, 'msg':msg, 'section':opts.processing_opts.section} );
1353 				stwit.combined_finished[opts.processing_opts.section] = true;
1354 				sc.helpers.dump(stwit.combined_errors);
1355 				sc.helpers.dump(stwit.combined_finished);
1356 				if (opts.process_callback) {
1357 					opts.process_callback.call(stwit, [], opts, opts.processing_opts);
1358 				}
1359 			}
1360 			
1361         },
1362         'success':function(data) {
1363 			// sc.helpers.dump("Success! \n\n" + data);
1364 			sc.helpers.dump(opts.url + ' success!'+" data:"+data);
1365 			
1366 			try {
1367 				data = sc.helpers.deJSON(data);
1368 			} catch(e) {
1369 				stwit.triggerEvent(document, opts.failure_event_type, {'url':opts.url, 'xhr':xhr, 'msg':'Error decoding data from server'});
1370 			}
1371 
1372 			if (opts.process_callback) {
1373 				/*
1374 					using .call here and passing stwit as the first param
1375 					ensures that "this" inside the callback refers to our
1376 					SpazTwit object, and not the jQuery.Ajax object
1377 				*/
1378 				opts.process_callback.call(stwit, data, opts, opts.processing_opts);
1379 			} else {
1380 				if (opts.success_callback) {
1381 					sch.error('CALLING SUCCESS CALLBACK');
1382 					opts.success_callback(data);
1383 				}
1384 				// jQuery().trigger(opts.success_event_type, [data]);
1385 				stwit.triggerEvent(opts.success_event_type, data);
1386 			}			
1387         },
1388         'beforeSend':function(xhr){
1389 			sc.helpers.dump(opts.url + ' beforesend');
1390 			if (stwit.auth) {
1391 				sch.debug('signing request');
1392 				xhr.setRequestHeader('Authorization', stwit.auth.signRequest(opts.method, opts.url, opts.data));
1393 			} else {
1394 				sch.debug('NOT signing request -- no auth object provided');
1395 			}
1396         },
1397         'type': 	opts.method,
1398         'url': 		opts.url,
1399         'data': 	opts.data,
1400 		'dataType':'text'
1401 	});
1402 	
1403 	return xhr;
1404 };
1405 
1406 
1407 
1408 /**
1409  * general processor for timeline data 
1410  * @private
1411  */
1412 SpazTwit.prototype._processTimeline = function(section_name, ret_items, opts, processing_opts) {
1413 	
1414 	sch.debug(opts);
1415 	
1416 	if (!processing_opts) { processing_opts = {}; }
1417 
1418 	if (section_name !== SPAZCORE_SECTION_USER) { // the user timeline section isn't persistent
1419 		/*
1420 			reset .newitems data properties
1421 		*/
1422 		this.data[section_name].newitems = [];
1423 		
1424 	}
1425 	
1426 
1427 	if (ret_items.length > 0){
1428 		
1429 		var proc_items = [];
1430 		
1431 		/*
1432 			we process each item, adding some attributes and generally making it cool
1433 		*/
1434 		for (var k=0; k<ret_items.length; k++) {
1435 			if (ret_items[k]) {
1436 				proc_items.push(this._processItem(ret_items[k], section_name));
1437 			}
1438 		}
1439 		ret_items = proc_items;
1440 		proc_items = null;
1441 
1442 
1443 		/*
1444 			sort items
1445 		*/
1446 		ret_items.sort(this._sortItemsAscending);
1447 		
1448 		
1449 		if (section_name === SPAZCORE_SECTION_USER) { // special case -- we don't keep this data, just parse and fire it off
1450 
1451 			if (opts.success_callback) {
1452 				opts.success_callback(ret_items);
1453 			}
1454 
1455 			this.triggerEvent(opts.success_event_type, ret_items);
1456 			
1457 		} else { // this is a "normal" timeline that we want to be persistent
1458 			
1459 			if (opts.is_update_item) {
1460 				/*
1461 					we do not want this to be the lastid, instead remember it in combined.updates
1462 				*/
1463 				this.combinedTimelineAddUpdates(ret_items);
1464 			} else {
1465 				// set lastid
1466 				var lastid = ret_items[ret_items.length-1].id;
1467 				this.data[section_name].lastid = lastid;
1468 				sc.helpers.dump('this.data['+section_name+'].lastid:'+this.data[section_name].lastid);
1469 			}
1470 
1471 			// add new items to data.newitems array
1472 			this.data[section_name].newitems = ret_items;
1473 
1474 			this._addToSectionItems(section_name, this.data[section_name].newitems);
1475 
1476 
1477 			// @todo check length of data.items, and remove oldest extras if necessary
1478 			/*
1479 				@todo
1480 			*/
1481 
1482 			/*
1483 				Fire off the new section data event
1484 			*/
1485 			if (!processing_opts.combined) {
1486 				
1487 				if (opts.success_callback) {
1488 					opts.success_callback(this.data[section_name].newitems);
1489 				}
1490 				
1491 				this.triggerEvent(opts.success_event_type, this.data[section_name].items);
1492 			} else {
1493 				this.combined_finished[section_name] = true;
1494 				sc.helpers.dump("this.combined_finished["+section_name+"]:"+this.combined_finished[section_name]);
1495 			}
1496 			
1497 
1498 
1499 			/*
1500 				add on to newitems array for combined section
1501 			*/
1502 			this.data[SPAZCORE_SECTION_COMBINED].newitems = this.data[SPAZCORE_SECTION_COMBINED].newitems.concat(this.data[section_name].newitems);
1503 			
1504 		}
1505 
1506 
1507 	} else { // no new items, but we should fire off success anyway
1508 		if (!processing_opts.combined) {
1509 			// jQuery().trigger(finished_event, []);
1510 			if (opts.success_callback) {
1511 				opts.success_callback();
1512 			}
1513 			this.triggerEvent(opts.success_event_type);
1514 			
1515 		} else {
1516 			this.combined_finished[section_name] = true;
1517 		}
1518 	}
1519 	
1520 	/*
1521 		Fire off the new combined data event
1522 	*/
1523 	if (this.combinedTimelineFinished()) {
1524 		
1525 		/*
1526 			Remove those updates from combined newitems
1527 		*/
1528 		this.combinedNewItemsRemoveUpdates();
1529 
1530 		/*
1531 			we do this stuff here to avoid processing repeatedly
1532 		*/
1533 		
1534 		this._addToSectionItems(SPAZCORE_SECTION_COMBINED, this.data[SPAZCORE_SECTION_COMBINED].newitems, this._sortItemsByDateAsc);
1535 		
1536 		// sort these items -- the timelines can be out of order when combined
1537 
1538 		
1539 		// sc.helpers.dump('Removing duplicates in '+SPAZCORE_SECTION_COMBINED+' newitems');
1540 		// 
1541 		this.data[SPAZCORE_SECTION_COMBINED].newitems = this._cleanupItemArray(this.data[SPAZCORE_SECTION_COMBINED].newitems, this.data[SPAZCORE_SECTION_COMBINED].max, this._sortItemsByDateAsc);
1542 		
1543 		if (this.combinedTimelineHasErrors()) {
1544 			if (opts.failure_callback) {
1545 				opts.failure_callback(this.combined_errors);
1546 			}
1547 			
1548 			this.triggerEvent('error_combined_timeline_data', this.combined_errors);
1549 		}
1550 		
1551 		if (opts.success_callback) {
1552 			opts.success_callback(this.data[SPAZCORE_SECTION_COMBINED].newitems);
1553 		}
1554 		sch.debug('this.data[SPAZCORE_SECTION_COMBINED].newitems has '+this.data[SPAZCORE_SECTION_COMBINED].newitems.length+' items');
1555 		this.triggerEvent('new_combined_timeline_data', this.data[SPAZCORE_SECTION_COMBINED].newitems);
1556 		this.data[SPAZCORE_SECTION_COMBINED].newitems = []; // reset combined.newitems
1557 		this.initializeCombinedTracker();
1558 	}
1559 };
1560 
1561 
1562 /**
1563  * Adds an array of items to the .items property of the appropriate section, then
1564  * removes dupes, extras, and optionally sorts the section items
1565  * @param {string} section_name
1566  * @param {array}  arr  an array of items
1567  * @param {function}  sortfunc - optional 
1568  */
1569 SpazTwit.prototype._addToSectionItems = function(section_name, arr, sortfunc) {
1570 	// concat new items onto data.items array
1571 	var data = this.data[section_name];
1572 	data.items = this._cleanupItemArray(data.items.concat(arr), null, sortfunc);
1573 };
1574 
1575 /**
1576  * Sorts (optionally), removes dupes, and removes extra items from a given
1577  * array of section items
1578  * 
1579  * @param {array} arr
1580  * @param {max} integer
1581  * @param {func} sortfunc - optional
1582  * 
1583  * @return {array} 
1584  */
1585 SpazTwit.prototype._cleanupItemArray = function(arr, max, sortfunc) {
1586 	if (sortfunc) {
1587 		arr = arr.sort(sortfunc);
1588 	}
1589 	arr = this.removeDuplicates(arr);
1590 	sch.debug('NOT removing extras -- we don\'t do that anymore');
1591 	// arr = this.removeExtraElements(arr, max);
1592 	return arr;
1593 };
1594 
1595 /**
1596  * This modifies a Twitter post, adding some properties. All new properties are
1597  * prepended with "SC_"
1598  * 
1599  * this executes within the jQuery.each scope, so this === the item 
1600  */
1601 SpazTwit.prototype._processItem = function(item, section_name) {
1602 	
1603 	item.SC_timeline_from = section_name;
1604 	if (this.username) {
1605 		item.SC_user_received_by = this.username;
1606 	}
1607 	
1608 	/*
1609 		is reply? Then add .SC_is_reply
1610 	*/
1611 	if ( (item.in_reply_to_screen_name && item.SC_user_received_by) ) {
1612 		if (item.in_reply_to_screen_name.toLowerCase() === item.SC_user_received_by.toLowerCase() ) {
1613 			item.SC_is_reply = true;
1614 		}
1615 	}
1616 	
1617 	/*
1618 		is an official API retweet? then add .SC_is_retweet
1619 	*/
1620 	if ( item.retweeted_status ) {
1621 		item.SC_is_retweet = true;
1622 	}
1623 	
1624 	/*
1625 		If it comes from the replies timeline, it's a reply (aka a mention)
1626 	*/
1627 	if (section_name === SPAZCORE_SECTION_REPLIES) {
1628 		item.SC_is_reply = true;
1629 	}
1630 	
1631 	/*
1632 		Does it contain my name? then it's a reply
1633 	*/
1634 	if (this.username && sc.helpers.containsScreenName(item.text, this.username) ) {
1635 		item.SC_is_reply = true;
1636 	}
1637 	
1638 	/*
1639 		is dm?
1640 	*/
1641 	if (item.recipient_id && item.sender_id) {
1642 		item.SC_is_dm = true;
1643 	}
1644 	
1645 	
1646 	/*
1647 		add unix timestamp .SC_created_at_unixtime for easier date comparison
1648 	*/
1649 	if (!item.SC_created_at_unixtime) {
1650 		item.SC_created_at_unixtime = sc.helpers.httpTimeToInt(item.created_at);
1651 	}
1652 	
1653 	/*
1654 		add raw text .SC_raw_text for unmodified text
1655 	*/
1656 	if (!item.SC_text_raw) {
1657 		item.SC_text_raw = item.text;
1658 	}
1659 	
1660 	/*
1661 		add "in_reply_to_screen_name" if it does not exist
1662 	*/
1663 	if (!item.in_reply_to_screen_name && item.in_reply_to_user_id) {
1664 		/**
1665 		 * @todo get this from the Spaz code 
1666 		 */
1667 	}
1668 	
1669 	/*
1670 		add .SC_retrieved_unixtime
1671 	*/
1672 	if (!item.SC_retrieved_unixtime) {
1673 		item.SC_retrieved_unixtime = sc.helpers.getTimeAsInt();
1674 	}
1675 	
1676 	return item;
1677 };
1678 
1679 
1680 
1681 /**
1682  * This modifies a Twitter post, adding some properties. All new properties are
1683  * prepended with "SC_"
1684  * 
1685  * this executes within the jQuery.each scope, so this === the item 
1686  */
1687 SpazTwit.prototype._processUser = function(item, section_name) {
1688 	
1689 	item.SC_timeline_from = section_name;
1690 	if (this.username) {
1691 		item.SC_user_received_by = this.username;
1692 	}
1693 	
1694 	
1695 	if (section_name === SPAZCORE_SECTION_FOLLOWERSLIST) {
1696 		item.SC_is_follower;
1697 	}
1698 	if (section_name === SPAZCORE_SECTION_FRIENDLIST) {
1699 		item.SC_is_followed;
1700 	}
1701 	
1702 	/*
1703 		add unix timestamp .SC_created_at_unixtime for easier date comparison
1704 	*/
1705 	if (!item.SC_created_at_unixtime) {
1706 		item.SC_created_at_unixtime = sc.helpers.httpTimeToInt(item.created_at);
1707 	}
1708 	
1709 	/*
1710 		add .SC_retrieved_unixtime
1711 	*/
1712 	if (!item.SC_retrieved_unixtime) {
1713 		item.SC_retrieved_unixtime = sc.helpers.getTimeAsInt();
1714 	}
1715 	
1716 	return item;
1717 };
1718 
1719 
1720 /**
1721  * returns the header string for oAuth Echo usage
1722  */
1723 SpazTwit.prototype.getEchoHeader = function(opts) {
1724 	var url;
1725 	if (opts && opts.verify_url) {
1726 		url = opts.verify_url;
1727 	} else {
1728 		url = this.getAPIURL('verify_credentials');
1729 	}
1730 	
1731 	var method = 'GET';
1732 
1733 	var auth_header = this.auth.signRequest(method, url, null);
1734 
1735 	return auth_header;
1736 };
1737 
1738 
1739 /**
1740  * this is a general wrapper for non-timeline methods on the Twitter API. We
1741  * use this to call methods that will return a single response 
1742  * 
1743  * @param {obj} opts a set of options for this method 
1744  * @param {string} opts.url The url for the request
1745  * @param {string} [opts.method] the HTTP method to use. default is POST
1746  * @param {number} [opts.timeout] the timeout for the request. default is 60 seconds
1747  * @param {object} [opts.data] data to pass with the request
1748  * @param {string} [opts.username]
1749  * @param {string} [opts.password]
1750  * @param {function} [opts.process_callback] a function to call on the retured data for extra processing on success
1751  * @param {string} [opts.success_event_type] the event to trigger on success
1752  * @param {string} [opts.failure_event_type] the event to trigger on failure
1753  * @param {function} [opts.success_callback] a callback to fire on success
1754  * @param {function} [opts.failure_callback] a callback to fire on failure
1755  */
1756 SpazTwit.prototype._callMethod = function(opts) {
1757 	
1758 	opts = sch.defaults({
1759 		'method':'POST',
1760 		'timeout':this.DEFAULT_TIMEOUT,
1761 		'url':null,
1762 		'data':null,
1763 		'process_callback':null,
1764 		'success_event_type':null,
1765 		'failure_event_type':null,
1766 		'success_callback':null,
1767 		'failure_callback':null
1768 	}, opts);
1769 	
1770 	var method;
1771 	
1772 	/*
1773 		for closure references
1774 	*/
1775 	var stwit = this;
1776 	
1777 	if (opts.method) {
1778 		method = opts.method;
1779 	} else {
1780 		method = 'POST';
1781 	}
1782 	
1783 	var xhr = jQuery.ajax({
1784 		'timeout' :this.opts.timeout,
1785 	    'complete':function(xhr, msg){
1786 	        sc.helpers.dump(opts.url + ' complete:'+msg);
1787 	    },
1788 	    'error':function(xhr, msg, exc) {
1789 			sc.helpers.error(opts.url + ' error:'+msg);
1790 	        if (xhr) {
1791 				if (!xhr.readyState < 4) {
1792 					sc.helpers.dump("Error:"+xhr.status+" from "+opts['url']);
1793 					if (xhr.responseText) {
1794 						try {
1795 							var data = sc.helpers.deJSON(xhr.responseText);
1796 						} catch(e) {
1797 							sc.helpers.dump(e.name + ":" + e.message);
1798 							data = xhr.responseText;
1799 						}
1800 					}
1801 				}
1802 				if (opts.failure_callback) {
1803 					opts.failure_callback(xhr, msg, exc);
1804 				}
1805 				if (opts.failure_event_type) {
1806 					// jQuery().trigger(opts.failure_event_type, [{'url':opts.url, 'xhr':xhr, 'msg':msg}]);
1807 					stwit.triggerEvent(opts.failure_event_type, {'url':opts.url, 'xhr':xhr, 'msg':msg});
1808 				}
1809 	
1810 	        } else {
1811 	            sc.helpers.dump("Error:Unknown from "+opts['url']);
1812 				if (opts.failure_callback) {
1813 					opts.failure_callback(null, msg, exc);
1814 				}
1815 				if (opts.failure_event_type) {
1816 					// jQuery().trigger(opts.failure_event_type, [{'url':opts.url, 'xhr':null, 'msg':'Unknown Error'}]);
1817 					stwit.triggerEvent(opts.failure_event_type, {'url':opts.url, 'xhr':null, 'msg':'Unknown Error'});
1818 
1819 				}
1820 	        }
1821 			// jQuery().trigger('spaztwit_ajax_error', [{'url':opts.url, 'xhr':xhr, 'msg':msg}]);
1822 			stwit.triggerEvent('spaztwit_ajax_error', {'url':opts.url, 'xhr':xhr, 'msg':msg});
1823 	    },
1824 	    'success':function(data) {
1825 			sc.helpers.error(opts.url + ' success');
1826 			data = sc.helpers.deJSON(data);
1827 			if (opts.process_callback) {
1828 				/*
1829 					using .call here and passing stwit as the first param
1830 					ensures that "this" inside the callback refers to our
1831 					SpazTwit object, and not the jQuery.Ajax object
1832 				*/
1833 				opts.process_callback.call(stwit, data, opts);
1834 			} else {
1835 				if (opts.success_callback) {
1836 					opts.success_callback(data);
1837 				}
1838 				// jQuery().trigger(opts.success_event_type, [data]);
1839 				stwit.triggerEvent(opts.success_event_type, data);
1840 				
1841 			}
1842 	    },
1843 	    'beforeSend':function(xhr){
1844 			sc.helpers.dump(opts.url + ' beforesend');
1845 			if (stwit.auth) {
1846 				sch.debug('signing request');
1847 				xhr.setRequestHeader('Authorization', stwit.auth.signRequest(method, opts.url, opts.data));
1848 			} else {
1849 				sch.debug('NOT signing request -- no auth object provided');
1850 			}
1851 	    },
1852 	    'type': method,
1853 	    'url' : opts.url,
1854 		'data': opts.data,
1855 		'dataType':'text'
1856 	});
1857 	return xhr;
1858 };
1859 
1860 
1861 
1862 SpazTwit.prototype.getUser = function(user_id, onSuccess, onFailure) {
1863 	var data = {};
1864 	data['id'] = user_id;
1865 	
1866 	var url = this.getAPIURL('show_user', data);
1867 	
1868 	var opts = {
1869 		'url':url,
1870 		// 'process_callback': this._processUserData,
1871 		'success_event_type':'get_user_succeeded',
1872 		'failure_event_type':'get_user_failed',
1873 		'success_callback':onSuccess,
1874 		'failure_callback':onFailure,
1875 		'method':'GET'
1876 	};
1877 
1878 	/*
1879 		Perform a request and get true or false back
1880 	*/
1881 	var xhr = this._callMethod(opts);
1882 };
1883 
1884 
1885 
1886 SpazTwit.prototype.getFriendsList = function() {
1887 	
1888 	var url = this.getAPIURL('friendslist');
1889 	
1890 	var opts = {
1891 		'url':url,
1892 		'process_callback': this._processFriendsList,
1893 		'success_event_type':'get_friendslist_succeeded',
1894 		'failure_event_type':'get_friendslist_failed',
1895 		'method':'GET'
1896 	};
1897 
1898 	var xhr = this._getTimeline(opts);
1899 };
1900 /**
1901  * @private
1902  */
1903 SpazTwit.prototype._processFriendsList = function(ret_items, opts, processing_opts) {
1904 	this._processUserList(SPAZCORE_SECTION_FRIENDLIST, ret_items, opts.success_event_type, processing_opts);
1905 };
1906 
1907 
1908 
1909 
1910 
1911 
1912 SpazTwit.prototype.getFollowersList = function() {
1913 	var url = this.getAPIURL('followerslist');
1914 	
1915 	var opts = {
1916 		'url':url,
1917 		'process_callback': this._processFollowersList,
1918 		'success_event_type':'get_followerslist_succeeded',
1919 		'failure_event_type':'get_followerslist_failed',
1920 		'method':'GET'
1921 	};
1922 
1923 	var xhr = this._getTimeline(opts);
1924 };
1925 /**
1926  * @private
1927  */
1928 SpazTwit.prototype._processFollowersList = function(ret_items, opts, processing_opts) {
1929 	this._processUserList(SPAZCORE_SECTION_FOLLOWERSLIST, ret_items, opts.success_event_type, processing_opts);
1930 };
1931 
1932 
1933 
1934 /**
1935  * general processor for timeline data 
1936  * @private
1937  */
1938 SpazTwit.prototype._processUserList = function(section_name, ret_items, opts, processing_opts) {
1939 	
1940 	if (!processing_opts) { processing_opts = {}; }
1941 
1942 	if (ret_items.length > 0){
1943 		/*
1944 			we process each item, adding some attributes and generally making it cool
1945 		*/
1946 		for (var k=0; k<ret_items.length; k++) {
1947 			ret_items[k] = this._processUser(ret_items[k], section_name);
1948 			sch.dump(ret_items[k]);
1949 		}
1950 
1951 		/*
1952 			sort items
1953 		*/
1954 		ret_items.sort(this._sortItemsAscending);
1955 		
1956 			
1957 		// set lastid
1958 		var lastid = ret_items[ret_items.length-1].id;
1959 		this.data[section_name].lastid = lastid;
1960 		sc.helpers.dump('this.data['+section_name+'].lastid:'+this.data[section_name].lastid);
1961 
1962 		// add new items to data.newitems array
1963 		this.data[section_name].newitems = ret_items;
1964 
1965 		this._addToSectionItems(section_name, this.data[section_name].newitems);
1966 
1967 		if (opts.success_callback) {
1968 			opts.success_callback(this.data[section_name].newitems);
1969 		}
1970 		this.triggerEvent(opts.success_event_type,this.data[section_name].newitems );
1971 
1972 	} else { // no new items, but we should fire off success anyway
1973 		if (opts.success_callback) {
1974 			opts.success_callback();
1975 		}
1976 		this.triggerEvent(opts.success_event_type);
1977 	}
1978 
1979 };
1980 
1981 
1982 SpazTwit.prototype.addFriend = function(user_id, onSuccess, onFailure) {
1983 	var data = {};
1984 	data['id'] = user_id;
1985 	
1986 	var url = this.getAPIURL('friendship_create', data);
1987 	
1988 	var opts = {
1989 		'url':url,
1990 		'success_event_type':'create_friendship_succeeded',
1991 		'failure_event_type':'create_friendship_failed',
1992 		'success_callback':onSuccess,
1993 		'failure_callback':onFailure,
1994 		'data':data
1995 	};
1996 
1997 	/*
1998 		Perform a request and get true or false back
1999 	*/
2000 	var xhr = this._callMethod(opts);
2001 };
2002 SpazTwit.prototype.removeFriend = function(user_id, onSuccess, onFailure) {
2003 	var data = {};
2004 	data['id'] = user_id;
2005 	
2006 	var url = this.getAPIURL('friendship_destroy', data);
2007 	
2008 	var opts = {
2009 		'url':url,
2010 		'success_event_type':'destroy_friendship_succeeded',
2011 		'failure_event_type':'destroy_friendship_failed',
2012 		'success_callback':onSuccess,
2013 		'failure_callback':onFailure,
2014 		'data':data
2015 	};
2016 
2017 	/*
2018 		Perform a request and get true or false back
2019 	*/
2020 	var xhr = this._callMethod(opts);
2021 
2022 };
2023 
2024 /**
2025  * @param {string|number} target_id the target user id, or screen name if prefixed with a "@" 
2026  * @param {string|number} [source_id] the surce user id, or screen name if prefixed with a "@" 
2027  * @param {function} [onSuccess] success callback
2028  * @param {function} [onFailure] failure callback
2029  */
2030 SpazTwit.prototype.showFriendship = function(target_id, source_id, onSuccess, onFailure) {
2031 	var data = {};
2032 	
2033 	if (sch.isString(target_id) && target_id.indexOf('@')===0) {
2034 		data['target_screen_name'] = target_id.substr(1);
2035 	} else {
2036 		data['target_id'] = target_id;
2037 	}
2038 	
2039 	if (source_id) {
2040 		if (sch.isString(source_id) && source_id.indexOf('@')===0) {
2041 			data['source_screen_name'] = source_id.substr(1);
2042 		} else {
2043 			data['source_id'] = source_id;
2044 		}
2045 		
2046 	}
2047 	
2048 	
2049 	var url = this.getAPIURL('friendship_show', data);
2050 	
2051 	var opts = {
2052 		'url':url,
2053 		'method':'GET',
2054 		'success_event_type':'show_friendship_succeeded',
2055 		'failure_event_type':'show_friendship_failed',
2056 		'success_callback':onSuccess,
2057 		'failure_callback':onFailure,
2058 		'data':data
2059 	};
2060 
2061 	/*
2062 		Perform a request and get true or false back
2063 	*/
2064 	var xhr = this._callMethod(opts);
2065 
2066 };
2067 
2068 SpazTwit.prototype.getIncomingFriendships = function(cursor, onSuccess, onFailure) {
2069 	var data = {};
2070 	if (!cursor) {
2071 		cursor = -1;
2072 	}
2073 	data['cursor'] = cursor;
2074 	
2075 	var url = this.getAPIURL('friendship_incoming', data);
2076 	
2077 	var opts = {
2078 		'url':url,
2079 		'method':'GET',
2080 		'success_event_type':'get_incoming_friendships_succeeded',
2081 		'failure_event_type':'get_incoming_friendships_failed',
2082 		'success_callback':onSuccess,
2083 		'failure_callback':onFailure,
2084 		'data':data
2085 	};
2086 
2087 	/*
2088 		Perform a request and get true or false back
2089 	*/
2090 	var xhr = this._callMethod(opts);
2091 
2092 };
2093 
2094 SpazTwit.prototype.getOutgoingFriendships = function(cursor, onSuccess, onFailure) {
2095 	var data = {};
2096 	if (!cursor) {
2097 		cursor = -1;
2098 	}
2099 	data['cursor'] = cursor;
2100 	
2101 	var url = this.getAPIURL('friendship_outgoing', data);
2102 	
2103 	var opts = {
2104 		'url':url,
2105 		'method':'GET',
2106 		'success_event_type':'get_outgoing_friendships_succeeded',
2107 		'failure_event_type':'get_outgoing_friendships_failed',
2108 		'success_callback':onSuccess,
2109 		'failure_callback':onFailure,
2110 		'data':data
2111 	};
2112 
2113 	/*
2114 		Perform a request and get true or false back
2115 	*/
2116 	var xhr = this._callMethod(opts);
2117 
2118 };
2119 
2120 SpazTwit.prototype.getFriendsGraph = function(user_id, cursor, onSuccess, onFailure) {
2121 	var data = {};
2122 	if (!cursor) {
2123 		cursor = -1;
2124 	}
2125 	data['cursor'] = cursor;
2126 	data['user_id'] = user_id;
2127 	
2128 	var url = this.getAPIURL('graph_friends', data);
2129 	
2130 	var opts = {
2131 		'url':url,
2132 		'method':'GET',
2133 		'success_event_type':'get_friends_graph_succeeded',
2134 		'failure_event_type':'get_friends_graph_failed',
2135 		'success_callback':onSuccess,
2136 		'failure_callback':onFailure,
2137 		'data':data
2138 	};
2139 
2140 	/*
2141 		Perform a request and get true or false back
2142 	*/
2143 	var xhr = this._callMethod(opts);
2144 
2145 };
2146 
2147 SpazTwit.prototype.getFollowersGraph = function(user_id, cursor, onSuccess, onFailure) {
2148 	var data = {};
2149 	if (!cursor) {
2150 		cursor = -1;
2151 	}
2152 	data['cursor'] = cursor;
2153 	data['user_id'] = user_id;
2154 	
2155 	var url = this.getAPIURL('graph_followers', data);
2156 	
2157 	var opts = {
2158 		'url':url,
2159 		'method':'GET',
2160 		'success_event_type':'get_followers_graph_succeeded',
2161 		'failure_event_type':'get_followers_graph_failed',
2162 		'success_callback':onSuccess,
2163 		'failure_callback':onFailure,
2164 		'data':data
2165 	};
2166 
2167 	/*
2168 		Perform a request and get true or false back
2169 	*/
2170 	var xhr = this._callMethod(opts);
2171 
2172 };
2173 
2174 SpazTwit.prototype.block = function(user_id, onSuccess, onFailure) {
2175 	var data = {};
2176 	data['id'] = user_id;
2177 	
2178 	var url = this.getAPIURL('block_create', data);
2179 	
2180 	var opts = {
2181 		'url':url,
2182 		'success_event_type':'create_block_succeeded',
2183 		'failure_event_type':'create_block_failed',
2184 		'success_callback':onSuccess,
2185 		'failure_callback':onFailure,
2186 		'data':data
2187 	};
2188 
2189 	/*
2190 		Perform a request and get true or false back
2191 	*/
2192 	var xhr = this._callMethod(opts);
2193 };
2194 
2195 SpazTwit.prototype.unblock = function(user_id, onSuccess, onFailure) {
2196 	var data = {};
2197 	data['id'] = user_id;
2198 	
2199 	var url = this.getAPIURL('block_destroy', data);
2200 	
2201 	var opts = {
2202 		'url':url,
2203 		'success_event_type':'destroy_block_succeeded',
2204 		'failure_event_type':'destroy_block_failed',
2205 		'success_callback':onSuccess,
2206 		'failure_callback':onFailure,
2207 		'data':data
2208 	};
2209 
2210 	/*
2211 		Perform a request and get true or false back
2212 	*/
2213 	var xhr = this._callMethod(opts);
2214 
2215 };
2216 
2217 SpazTwit.prototype.follow = function(user_id, onSuccess, onFailure) { // to add notification
2218 	var data = {};
2219 	data['id'] = user_id;
2220 	
2221 	var url = this.getAPIURL('follow', data);
2222 	
2223 	var opts = {
2224 		'url':url,
2225 		'username':this.username,
2226 		'password':this.password,
2227 		'success_event_type':'follow_succeeded',
2228 		'failure_event_type':'follow_failed',
2229 		'success_callback':onSuccess,
2230 		'failure_callback':onFailure,
2231 		'data':data
2232 	};
2233 
2234 	/*
2235 		Perform a request and get true or false back
2236 	*/
2237 	var xhr = this._callMethod(opts);
2238     
2239 };
2240 
2241 SpazTwit.prototype.unfollow = function(user_id, onSuccess, onFailure) { // to remove notification
2242 	var data = {};
2243 	data['id'] = user_id;
2244 	
2245 	var url = this.getAPIURL('unfollow', data);
2246 	
2247 	var opts = {
2248 		'url':url,
2249 		'username':this.username,
2250 		'password':this.password,
2251 		'success_event_type':'unfollow_succeeded',
2252 		'failure_event_type':'unfollow_failed',
2253 		'success_callback':onSuccess,
2254 		'failure_callback':onFailure,
2255 		'data':data
2256 	};
2257 
2258 	/*
2259 		Perform a request and get true or false back
2260 	*/
2261 	var xhr = this._callMethod(opts);
2262     
2263 };
2264 
2265 
2266 SpazTwit.prototype.update = function(status, source, in_reply_to_status_id, onSuccess, onFailure) {
2267 
2268 	var url = this.getAPIURL('update');
2269 	
2270 	var data = {};
2271 	if (in_reply_to_status_id) {
2272 		data.in_reply_to_status_id = in_reply_to_status_id;
2273 	}
2274 	if (source) {
2275 		data.source = source;
2276 	} else {
2277 		data.source = this.source;
2278 	}
2279 	data.status = status;
2280 	
2281 	var opts = {
2282 		'url':url,
2283 		'data':data,
2284 		'process_callback': this._processUpdateReturn,
2285 		'success_callback':onSuccess,
2286 		'failure_callback':onFailure,
2287 		'success_event_type':'update_succeeded',
2288 		'failure_event_type':'update_failed'
2289 	};
2290 
2291 	/*
2292 		Perform a request and get true or false back
2293 	*/
2294 	var xhr = this._callMethod(opts);
2295 
2296 	
2297 };
2298 
2299 SpazTwit.prototype._processUpdateReturn = function(data, opts) {
2300 	
2301 	/*
2302 		Add this to the HOME section and fire off the event when done
2303 	*/	
2304 	opts.is_update_item = true;
2305 	this._processTimeline(SPAZCORE_SECTION_HOME, [data], opts);
2306 };
2307 
2308 /**
2309  * destroy/delete a status
2310  * @param {Number|String} id the id of the status 
2311  */
2312 SpazTwit.prototype.destroy = function(id, onSuccess, onFailure) {
2313 	var data = {};
2314 	data['id'] = id;
2315 	
2316 	var url = this.getAPIURL('destroy_status', data);
2317 	
2318 	var opts = {
2319 		'url':url,
2320 		'data':data,
2321 		'success_event_type':'destroy_status_succeeded',
2322 		'success_callback':onSuccess,
2323 		'failure_callback':onFailure,
2324 		'failure_event_type':'destroy_status_failed'
2325 	};
2326 
2327 	/*
2328 		Perform a request and get true or false back
2329 	*/
2330 	var xhr = this._callMethod(opts);
2331 };
2332 
2333 /**
2334  * destroy/delete a direct message
2335  * @param {Number|String} id the id of the status 
2336  */
2337 SpazTwit.prototype.destroyDirectMessage = function(id, onSuccess, onFailure) {
2338 	var data = {};
2339 	data['id'] = id;
2340 	
2341 	var url = this.getAPIURL('dm_destroy', data);
2342 	
2343 	var opts = {
2344 		'url':url,
2345 		'data':data,
2346 		'success_event_type':'destroy_dm_succeeded',
2347 		'success_callback':onSuccess,
2348 		'failure_callback':onFailure,
2349 		'failure_event_type':'destroy_dm_failed'
2350 	};
2351 
2352 	/*
2353 		Perform a request and get true or false back
2354 	*/
2355 	var xhr = this._callMethod(opts);
2356 };
2357 
2358 
2359 SpazTwit.prototype.getOne = function(id, onSuccess, onFailure) {
2360 	var data = {};
2361 	data['id'] = id;
2362 	
2363 	var url = this.getAPIURL('show', data);
2364 	
2365 	var opts = {
2366 		'url':url,
2367 		'process_callback': this._processOneItem,
2368 		'success_event_type':'get_one_status_succeeded',
2369 		'success_callback':onSuccess,
2370 		'failure_callback':onFailure,
2371 		'failure_event_type':'get_one_status_failed',
2372 		'method':'GET'
2373 	};
2374 
2375 	/*
2376 		Perform a request and get true or false back
2377 	*/
2378 	var xhr = this._callMethod(opts);
2379 };
2380 
2381 
2382 SpazTwit.prototype._processOneItem = function(data, opts) {
2383 	
2384 	/*
2385 		this item needs to be added to the friends timeline
2386 		so we can avoid dupes
2387 	*/
2388 	data = this._processItem(data);
2389 	if (opts.success_callback) {
2390 		opts.success_callback(data);
2391 	}
2392 	this.triggerEvent(opts.success_event_type, data);
2393 	
2394 };
2395 
2396 // Retweet API
2397 
2398 /*
2399  * Retweets a tweet.
2400  * id: the numeric id of a tweet
2401  */
2402  
2403 SpazTwit.prototype.retweet = function(id, onSuccess, onFailure) {
2404 	var data = {};
2405 	data['id'] = id;
2406 	
2407 	var url = this.getAPIURL('retweet', data);
2408 	
2409 	var opts = {
2410 		'url' : url,
2411 		'username' : this.username,
2412 		'password' : this.password,
2413 		'success_event_type' : 'retweet_succeeded',
2414 		'failure_event_type' : 'retweet_failed',
2415 		'success_callback' : onSuccess,
2416 		'failure_callback' : onFailure,
2417 		'data' : data
2418 	};
2419 	
2420 	var xhr = this._callMethod(opts);
2421 };
2422 
2423 /*
2424  * Gets up to 100 of the latest retweets of a tweet.
2425  * id: the tweet to get retweets of
2426  * count: the number of retweets to get
2427  */
2428 
2429 SpazTwit.prototype.getRetweets = function(id, count) {
2430 	var url = this.getAPIURL('retweets', {
2431 		'id' : id,
2432 		'count' : count
2433 	});
2434 	
2435 	var opts = {
2436 		'url' : url,
2437 		'username' : this.username,
2438 		'password' : this.password,
2439 		'success_event_type' : 'get_retweets_succeeded',
2440 		'failure_event_Type' : 'get_retweets_failed',
2441 		'method' : 'GET'
2442 	};
2443 	
2444 	var xhr = this._getTimeline(opts);
2445 };
2446 
2447 /*
2448  * Returns up to 200 of the most recent retweets by the user
2449  * since: the numeric id of the tweet serving as a floor
2450  * max: the numeric id of the tweet serving as a ceiling
2451  * count: the number of tweets to return. Cannot be over 200.
2452  * page: the page of results to return.
2453  */
2454  
2455 SpazTwit.prototype.retweetedByMe = function(since, max, count, page){
2456 	var params = {};
2457 	if(since != null){
2458 		params['since_id'] = since;
2459 	}
2460 	if(max != null){
2461 		params['max_id'] = max;
2462 	}
2463 	if(count == null){
2464 		count = 20;
2465 	}
2466 	params['count'] = count;
2467 	if(page == null){
2468 		page = 1;
2469 	}
2470 	params['page'] = page;
2471 	var url = this.getAPIURL('retweeted_by_me', params);
2472 	
2473 	var opts = {
2474 		'url' : url,
2475 		'username' : this.username,
2476 		'password' : this.password,
2477 		'success_event_type' : 'retweeted_by_me_succeeded',
2478 		'failure_event_type' : 'retweeted_by_me_failed',
2479 		'method' : 'GET'
2480 	};
2481 	
2482 	var xhr = this._getTimeline(opts);
2483 };
2484 
2485 /*
2486  * Returns up to 200 of the most recent retweets by the user's friends
2487  * since: the numeric id of the tweet serving as a floor
2488  * max: the numeric id of the tweet serving as a ceiling
2489  * count: the number of tweets to return. Cannot be over 200.
2490  * page: the page of results to return.
2491  */
2492  
2493 SpazTwit.prototype.retweetedToMe = function(since, max, count, page){
2494 	var params = {};
2495 	if(since != null){
2496 		params['since_id'] = since;
2497 	}
2498 	if(max != null){
2499 		params['max_id'] = max;
2500 	}
2501 	if(count == null){
2502 		count = 20;
2503 	}
2504 	params['count'] = count;
2505 	if(page == null){
2506 		page = 1;
2507 	}
2508 	params['page'] = page;
2509 	var url = this.getAPIURL('retweeted_to_me', params);
2510 	
2511 	var opts = {
2512 		'url' : url,
2513 		'username' : this.username,
2514 		'password' : this.password,
2515 		'success_event_type' : 'retweeted_to_me_succeeded',
2516 		'failure_event_type' : 'retweeted_to_me_failed',
2517 		'method' : 'GET'
2518 	};
2519 	
2520 	var xhr = this._getTimeline(opts);
2521 };
2522 
2523 /*
2524  * Returns up to 200 of the most recent retweets of the user's tweets
2525  * since: the numeric id of the tweet serving as a floor
2526  * max: the numeric id of the tweet serving as a ceiling
2527  * count: the number of tweets to return. Cannot be over 200.
2528  * page: the page of results to return.
2529  */
2530  
2531 SpazTwit.prototype.retweetsOfMe = function(since, max, count, page){
2532 	var params = {};
2533 	if(since != null){
2534 		params['since_id'] = since;
2535 	}
2536 	if(max != null){
2537 		params['max_id'] = max;
2538 	}
2539 	if(count == null){
2540 		count = 20;
2541 	}
2542 	params['count'] = count;
2543 	if(page == null){
2544 		page = 1;
2545 	}
2546 	params['page'] = page;
2547 	var url = this.getAPIURL('retweets_of_me', params);
2548 	
2549 	var opts = {
2550 		'url' : url,
2551 		'username' : this.username,
2552 		'password' : this.password,
2553 		'success_event_type' : 'retweets_of_me_succeeded',
2554 		'failure_event_type' : 'retweets_of_me_failed',
2555 		'method' : 'GET'
2556 	};
2557 	
2558 	var xhr = this._getTimeline(opts);
2559 };
2560 
2561 SpazTwit.prototype.favorite = function(id, onSuccess, onFailure) {
2562 	var data = {};
2563 	data['id'] = id;
2564 	
2565 	var url = this.getAPIURL('favorites_create', data);
2566 	
2567 	var opts = {
2568 		'url':url,
2569 		'success_event_type':'create_favorite_succeeded',
2570 		'failure_event_type':'create_favorite_failed',
2571 		'success_callback':onSuccess,
2572 		'failure_callback':onFailure,
2573 		'data':data
2574 	};
2575 
2576 	/*
2577 		Perform a request and get true or false back
2578 	*/
2579 	var xhr = this._callMethod(opts);
2580 };
2581 
2582 SpazTwit.prototype.unfavorite = function(id, onSuccess, onFailure) {
2583 	var data = {};
2584 	data['id'] = id;
2585 	
2586 	var url = this.getAPIURL('favorites_destroy', data);
2587 	
2588 	var opts = {
2589 		'url':url,
2590 		'success_event_type':'destroy_favorite_succeeded',
2591 		'failure_event_type':'destroy_favorite_failed',
2592 		'success_callback':onSuccess,
2593 		'failure_callback':onFailure,
2594 		'data':data
2595 	};
2596 
2597 	/*
2598 		Perform a request and get true or false back
2599 	*/
2600 	var xhr = this._callMethod(opts);
2601 };
2602 
2603 
2604 
2605 
2606 SpazTwit.prototype.updateLocation = function(location_str, onSuccess, onFailure) {
2607 	var data = {};
2608 	data.location = location_str;
2609 	
2610 	this.setBaseURL(SPAZCORE_SERVICEURL_TWITTER);
2611 	
2612 	var url = this.getAPIURL('update_profile');
2613 	
2614 	var opts = {
2615 		'url':url,
2616 		'success_event_type':'update_location_succeeded',
2617 		'failure_event_type':'update_location_failed',
2618 		'success_callback':onSuccess,
2619 		'failure_callback':onFailure,
2620 		'data':data
2621 	};
2622 
2623 	/*
2624 		Perform a request and get true or false back
2625 	*/
2626 	var xhr = this._callMethod(opts);
2627 };
2628 
2629 SpazTwit.prototype.updateProfile = function(name, email, url, location, description) {
2630 	
2631 };
2632 
2633 
2634 
2635 /**
2636  * get the current rate limit status
2637  * @param {Function} onSuccess callback for success 
2638  * @param {Function} onFailure callback for failure 
2639  */
2640 SpazTwit.prototype.getRateLimitStatus = function(onSuccess, onFailure) {
2641 	
2642 	var url = this.getAPIURL('ratelimit_status');
2643 	
2644 	var opts = {
2645 		'method':'GET',
2646 		'url':url,
2647 		'success_event_type':'ratelimit_status_succeeded',
2648 		'failure_event_type':'ratelimit_status_failed',
2649 		'success_callback':onSuccess,
2650 		'failure_callback':onFailure
2651 	};
2652 
2653 	/*
2654 		Perform a request and get true or false back
2655 	*/
2656 	var xhr = this._callMethod(opts);
2657 	
2658 };
2659 
2660 SpazTwit.prototype.test = function() {};
2661 
2662 
2663 /**
2664  * @private 
2665  */
2666 SpazTwit.prototype._postProcessURL = function(url) {
2667 	
2668 	if (typeof Mojo !== "undefined") { // we're in webOS		
2669 		if (use_palmhost_proxy) { // we are not on an emu or device, so proxy calls
2670 			var re = /https?:\/\/.[^\/:]*(?::[0-9]+)?/;
2671 			var match = url.match(re);
2672 			if (match && match[0] !== Mojo.hostingPrefix) {
2673 				url = "/proxy?url=" + encodeURIComponent(url);
2674 			}
2675 			return url;		
2676 		} else {
2677 			return url;
2678 		}
2679 		
2680 	} else {
2681 		return url;
2682 	}
2683 };
2684 
2685 
2686 /**
2687  * sorting function for an array of tweets. Asc by ID.
2688  * 
2689  * Example: itemsarray.sort(this._sortItemsAscending) 
2690  * @param {object} a a twitter message object
2691  * @param {object} b a twitter message object
2692  * @return {integer}
2693  * @private
2694  */
2695 SpazTwit.prototype._sortItemsAscending = function(a,b) {
2696 	return (a.id - b.id);
2697 };
2698 
2699 /**
2700  * sorting function for an array of tweets. Desc by ID.
2701  * 
2702  * Example: itemsarray.sort(this._sortItemsDescending) 
2703  * @param {object} a a twitter message object
2704  * @param {object} b a twitter message object
2705  * @return {integer}
2706  * @private
2707  */
2708 SpazTwit.prototype._sortItemsDescending = function(a,b) {
2709 	return (b.id - a.id);
2710 };
2711 
2712 
2713 
2714 /**
2715  * sorting function for an array of tweets. Asc by date.
2716  * 
2717  * requires SpazCore helpers/datetime.js for httpTimeToInt()
2718  * 
2719  * Example: itemsarray.sort(this._sortItemsByDateAsc) 
2720  * @param {object} a a twitter message object
2721  * @param {object} b a twitter message object
2722  * @return {integer}
2723  * @private
2724  */
2725 SpazTwit.prototype._sortItemsByDateAsc = function(a,b) {
2726 	var time_a = sc.helpers.httpTimeToInt(a.created_at);
2727 	var time_b = sc.helpers.httpTimeToInt(b.created_at);
2728 	return (time_a - time_b);
2729 };
2730 
2731 /**
2732  * sorting function for an array of tweets. Desc by date.
2733  * 
2734  * requires SpazCore helpers/datetime.js for httpTimeToInt()
2735  * 
2736  * Example: itemsarray.sort(this._sortItemsByDateDesc)
2737  * @param {object} a a twitter message object
2738  * @param {object} b a twitter message object 
2739  * @return {integer}
2740  * @private
2741  */
2742 SpazTwit.prototype._sortItemsByDateDesc = function(a,b) {
2743 	var time_a = sc.helpers.httpTimeToInt(a.created_at);
2744 	var time_b = sc.helpers.httpTimeToInt(b.created_at);
2745 	return (time_b - time_a);
2746 };
2747 
2748 
2749 /**
2750  * this takes an array of messages and returns one with any duplicates removed
2751  * 
2752  * This is based on the jQuery.unique() method
2753  * 
2754  * @param {array} array an array of Twitter message objects
2755  * @return {array}
2756  */
2757 SpazTwit.prototype.removeDuplicates = function(arr) {
2758 	
2759 	var ret = [], done = {}, length = arr.length;
2760 
2761 	try {
2762 		for ( var i = 0; i < length; i++ ) {
2763 			var id = arr[i].id;
2764 			
2765 			if ( !done[ id ] ) {
2766 				done[ id ] = true;
2767 				ret.push( arr[ i ] );
2768 			} else {
2769 				sc.helpers.dump("removing dupe " + arr[i].id + ', "'+arr[i].text+'"');
2770 			}
2771 		}
2772 
2773 	} catch( e ) {
2774 		sc.helpers.dump(e.name + ":" + e.message);
2775 		ret = arr;
2776 	}
2777 	return ret;
2778 	
2779 };
2780 
2781 
2782 /**
2783  * removes extra elements from a timeline array.
2784  * @param {array} items the timeline array
2785  * @param {integer} max the max # of items we should have
2786  * @param {boolean} remove_from_top whether or not to remove extra items from the top. default is FALSE 
2787  */
2788 SpazTwit.prototype.removeExtraElements = function(items, max, remove_from_top) {
2789 	
2790 	if (!remove_from_top) {
2791 		remove_from_top = false;
2792 	}
2793 	
2794 	var diff = items.length - max;
2795 	if (diff > 0) {
2796 		
2797 		if (!remove_from_top) {
2798 			sc.helpers.dump("array length is " + items.length + " > " + max + "; removing last " + diff + " entries");
2799 	        items.splice(diff * -1, diff);
2800 		} else {
2801 			sc.helpers.dump("array length is " + items.length + " > " + max + "; removing first " + diff + " entries");
2802 	        items.splice(0, diff);
2803 		}
2804 	}
2805 	
2806 	return items;
2807 };
2808 
2809 
2810 /**
2811  * gets the saved searches the authenticating user has 
2812  */
2813 SpazTwit.prototype.getSavedSearches = function(onSuccess, onFailure) {
2814 	var url = this.getAPIURL('saved_searches');
2815 	
2816 	var opts = {
2817 		'url':url,
2818 		'success_event_type':'new_saved_searches_data',
2819 		'failure_event_type':'error_saved_searches_data',
2820 		'success_callback':onSuccess,
2821 		'failure_callback':onFailure,
2822 		'method':'GET'
2823 	};
2824 
2825 	/*
2826 		Perform a request and get true or false back
2827 	*/
2828 	var xhr = this._callMethod(opts);
2829 };
2830 
2831 /**
2832  * Saves the search query to the Twitter servers
2833  * 
2834  * @param {String} search_query 
2835  */
2836 SpazTwit.prototype.addSavedSearch = function(search_query, onSuccess, onFailure) {
2837 	var url = this.getAPIURL('saved_searches_create');
2838 	
2839 	var opts = {
2840 		'url':url,
2841 		'success_event_type':'create_saved_search_succeeded',
2842 		'failure_event_type':'create_saved_search_failed',
2843 		'success_callback':onSuccess,
2844 		'failure_callback':onFailure,
2845 		'data':{'query':search_query},
2846 		'method':'POST'
2847 	};
2848 
2849 	/*
2850 		Perform a request and get true or false back
2851 	*/
2852 	var xhr = this._callMethod(opts);
2853 	
2854 };
2855 
2856 /**
2857  * Delete the saved search corresponding to the given ID
2858  * 
2859  * @param {String} search_id  Note that this is converted to a string via search_id.toString()
2860  */
2861 SpazTwit.prototype.removeSavedSearch = function(search_id, onSuccess, onFailure) {
2862 	var url = this.getAPIURL('saved_searches_destroy', search_id.toString());
2863 	
2864 	var opts = {
2865 		'url':url,
2866 		'success_event_type':'destroy_saved_search_succeeded',
2867 		'failure_event_type':'destroy_saved_search_failed',
2868 		'success_callback':onSuccess,
2869 		'failure_callback':onFailure,
2870 		'data':{'id':search_id},
2871 		'method':'POST'
2872 	};
2873 	
2874 	sch.debug('opts for removeSavedSearch');
2875 	sch.debug(opts);
2876 
2877 	/*
2878 		Perform a request and get true or false back
2879 	*/
2880 	var xhr = this._callMethod(opts);
2881 	
2882 };
2883 
2884 
2885 
2886 
2887 /**
2888  * retrieves the list of lists 
2889  */
2890 SpazTwit.prototype.getLists = function(user, onSuccess, onFailure) {
2891 	if (!user && !this.username) {
2892 		return;
2893 	} else if (!user) {
2894 	    user = this.username;
2895 	}
2896 
2897 	var url = this.getAPIURL('lists', {
2898 	    'user':user
2899 	});
2900 	
2901 	var opts = {
2902 		'url':url,
2903 		'success_event_type':'get_lists_succeeded',
2904 		'failure_event_type':'get_lists_failed',
2905 		'success_callback':onSuccess,
2906 		'failure_callback':onFailure,
2907 		'method':'GET'
2908 	};
2909 
2910 	var xhr = this._callMethod(opts);
2911 };
2912 
2913 
2914 
2915 
2916 /**
2917  * general processor for user lists data
2918  * @private
2919  */
2920 SpazTwit.prototype._processUserLists = function(section_name, ret_items, opts, processing_opts) {
2921   
2922     if (!processing_opts) { processing_opts = {}; }
2923 
2924 	if (ret_items.length > 0){
2925 		/*
2926 			we process each item, adding some attributes and generally making it cool
2927 		*/
2928 		for (var k=0; k<ret_items.length; k++) {
2929 			ret_items[k] = this._processList(ret_items[k], section_name);
2930 			sch.dump(ret_items[k]);
2931 		}
2932 
2933 		/*
2934 			sort items
2935 		*/
2936 		ret_items.sort(this._sortItemsAscending);
2937 
2938 		// set lastid
2939 		var lastid = ret_items[ret_items.length-1].id;
2940 		this.data[section_name].lastid = lastid;
2941 		sc.helpers.dump('this.data['+section_name+'].lastid:'+this.data[section_name].lastid);
2942 
2943 		// add new items to data.newitems array
2944 		this.data[section_name].newitems = ret_items;
2945 
2946 		this._addToSectionItems(section_name, this.data[section_name].newitems);
2947 
2948 		if (opts.success_callback) {
2949 			opts.success_callback(this.data[section_name].newitems);
2950 		}
2951 		this.triggerEvent(opts.success_event_type, this.data[section_name].newitems );
2952 
2953 	} else { // no new items, but we should fire off success anyway
2954 		
2955 		if (opts.success_callback) {
2956 			opts.success_callback();
2957 		}
2958 		this.triggerEvent(opts.success_event_type);
2959 	}
2960 };
2961 
2962 /**
2963  * This modifies a Twitter user list, adding some properties. All new properties are
2964  * prepended with "SC_"
2965  * 
2966  * this executes within the jQuery.each scope, so this === the item 
2967  */
2968 SpazTwit.prototype._processList = function(item, section_name) {	
2969 	/*
2970 		add .SC_retrieved_unixtime
2971 	*/
2972 	if (!item.SC_retrieved_unixtime) {
2973 		item.SC_retrieved_unixtime = sc.helpers.getTimeAsInt();
2974 	}
2975 	
2976 	return item;
2977 };
2978 
2979 
2980 /**
2981  * retrieves a given list timeline
2982  * @param {string} list 
2983  */
2984 SpazTwit.prototype.getListInfo = function(list, user, onSuccess, onFailure) {
2985 	if (!user && !this.username) {
2986 		sch.error('must pass a username or have one set to get list');
2987 		return;
2988 	}
2989 	
2990 	user = user || this.username;
2991 
2992 	var url = this.getAPIURL('lists_list', {
2993 	    'user':user,
2994 		'slug':list
2995 	});
2996 	
2997 	var opts = {
2998 		'url':url,
2999 		'success_event_type':'get_list_succeeded',
3000 		'failure_event_type':'get_list_failed',
3001 		'success_callback':onSuccess,
3002 		'failure_callback':onFailure,
3003 		'method':'GET'
3004 	};
3005 
3006 	var xhr = this._callMethod(opts);
3007 };
3008 
3009 
3010 /**
3011  * retrieves a given list timeline
3012  * @param {string} list 
3013  * @param {string} user the user who owns this list
3014  * @param {function} [onSuccess] function to call on success
3015  * @param {function} [onFailure] function to call on failure
3016  */
3017 SpazTwit.prototype.getListTimeline = function(list, user, onSuccess, onFailure) {
3018 	if (!user && !this.username) {
3019 		sch.error('must pass a username or have one set to get list');
3020 		return;
3021 	}
3022 	
3023 	user = user || this.username;
3024 
3025 	var url = this.getAPIURL('lists_timeline', {
3026 	    'user':user,
3027 		'slug':list
3028 	});
3029 	
3030 	var opts = {
3031 		'url':url,
3032 		'success_event_type':'get_list_timeline_succeeded',
3033 		'failure_event_type':'get_list_timeline_failed',
3034 		'success_callback':onSuccess,
3035 		'failure_callback':onFailure,
3036 		'method':'GET',
3037 		'process_callback':this._processListTimeline,
3038 		'processing_opts': {
3039 			'user':user,
3040 			'slug':list
3041 		}
3042 	};
3043 
3044 	var xhr = this._getTimeline(opts);
3045 };
3046 
3047 
3048 SpazTwit.prototype._processListTimeline = function(data, opts, processing_opts) {
3049 	if (!processing_opts) { processing_opts = {}; }
3050 	
3051 	var user = processing_opts.user || null;
3052 	var slug = processing_opts.slug || null;
3053 	
3054 	var rdata = {
3055 		'statuses':data,
3056 		'user':user,
3057 		'slug':slug
3058 	};
3059 	
3060 	/*
3061 		grab the array of items
3062 	*/
3063 	// jQuery().trigger(finished_event, [ret_items]);
3064 	
3065 	if (opts.success_callback) {
3066 		opts.success_callback(rdata);
3067 	}
3068 	this.triggerEvent(opts.success_event_type, rdata);
3069 };
3070 
3071 /**
3072  * retrieves a given list's members
3073  * @param {string} list 
3074  */
3075 SpazTwit.prototype.getListMembers = function(list, user) {
3076 	if (!user && !this.username) {
3077 		sch.error('must pass a username or have one set to get list');
3078 		return;
3079 	}
3080 	
3081 	user = user || this.username;
3082 
3083 	var url = this.getAPIURL('lists_members', {
3084 	    'user':user,
3085 		'slug':list
3086 	});
3087 	
3088 	var opts = {
3089 		'url':url,
3090 		'success_event_type':'get_list_members_succeeded',
3091 		'failure_event_type':'get_list_members_failed',
3092 		'method':'GET',
3093 		'process_callback':this._processListTimeline,
3094 		'processing_opts': {
3095 			'user':user,
3096 			'slug':list
3097 		}
3098 	};
3099 
3100 	var xhr = this._getTimeline(opts);
3101 };
3102 
3103 /**
3104  * create a new list for the authenticated user
3105  * @param {string} list  The list name
3106  * @param {string} visibility   "public" or "private"
3107  * @param {string} [description]  The list description
3108  */
3109 SpazTwit.prototype.addList = function(list, visibility, description) {
3110 	var data = {};
3111 	data['name'] = list;
3112 	data['mode'] = visibility;
3113 	data['description'] = description;
3114 	
3115 	var url = this.getAPIURL('lists', {
3116 		'user': this.username
3117 	});
3118 	
3119 	var opts = {
3120 		'url':url,
3121 		'success_event_type':'create_list_succeeded',
3122 		'failure_event_type':'create_list_failed',
3123 		'success_callback':null,
3124 		'failure_callback':null,
3125 		'data':data
3126 	};
3127 	
3128 	var xhr = this._callMethod(opts);
3129 };
3130 
3131 SpazTwit.prototype.updateList = function(list, name, visibility, description){
3132 	var data = {};
3133 	data['name'] = name;
3134 	data['mode'] = visibility;
3135 	data['description'] = description;
3136 	
3137 	var url = this.getAPIURL('lists_list', {
3138 		'user': this.username,
3139 		'slug': list
3140 	});
3141 	
3142 	var opts = {
3143 		'url':url,
3144 		'username':this.username,
3145 		'password':this.password,
3146 		'success_event_type':'update_list_succeeded',
3147 		'failure_event_type':'update_list_failed',
3148 		'data':data
3149 	};
3150 	
3151 	var xhr = this._callMethod(opts);
3152 };
3153 
3154 /**
3155  * delete a list
3156  * @param {string} list  The list name 
3157  */
3158 SpazTwit.prototype.removeList = function(list, user) {
3159 	
3160 	if (!user && !this.username) {
3161 		sch.error('must pass a username or have one set to remove list');
3162 		return;
3163 	}
3164 	
3165 	user = user || this.username;
3166 	
3167 	var url = this.getAPIURL('lists_list', {
3168 		'user': user,
3169 		'slug':list
3170 	});
3171 	
3172 	var opts = {
3173 		'url':url,
3174 		'success_event_type':'remove_list_succeeded',
3175 		'failure_event_type':'remove_list_failed',
3176 		'method':'DELETE'
3177 	};
3178 	
3179 	var xhr = this._callMethod(opts);
3180 };
3181 
3182 /**
3183  * add a user to a list
3184  */
3185 SpazTwit.prototype.addUserToList = function(user, list, list_user) {
3186 	var data = {};
3187 	data['list_id'] = list;
3188 	data['id'] = list_user;
3189 	
3190 	
3191 	if (!user && !this.username) {
3192 		sch.error('must pass a username or have one set to add a user to a list');
3193 		return;
3194 	}
3195 	
3196 	user = user || this.username;
3197 	
3198 	var url = this.getAPIURL('lists_members', {
3199 		'user': user,
3200 		'slug': list
3201 	});
3202 	
3203 	var opts = {
3204 		'url':url,
3205 		'success_event_type':'add_list_user_succeeded',
3206 		'failure_event_type':'add_list_user_failed',
3207 		'data':data
3208 	};
3209 	
3210 	var xhr = this._callMethod(opts);
3211 };
3212 
3213 /**
3214  * delete a user from a list 
3215  */
3216 SpazTwit.prototype.removeUserFromList = function(user, list, list_user) {
3217 	var data = {};
3218 	data['list_id'] = list;
3219 	data['id'] = list_user;
3220 	
3221 	
3222 	if (!user && !this.username) {
3223 		sch.error('must pass a username or have one set to remove a user from a list');
3224 		return;
3225 	}
3226 	
3227 	user = user || this.username;
3228 	
3229 	var url = this.getAPIURL('lists_members', {
3230 		'user': user,
3231 		'slug': list
3232 	});
3233 	
3234 	var opts = {
3235 		'url':url,
3236 		'success_event_type':'create_list_succeeded',
3237 		'failure_event_type':'create_list_failed',
3238 		'success_event_type':'remove_list_user_succeeded',
3239 		'failure_event_type':'remove_list_user_failed',
3240 		'data':data,
3241 		'method':'DELETE'
3242 	};
3243 	
3244 	var xhr = this._callMethod(opts);
3245 };
3246 
3247 
3248 SpazTwit.prototype.listsSubscribedTo = function(user) {
3249 	if(!user && !this.username) {
3250 		sch.error('must pass a username or have one set to retrieve subscribed lists');
3251 		return false;
3252 	}
3253 	
3254 	user = user || this.username;
3255 	
3256 	var url = this.getAPIURL('lists_subscriptions', {
3257 		'user': user
3258 	});
3259 	
3260 	var opts = {
3261 		'url':url,
3262 		'username': this.username,
3263 		'password': this.password,
3264 		'success_event_type':'get_subscriptions_succeeded',
3265 		'failure_event_type':'get_subscriptions_failed'
3266 	};
3267 	
3268 	var xhr = this._callMethod(opts);
3269 };
3270 
3271 SpazTwit.prototype.listMemberships = function(user) {
3272 	if(!user && !this.username) {
3273 		sch.error('must pass a username or have one set to retrieve list memberships');
3274 		return false;
3275 	}
3276 	
3277 	user = user || this.username;
3278 	
3279 	var url = this.getAPIURL('lists_memberships', {
3280 		'user': user
3281 	});
3282 	
3283 	var opts = {
3284 		'url':url,
3285 		'username': this.username,
3286 		'password': this.password,
3287 		'success_event_type':'get_list_memberships_succeeded',
3288 		'failure_event_type':'get_list_memberships_failed'
3289 	};
3290 	
3291 	var xhr = this._callMethod(opts);
3292 };
3293 
3294 SpazTwit.prototype.getListSubscribers = function(list, user){
3295 	if(!user && !this.username) {
3296 		sch.error('must pass a username or have one set to retrieve list subscribers');
3297 		return false;
3298 	}
3299 	
3300 	user = user || this.username;
3301 	
3302 	var url = this.getAPIURL('lists_subscribers', {
3303 		'user': user,
3304 		'slug': list
3305 	});
3306 	
3307 	var opts = {
3308 		'url':url,
3309 		'username': this.username,
3310 		'password': this.password,
3311 		'success_event_type':'get_list_subscribers_succeeded',
3312 		'failure_event_type':'get_list_subscribers_failed',
3313 		'method':'GET'
3314 	};
3315 	
3316 	var xhr = this._callMethod(opts);
3317 };
3318 
3319 SpazTwit.prototype.isSubscribed = function(list, list_user, user){
3320 	if(!user && !this.username) {
3321 		sch.error('must pass a username or have one set to retrieve list subscribers');
3322 		return false;
3323 	}
3324 	
3325 	user = user || this.username;
3326 	
3327 	var url = this.getAPIURL('lists_check_subscriber', {
3328 		'user': user,
3329 		'slug': list,
3330 		'id': list_user
3331 	});
3332 	
3333 	var opts = {
3334 		'url':url,
3335 		'username': this.username,
3336 		'password': this.password,
3337 		'success_event_type':'check_list_subscribers_succeeded',
3338 		'failure_event_type':'check_list_subscribers_failed',
3339 		'method':'GET'
3340 	};
3341 	
3342 	var xhr = this._callMethod(opts);
3343 };
3344 
3345 SpazTwit.prototype.subscribe = function(list, user){
3346 	if(!user && !this.username) {
3347 		sch.error('must pass a username or have one set to subscribe to a list');
3348 		return false;
3349 	}
3350 	
3351 	user = user || this.username;
3352 	
3353 	var url = this.getAPIURL('lists_subscribers', {
3354 		'user': user,
3355 		'slug': list
3356 	});
3357 	
3358 	var opts = {
3359 		'url':url,
3360 		'username': this.username,
3361 		'password': this.password,
3362 		'success_event_type':'list_subscribe_succeeded',
3363 		'failure_event_type':'list_subscribe_failed',
3364 		'method':'POST'
3365 	};
3366 	
3367 	var xhr = this._callMethod(opts);
3368 };
3369 
3370 SpazTwit.prototype.unsubscribe = function(list, user){
3371 	if(!user && !this.username) {
3372 		sch.error('must pass a username or have one set to unsubscribe');
3373 		return false;
3374 	}
3375 	
3376 	user = user || this.username;
3377 	
3378 	var url = this.getAPIURL('lists_subscribers', {
3379 		'user': user,
3380 		'slug': list,
3381 		'id': list_user
3382 	});
3383 	
3384 	var opts = {
3385 		'url':url,
3386 		'username': this.username,
3387 		'password': this.password,
3388 		'success_event_type':'list_unsubscribe_succeeded',
3389 		'failure_event_type':'list_unsubscribe_failed',
3390 		'method':'DELETE'
3391 	};
3392 	
3393 	var xhr = this._callMethod(opts);
3394 };
3395 
3396 SpazTwit.prototype.isMember = function(list, list_user, user){
3397 	if(!user && !this.username) {
3398 		sch.error('must pass a username or have one set to retrieve list memberships');
3399 		return false;
3400 	}
3401 	
3402 	user = user || this.username;
3403 	
3404 	var url = this.getAPIURL('lists_check_member', {
3405 		'user': user,
3406 		'slug': list,
3407 		'id': list_user
3408 	});
3409 	
3410 	var opts = {
3411 		'url':url,
3412 		'username': this.username,
3413 		'password': this.password,
3414 		'success_event_type':'check_list_members_succeeded',
3415 		'failure_event_type':'check_list_members_failed',
3416 		'method':'GET'
3417 	};
3418 	
3419 	var xhr = this._callMethod(opts);
3420 };
3421 
3422 /*
3423  * Marks a user as a spammer and blocks them
3424  * @param {integer} user_id a user_id (not a screen name!)
3425  * @param {function} onSuccess callback
3426  * @param {function} onFailure callback
3427  */
3428 SpazTwit.prototype.reportSpam = function(user_id, onSuccess, onFailure) {
3429 	var url = this.getAPIURL('report_spam');
3430 	
3431 	var data = {};
3432 	data['user_id'] = user_id;
3433 	
3434 	var opts = {
3435 		'url':url,
3436 		'username': this.username,
3437 		'password': this.password,
3438 		'success_callback': onSuccess,
3439 		'failure_callback': onFailure,
3440 		'success_event_type':'report_spam_succeeded',
3441 		'failure_event_type':'report_spam_failed',
3442 		'method':'POST',
3443 		'data':data
3444 	};
3445 	
3446 	var xhr = this._callMethod(opts);
3447 };
3448 
3449 
3450 
3451 SpazTwit.prototype.openUserStream = function(onData, onFailure) {
3452 	var that = this;
3453 
3454 	/*
3455 		close existing stream
3456 	*/
3457 	this.closeUserStream();
3458 	
3459 	/*
3460 		open new stream
3461 	*/
3462 	this.userstream = new SpazTwitterStream({
3463 		'auth'   : this.auth,
3464 		'onData' : function(data) {
3465 			var item;
3466 			data = sch.trim(data);
3467 			if (data) {
3468 				sch.error('new data:'+data);
3469 				item = sch.deJSON(data);
3470 				
3471 				if (item.source && item.user && item.text) { // is "normal" status
3472 					item = that._processItem(item, SPAZCORE_SECTION_HOME);
3473 					if (onData) {
3474 						onData(item);
3475 					}
3476 				}
3477 
3478 				if (item.direct_message) { // is DM
3479 					item = that._processItem(item.direct_message, SPAZCORE_SECTION_HOME);
3480 					if (onData) {
3481 						onData(item);
3482 					}
3483 				}
3484 				
3485 			}
3486 		}
3487 	});
3488 	this.userstream.connect();
3489 	return this.userstream;
3490 };
3491 
3492 
3493 SpazTwit.prototype.closeUserStream = function() {
3494 	if (this.userstream) {
3495 		sch.error('userstream exist… disconnecting');
3496 		this.userstream.disconnect();
3497 		this.userstream = null;
3498 	}
3499 };
3500 
3501 
3502 SpazTwit.prototype.userStreamExists = function() {
3503 	if (this.userstream) {
3504 		return true;
3505 	}
3506 	return false;
3507 };
3508 
3509 
3510 
3511 /**
3512  *  
3513  */
3514 SpazTwit.prototype.triggerEvent = function(type, data) {
3515 	var target = this.opts.event_target || document;
3516 	data   = data || null;
3517 	
3518 	sc.helpers.dump('TriggerEvent: target:'+target.toString()+ ' type:'+type+ ' data:'+data);
3519 	
3520 	if (this.opts.event_mode === 'jquery') {
3521 		data = [data];
3522 		jQuery(target).trigger(type, data);
3523 	} else {
3524 		sc.helpers.trigger(type, target, data);	
3525 	}
3526 	
3527 };
3528 
3529 /**
3530  * shortcut for SpazTwit if the SpazCore libraries are being used
3531  * 
3532  */
3533 if (sc) {
3534 	var scTwit = SpazTwit;
3535 }
3536 
3537