FHSTwitterEngine.m 92 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242
  1. //
  2. // FHSTwitterEngine.m
  3. // FHSTwitterEngine
  4. //
  5. // Created by Nathaniel Symer on 8/22/12.
  6. // Copyright (C) 2012 Nathaniel Symer.
  7. //
  8. // Permission is hereby granted, free of charge, to any person obtaining a copy
  9. // of this software and associated documentation files (the "Software"), to deal
  10. // in the Software without restriction, including without limitation the rights
  11. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  12. // copies of the Software, and to permit persons to whom the Software is
  13. // furnished to do so, subject to the following conditions:
  14. //
  15. // The above copyright notice and this permission notice shall be included in
  16. // all copies or substantial portions of the Software.
  17. //
  18. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  19. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  20. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  21. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  22. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  23. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  24. // THE SOFTWARE.
  25. #import "FHSTwitterEngine.h"
  26. #import "OAuthConsumer.h"
  27. #import <QuartzCore/QuartzCore.h>
  28. #import <SystemConfiguration/SystemConfiguration.h>
  29. #import <sys/socket.h>
  30. #import <netinet/in.h>
  31. #import <ifaddrs.h>
  32. #import <objc/runtime.h>
  33. NSString * const FHSProfileBackgroundColorKey = @"profile_background_color";
  34. NSString * const FHSProfileLinkColorKey = @"profile_link_color";
  35. NSString * const FHSProfileSidebarBorderColorKey = @"profile_sidebar_border_color";
  36. NSString * const FHSProfileSidebarFillColorKey = @"profile_sidebar_fill_color";
  37. NSString * const FHSProfileTextColorKey = @"profile_text_color";
  38. NSString * const FHSProfileNameKey = @"name";
  39. NSString * const FHSProfileURLKey = @"url";
  40. NSString * const FHSProfileLocationKey = @"location";
  41. NSString * const FHSProfileDescriptionKey = @"description";
  42. static NSString * const errorFourhundred = @"Bad Request: The request you are trying to make has missing or bad parameters.";
  43. static NSString * const authBlockKey = @"FHSTwitterEngineOAuthCompletion";
  44. static FHSTwitterEngine *sharedInstance = nil;
  45. id removeNull(id rootObject) {
  46. if ([rootObject isKindOfClass:[NSDictionary class]]) {
  47. NSMutableDictionary *sanitizedDictionary = [NSMutableDictionary dictionaryWithDictionary:rootObject];
  48. [rootObject enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
  49. id sanitized = removeNull(obj);
  50. if (!sanitized) {
  51. [sanitizedDictionary setObject:@"" forKey:key];
  52. } else {
  53. [sanitizedDictionary setObject:sanitized forKey:key];
  54. }
  55. }];
  56. return [NSMutableDictionary dictionaryWithDictionary:sanitizedDictionary];
  57. }
  58. if ([rootObject isKindOfClass:[NSArray class]]) {
  59. NSMutableArray *sanitizedArray = [NSMutableArray arrayWithArray:rootObject];
  60. [rootObject enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  61. id sanitized = removeNull(obj);
  62. if (!sanitized) {
  63. [sanitizedArray replaceObjectAtIndex:[sanitizedArray indexOfObject:obj] withObject:@""];
  64. } else {
  65. [sanitizedArray replaceObjectAtIndex:[sanitizedArray indexOfObject:obj] withObject:sanitized];
  66. }
  67. }];
  68. return [NSMutableArray arrayWithArray:sanitizedArray];
  69. }
  70. if ([rootObject isKindOfClass:[NSNull class]]) {
  71. return (id)nil;
  72. } else {
  73. return rootObject;
  74. }
  75. }
  76. NSError * getBadRequestError() {
  77. return [NSError errorWithDomain:errorFourhundred code:400 userInfo:nil];
  78. }
  79. NSError * getNilReturnLengthError() {
  80. return [NSError errorWithDomain:@"Twitter successfully processed the request, but did not return any content" code:204 userInfo:nil];
  81. }
  82. @interface FHSTwitterEngineController : UIViewController <UIWebViewDelegate>
  83. @property (nonatomic, retain) UINavigationBar *navBar;
  84. @property (nonatomic, retain) UIView *blockerView;
  85. @property (nonatomic, retain) UIToolbar *pinCopyBar;
  86. //@property (nonatomic, retain) FHSTwitterEngine *engine;
  87. @property (nonatomic, retain) UIWebView *theWebView;
  88. @property (nonatomic, retain) OAToken *requestToken;
  89. //- (id)initWithEngine:(FHSTwitterEngine *)theEngine;
  90. - (NSString *)locatePin;
  91. - (void)showPinCopyPrompt;
  92. @end
  93. @interface FHSTwitterEngine()
  94. // Login stuff
  95. - (NSString *)getRequestTokenString;
  96. // General Get request sender
  97. - (id)sendRequest:(NSURLRequest *)request;
  98. // These are here to obfuscate them from prying eyes
  99. @property (retain, nonatomic) OAConsumer *consumer;
  100. @property (assign, nonatomic) BOOL shouldClearConsumer;
  101. @property (retain, nonatomic) NSDateFormatter *dateFormatter;
  102. @end
  103. @implementation NSString (FHSTwitterEngine)
  104. - (NSString *)fhs_trimForTwitter {
  105. NSString *string = [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
  106. return (string.length > 140)?[string substringToIndex:140]:string;
  107. }
  108. - (NSString *)fhs_stringWithRange:(NSRange)range {
  109. return [[self substringFromIndex:range.location]substringToIndex:range.length];
  110. }
  111. - (BOOL)fhs_isNumeric {
  112. const char *raw = (const char *)[self UTF8String];
  113. for (int i = 0; i < strlen(raw); i++) {
  114. if (raw[i] < '0' || raw[i] > '9') {
  115. return NO;
  116. }
  117. }
  118. return YES;
  119. }
  120. @end
  121. @implementation FHSTwitterEngine
  122. static NSString * const url_search_tweets = @"https://api.twitter.com/1.1/search/tweets.json";
  123. static NSString * const url_users_search = @"https://api.twitter.com/1.1/users/search.json";
  124. static NSString * const url_users_show = @"https://api.twitter.com/1.1/users/show.json";
  125. static NSString * const url_users_report_spam = @"https://api.twitter.com/1.1/users/report_spam.json";
  126. static NSString * const url_users_lookup = @"https://api.twitter.com/1.1/users/lookup.json";
  127. static NSString * const url_lists_create = @"https://api.twitter.com/1.1/lists/create.json";
  128. static NSString * const url_lists_show = @"https://api.twitter.com/1.1/lists/show.json";
  129. static NSString * const url_lists_update = @"https://api.twitter.com/1.1/lists/update.json";
  130. static NSString * const url_lists_members = @"https://api.twitter.com/1.1/lists/members.json";
  131. static NSString * const url_lists_members_destroy_all = @"https://api.twitter.com/1.1/lists/members/destroy_all.json";
  132. static NSString * const url_lists_members_create_all = @"https://api.twitter.com/1.1/lists/members/create_all.json";
  133. static NSString * const url_lists_statuses = @"https://api.twitter.com/1.1/lists/statuses.json";
  134. static NSString * const url_lists_list = @"https://api.twitter.com/1.1/lists/list.json";
  135. static NSString * const url_statuses_home_timeline = @"https://api.twitter.com/1.1/statuses/home_timeline.json";
  136. static NSString * const url_statuses_update = @"https://api.twitter.com/1.1/statuses/update.json";
  137. static NSString * const url_statuses_retweets_of_me = @"https://api.twitter.com/1.1/statuses/retweets_of_me.json";
  138. static NSString * const url_statuses_user_timeline = @"https://api.twitter.com/1.1/statuses/user_timeline.json";
  139. static NSString * const url_statuses_metions_timeline = @"https://api.twitter.com/1.1/statuses/mentions_timeline.json";
  140. static NSString * const url_statuses_update_with_media = @"https://api.twitter.com/1.1/statuses/update_with_media.json";
  141. static NSString * const url_statuses_destroy = @"https://api.twitter.com/1.1/statuses/destroy.json";
  142. static NSString * const url_statuses_show = @"https://api.twitter.com/1.1/statuses/show.json";
  143. static NSString * const url_statuses_oembed = @"https://api.twitter.com/1.1/statuses/oembed.json";
  144. static NSString * const url_blocks_exists = @"https://api.twitter.com/1.1/blocks/exists.json";
  145. static NSString * const url_blocks_blocking = @"https://api.twitter.com/1.1/blocks/blocking.json";
  146. static NSString * const url_blocks_blocking_ids = @"https://api.twitter.com/1.1/blocks/blocking/ids.json";
  147. static NSString * const url_blocks_destroy = @"https://api.twitter.com/1.1/blocks/destroy.json";
  148. static NSString * const url_blocks_create = @"https://api.twitter.com/1.1/blocks/create.json";
  149. static NSString * const url_help_languages = @"https://api.twitter.com/1.1/help/languages.json";
  150. static NSString * const url_help_configuration = @"https://api.twitter.com/1.1/help/configuration.json";
  151. static NSString * const url_help_privacy = @"https://api.twitter.com/1.1/help/privacy.json";
  152. static NSString * const url_help_tos = @"https://api.twitter.com/1.1/help/tos.json";
  153. static NSString * const url_help_test = @"https://api.twitter.com/1.1/help/test.json";
  154. static NSString * const url_direct_messages_show = @"https://api.twitter.com/1.1/direct_messages/show.json";
  155. static NSString * const url_direct_messages_new = @"https://api.twitter.com/1.1/direct_messages/new.json";
  156. static NSString * const url_direct_messages_sent = @"https://api.twitter.com/1.1/direct_messages/sent.json";
  157. static NSString * const url_direct_messages_destroy = @"https://api.twitter.com/1.1/direct_messages/destroy.json";
  158. static NSString * const url_direct_messages = @"https://api.twitter.com/1.1/direct_messages.json";
  159. static NSString * const url_friendships_no_retweets_ids = @"https://api.twitter.com/1.1/friendships/no_retweets/ids.json";
  160. static NSString * const url_friendships_update = @"https://api.twitter.com/1.1/friendships/update.json";
  161. static NSString * const url_friendships_outgoing = @"https://api.twitter.com/1.1/friendships/outgoing.json";
  162. static NSString * const url_friendships_incoming = @"https://api.twitter.com/1.1/friendships/incoming.json";
  163. static NSString * const url_friendships_lookup = @"https://api.twitter.com/1.1/friendships/lookup.json";
  164. static NSString * const url_friendships_destroy = @"https://api.twitter.com/1.1/friendships/destroy.json";
  165. static NSString * const url_friendships_create = @"https://api.twitter.com/1.1/friendships/create.json";
  166. static NSString * const url_account_verify_credentials = @"https://api.twitter.com/1.1/account/verify_credentials.json";
  167. static NSString * const url_account_update_profile_colors = @"https://api.twitter.com/1.1/account/update_profile_colors.json";
  168. static NSString * const url_account_update_profile_background_image = @"https://api.twitter.com/1.1/account/update_profile_background_image.json";
  169. static NSString * const url_account_update_profile_image = @"https://api.twitter.com/1.1/account/update_profile_image.json";
  170. static NSString * const url_account_settings = @"https://api.twitter.com/1.1/account/settings.json";
  171. static NSString * const url_account_update_profile = @"https://api.twitter.com/1.1/account/update_profile.json";
  172. static NSString * const url_favorites_list = @"https://api.twitter.com/1.1/favorites/list.json";
  173. static NSString * const url_favorites_create = @"https://api.twitter.com/1.1/favorites/create.json";
  174. static NSString * const url_favorites_destroy = @"https://api.twitter.com/1.1/favorites/destroy.json";
  175. static NSString * const url_application_rate_limit_status = @"https://api.twitter.com/1.1/application/rate_limit_status.json";
  176. static NSString * const url_followers_ids = @"https://api.twitter.com/1.1/followers/ids.json";
  177. static NSString * const url_followers_list = @"https://api.twitter.com/1.1/followers/list.json";
  178. static NSString * const url_friends_ids = @"https://api.twitter.com/1.1/friends/ids.json";
  179. static NSString * const url_friends_list = @"https://api.twitter.com/1.1/friends/list.json";
  180. - (id)listFollowersForUser:(NSString *)user isID:(BOOL)isID withCursor:(NSString *)cursor {
  181. if (user.length == 0) {
  182. return getBadRequestError();
  183. }
  184. NSURL *baseURL = [NSURL URLWithString:url_friends_list];
  185. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  186. OARequestParameter *skipstatusP = [OARequestParameter requestParameterWithName:@"skip_status" value:@"true"];
  187. OARequestParameter *include_entitiesP = [OARequestParameter requestParameterWithName:@"include_entities" value:self.includeEntities?@"true":@"false"];
  188. OARequestParameter *screen_nameP = [OARequestParameter requestParameterWithName:isID?@"user_id":@"screen_name" value:user];
  189. OARequestParameter *cursorP = [OARequestParameter requestParameterWithName:@"cursor" value:cursor];
  190. return [self sendGETRequest:request withParameters:[NSArray arrayWithObjects:include_entitiesP, skipstatusP, screen_nameP, cursorP, nil]];
  191. }
  192. - (id)listFriendsForUser:(NSString *)user isID:(BOOL)isID withCursor:(NSString *)cursor {
  193. if (user.length == 0) {
  194. return getBadRequestError();
  195. }
  196. NSURL *baseURL = [NSURL URLWithString:url_friends_list];
  197. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  198. OARequestParameter *skipstatusP = [OARequestParameter requestParameterWithName:@"skip_status" value:@"true"];
  199. OARequestParameter *include_entitiesP = [OARequestParameter requestParameterWithName:@"include_entities" value:self.includeEntities?@"true":@"false"];
  200. OARequestParameter *screen_nameP = [OARequestParameter requestParameterWithName:isID?@"user_id":@"screen_name" value:user];
  201. OARequestParameter *cursorP = [OARequestParameter requestParameterWithName:@"cursor" value:cursor];
  202. return [self sendGETRequest:request withParameters:[NSArray arrayWithObjects:include_entitiesP, skipstatusP, screen_nameP, cursorP, nil]];
  203. }
  204. - (id)searchUsersWithQuery:(NSString *)q andCount:(int)count {
  205. if (count == 0) {
  206. return nil;
  207. }
  208. if (q.length == 0) {
  209. return getBadRequestError();
  210. }
  211. if (q.length > 1000) {
  212. q = [q substringToIndex:1000];
  213. }
  214. NSURL *baseURL = [NSURL URLWithString:url_users_search];
  215. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  216. OARequestParameter *include_entitiesP = [OARequestParameter requestParameterWithName:@"include_entities" value:self.includeEntities?@"true":@"false"];
  217. OARequestParameter *countP = [OARequestParameter requestParameterWithName:@"count" value:[NSString stringWithFormat:@"%d",count]];
  218. OARequestParameter *qP = [OARequestParameter requestParameterWithName:@"q" value:q];
  219. return [self sendGETRequest:request withParameters:[NSArray arrayWithObjects:include_entitiesP, countP, qP, nil]];
  220. }
  221. - (id)searchTweetsWithQuery:(NSString *)q count:(int)count resultType:(FHSTwitterEngineResultType)resultType unil:(NSDate *)untilDate sinceID:(NSString *)sinceID maxID:(NSString *)maxID {
  222. if (count == 0) {
  223. return nil;
  224. }
  225. if (q.length == 0) {
  226. return getBadRequestError();
  227. }
  228. if (q.length > 1000) {
  229. q = [q substringToIndex:1000];
  230. }
  231. NSURL *baseURL = [NSURL URLWithString:url_search_tweets];
  232. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  233. OARequestParameter *include_entitiesP = [OARequestParameter requestParameterWithName:@"include_entities" value:self.includeEntities?@"true":@"false"];
  234. OARequestParameter *countP = [OARequestParameter requestParameterWithName:@"count" value:[NSString stringWithFormat:@"%d",count]];
  235. OARequestParameter *untilP = [OARequestParameter requestParameterWithName:@"until" value:nil];
  236. OARequestParameter *result_typeP = [OARequestParameter requestParameterWithName:@"result_type" value:nil];
  237. OARequestParameter *qP = [OARequestParameter requestParameterWithName:@"q" value:q];
  238. [self.dateFormatter setDateFormat:@"YYYY-MM-DD"];
  239. NSString *untilString = [self.dateFormatter stringFromDate:untilDate];
  240. [self.dateFormatter setDateFormat:@"EEE MMM dd HH:mm:ss ZZZZ yyyy"];
  241. untilP.value = untilString;
  242. if (resultType == FHSTwitterEngineResultTypeMixed) {
  243. result_typeP.value = @"mixed";
  244. } else if (resultType == FHSTwitterEngineResultTypeRecent) {
  245. result_typeP.value = @"recent";
  246. } else if (resultType == FHSTwitterEngineResultTypePopular) {
  247. result_typeP.value = @"popular";
  248. }
  249. NSMutableArray *params = [NSMutableArray arrayWithObjects:countP, include_entitiesP, qP, nil];
  250. if (maxID.length > 0) {
  251. [params addObject:[OARequestParameter requestParameterWithName:@"max_id" value:maxID]];
  252. }
  253. if (sinceID.length > 0) {
  254. [params addObject:[OARequestParameter requestParameterWithName:@"since_id" value:sinceID]];
  255. }
  256. return [self sendGETRequest:request withParameters:params];
  257. }
  258. - (NSError *)createListWithName:(NSString *)name isPrivate:(BOOL)isPrivate description:(NSString *)description {
  259. if (name.length == 0) {
  260. return getBadRequestError();
  261. }
  262. NSURL *baseURL = [NSURL URLWithString:url_lists_create];
  263. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  264. OARequestParameter *nameP = [OARequestParameter requestParameterWithName:@"name" value:name];
  265. OARequestParameter *isPrivateP = [OARequestParameter requestParameterWithName:@"mode" value:isPrivate?@"private":@"public"];
  266. NSMutableArray *params = [NSMutableArray arrayWithObjects:nameP, isPrivateP, nil];
  267. if (description.length > 0) {
  268. [params addObject:[OARequestParameter requestParameterWithName:@"description" value:description]];
  269. }
  270. return [self sendPOSTRequest:request withParameters:params];
  271. }
  272. - (id)getListWithID:(NSString *)listID {
  273. if (listID.length == 0) {
  274. return getBadRequestError();
  275. }
  276. NSURL *baseURL = [NSURL URLWithString:url_lists_show];
  277. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  278. OARequestParameter *listIDP = [OARequestParameter requestParameterWithName:@"list_id" value:listID];
  279. return [self sendGETRequest:request withParameters:[NSArray arrayWithObjects:listIDP, nil]];
  280. }
  281. - (NSError *)changeDescriptionOfListWithID:(NSString *)listID toDescription:(NSString *)newName {
  282. if (listID.length == 0) {
  283. return getBadRequestError();
  284. }
  285. if (newName.length == 0 ) {
  286. return getBadRequestError();
  287. }
  288. NSURL *baseURL = [NSURL URLWithString:url_lists_update];
  289. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  290. OARequestParameter *listIDP = [OARequestParameter requestParameterWithName:@"list_id" value:listID];
  291. OARequestParameter *nameP = [OARequestParameter requestParameterWithName:@"description" value:newName];
  292. return [self sendPOSTRequest:request withParameters:[NSArray arrayWithObjects:listIDP, nameP, nil]];
  293. }
  294. - (NSError *)changeNameOfListWithID:(NSString *)listID toName:(NSString *)newName {
  295. if (listID.length == 0) {
  296. return getBadRequestError();
  297. }
  298. if (newName.length == 0 ) {
  299. return getBadRequestError();
  300. }
  301. NSURL *baseURL = [NSURL URLWithString:url_lists_update];
  302. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  303. OARequestParameter *listIDP = [OARequestParameter requestParameterWithName:@"list_id" value:listID];
  304. OARequestParameter *nameP = [OARequestParameter requestParameterWithName:@"name" value:newName];
  305. return [self sendPOSTRequest:request withParameters:[NSArray arrayWithObjects:listIDP, nameP, nil]];
  306. }
  307. - (NSError *)setModeOfListWithID:(NSString *)listID toPrivate:(BOOL)isPrivate {
  308. if (listID.length == 0) {
  309. return getBadRequestError();
  310. }
  311. NSURL *baseURL = [NSURL URLWithString:url_lists_update];
  312. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  313. OARequestParameter *listIDP = [OARequestParameter requestParameterWithName:@"list_id" value:listID];
  314. OARequestParameter *isPrivateP = [OARequestParameter requestParameterWithName:@"mode" value:isPrivate?@"private":@"public"];
  315. return [self sendPOSTRequest:request withParameters:[NSArray arrayWithObjects:listIDP, isPrivateP, nil]];
  316. }
  317. - (id)listUsersInListWithID:(NSString *)listID {
  318. if (listID.length == 0) {
  319. return getBadRequestError();
  320. }
  321. NSURL *baseURL = [NSURL URLWithString:url_lists_members];
  322. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  323. OARequestParameter *listIDP = [OARequestParameter requestParameterWithName:@"list_id" value:listID];
  324. return [self sendGETRequest:request withParameters:[NSArray arrayWithObjects:listIDP, nil]];
  325. }
  326. - (NSError *)removeUsersFromListWithID:(NSString *)listID users:(NSArray *)users {
  327. if (users.count == 0) {
  328. return getBadRequestError();
  329. }
  330. if (users.count > 100) {
  331. return getBadRequestError();
  332. }
  333. if (listID.length == 0) {
  334. return getBadRequestError();
  335. }
  336. NSURL *baseURL = [NSURL URLWithString:url_lists_members_destroy_all];
  337. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  338. OARequestParameter *screen_name = [OARequestParameter requestParameterWithName:@"screen_name" value:[users componentsJoinedByString:@","]];
  339. return [self sendPOSTRequest:request withParameters:[NSArray arrayWithObjects:screen_name, nil]];
  340. }
  341. - (NSError *)addUsersToListWithID:(NSString *)listID users:(NSArray *)users {
  342. if (users.count == 0) {
  343. return getBadRequestError();
  344. }
  345. if (users.count > 100) {
  346. return getBadRequestError();
  347. }
  348. if (listID.length == 0) {
  349. return getBadRequestError();
  350. }
  351. NSURL *baseURL = [NSURL URLWithString:url_lists_members_create_all];
  352. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  353. OARequestParameter *screen_name = [OARequestParameter requestParameterWithName:@"screen_name" value:[users componentsJoinedByString:@","]];
  354. return [self sendPOSTRequest:request withParameters:[NSArray arrayWithObjects:screen_name, nil]];
  355. }
  356. - (id)getTimelineForListWithID:(NSString *)listID count:(int)count {
  357. return [self getTimelineForListWithID:listID count:count sinceID:nil maxID:nil];
  358. }
  359. - (id)getTimelineForListWithID:(NSString *)listID count:(int)count sinceID:(NSString *)sinceID maxID:(NSString *)maxID {
  360. return [self getTimelineForListWithID:listID count:count sinceID:sinceID maxID:maxID excludeRetweets:YES excludeReplies:YES];
  361. }
  362. - (id)getTimelineForListWithID:(NSString *)listID count:(int)count excludeRetweets:(BOOL)excludeRetweets excludeReplies:(BOOL)excludeReplies {
  363. return [self getTimelineForListWithID:listID count:count sinceID:nil maxID:nil excludeRetweets:excludeRetweets excludeReplies:excludeReplies];
  364. }
  365. - (id)getTimelineForListWithID:(NSString *)listID count:(int)count sinceID:(NSString *)sinceID maxID:(NSString *)maxID excludeRetweets:(BOOL)excludeRetweets excludeReplies:(BOOL)excludeReplies {
  366. if (count == 0) {
  367. return nil;
  368. }
  369. if (listID.length == 0) {
  370. return getBadRequestError();
  371. }
  372. NSURL *baseURL = [NSURL URLWithString:url_lists_statuses];
  373. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  374. OARequestParameter *countP = [OARequestParameter requestParameterWithName:@"count" value:[NSString stringWithFormat:@"%d",count]];
  375. OARequestParameter *excludeRepliesP = [OARequestParameter requestParameterWithName:@"exclude_replies" value:excludeReplies?@"true":@"false"];
  376. OARequestParameter *includeRTsP = [OARequestParameter requestParameterWithName:@"include_rts" value:excludeRetweets?@"false":@"true"];
  377. OARequestParameter *listIDP = [OARequestParameter requestParameterWithName:@"list_id" value:listID];
  378. NSMutableArray *params = [NSMutableArray arrayWithObjects:countP, excludeRepliesP, includeRTsP, listIDP, nil];
  379. if (sinceID.length > 0) {
  380. [params addObject:[OARequestParameter requestParameterWithName:@"since_id" value:sinceID]];
  381. }
  382. if (maxID.length > 0) {
  383. [params addObject:[OARequestParameter requestParameterWithName:@"max_id" value:maxID]];
  384. }
  385. return [self sendGETRequest:request withParameters:params];
  386. }
  387. - (id)getListsForUser:(NSString *)user isID:(BOOL)isID {
  388. if (user.length == 0) {
  389. return getBadRequestError();
  390. }
  391. NSURL *baseURL = [NSURL URLWithString:url_lists_list];
  392. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  393. OARequestParameter *userP = [OARequestParameter requestParameterWithName:isID?@"user_id":@"screen_name" value:user];
  394. return [self sendGETRequest:request withParameters:[NSArray arrayWithObjects:userP, nil]];
  395. }
  396. - (id)getRetweetsForTweet:(NSString *)identifier count:(int)count {
  397. if (count == 0) {
  398. return nil;
  399. }
  400. if (identifier.length == 0) {
  401. return getBadRequestError();
  402. }
  403. NSURL *baseURL = [NSURL URLWithString:[NSString stringWithFormat:@"https://api.twitter.com/1.1/statuses/retweets/%@.json",identifier]];
  404. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  405. OARequestParameter *identifierP = [OARequestParameter requestParameterWithName:@"count" value:[NSString stringWithFormat:@"%d",count]];
  406. return [self sendGETRequest:request withParameters:[NSArray arrayWithObjects:identifierP, nil]];
  407. }
  408. - (id)getRetweetedTimelineWithCount:(int)count {
  409. return [self getRetweetedTimelineWithCount:count sinceID:nil maxID:nil];
  410. }
  411. - (id)getRetweetedTimelineWithCount:(int)count sinceID:(NSString *)sinceID maxID:(NSString *)maxID {
  412. if (count == 0) {
  413. return nil;
  414. }
  415. NSURL *baseURL = [NSURL URLWithString:url_statuses_retweets_of_me];
  416. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  417. OARequestParameter *countP = [OARequestParameter requestParameterWithName:@"count" value:[NSString stringWithFormat:@"%d",count]];
  418. OARequestParameter *excludeRepliesP = [OARequestParameter requestParameterWithName:@"exclude_replies" value:@"false"];
  419. OARequestParameter *includeRTsP = [OARequestParameter requestParameterWithName:@"include_rts" value:@"true"];
  420. NSMutableArray *params = [NSMutableArray array];
  421. [params addObject:countP];
  422. [params addObject:excludeRepliesP];
  423. [params addObject:includeRTsP];
  424. if (sinceID.length > 0) {
  425. [params addObject:[OARequestParameter requestParameterWithName:@"since_id" value:sinceID]];
  426. }
  427. if (maxID.length > 0) {
  428. [params addObject:[OARequestParameter requestParameterWithName:@"max_id" value:maxID]];
  429. }
  430. return [self sendGETRequest:request withParameters:params];
  431. }
  432. - (id)getMentionsTimelineWithCount:(int)count {
  433. return [self getMentionsTimelineWithCount:count sinceID:nil maxID:nil];
  434. }
  435. - (id)getMentionsTimelineWithCount:(int)count sinceID:(NSString *)sinceID maxID:(NSString *)maxID {
  436. if (count == 0) {
  437. return nil;
  438. }
  439. NSURL *baseURL = [NSURL URLWithString:url_statuses_metions_timeline];
  440. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  441. OARequestParameter *countP = [OARequestParameter requestParameterWithName:@"count" value:[NSString stringWithFormat:@"%d",count]];
  442. OARequestParameter *excludeRepliesP = [OARequestParameter requestParameterWithName:@"exclude_replies" value:@"false"];
  443. OARequestParameter *includeRTsP = [OARequestParameter requestParameterWithName:@"include_rts" value:@"true"];
  444. NSMutableArray *params = [NSMutableArray array];
  445. [params addObject:countP];
  446. [params addObject:excludeRepliesP];
  447. [params addObject:includeRTsP];
  448. if (sinceID.length > 0) {
  449. [params addObject:[OARequestParameter requestParameterWithName:@"since_id" value:sinceID]];
  450. }
  451. if (maxID.length > 0) {
  452. [params addObject:[OARequestParameter requestParameterWithName:@"max_id" value:maxID]];
  453. }
  454. return [self sendGETRequest:request withParameters:params];
  455. }
  456. - (NSError *)postTweet:(NSString *)tweetString withImageData:(NSData *)theData {
  457. return [self postTweet:tweetString withImageData:theData inReplyTo:nil];
  458. }
  459. - (NSError *)postTweet:(NSString *)tweetString withImageData:(NSData *)theData inReplyTo:(NSString *)irt {
  460. if (tweetString.length == 0) {
  461. return getBadRequestError();
  462. }
  463. if (theData.length == 0) {
  464. return getBadRequestError();
  465. }
  466. NSURL *baseURL = [NSURL URLWithString:url_statuses_update_with_media];
  467. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  468. OARequestParameter *statusP = [OARequestParameter requestParameterWithName:@"status" value:tweetString];
  469. OARequestParameter *mediaP = [OARequestParameter requestParameterWithName:@"media_data[]" value:[theData base64EncodingWithLineLength:0]];
  470. NSMutableArray *params = [NSMutableArray arrayWithObjects:statusP, mediaP, nil];
  471. if (irt.length > 0) {
  472. [params addObject:[OARequestParameter requestParameterWithName:@"in_reply_to_status_id" value:irt]];
  473. }
  474. return [self sendPOSTRequest:request withParameters:params];
  475. }
  476. - (NSError *)destroyTweet:(NSString *)identifier {
  477. if (identifier.length == 0) {
  478. return getBadRequestError();
  479. }
  480. NSURL *baseURL = [NSURL URLWithString:url_statuses_destroy];
  481. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  482. OARequestParameter *identifierP = [OARequestParameter requestParameterWithName:@"id" value:identifier];
  483. return [self sendPOSTRequest:request withParameters:[NSArray arrayWithObjects:identifierP, nil]];
  484. }
  485. - (id)getDetailsForTweet:(NSString *)identifier {
  486. if (identifier.length == 0) {
  487. return getBadRequestError();
  488. }
  489. NSURL *baseURL = [NSURL URLWithString:url_statuses_show];
  490. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  491. OARequestParameter *identifierP = [OARequestParameter requestParameterWithName:@"id" value:identifier];
  492. OARequestParameter *includeMyRetweet = [OARequestParameter requestParameterWithName:@"include_my_retweet" value:@"true"];
  493. return [self sendGETRequest:request withParameters:[NSArray arrayWithObjects:includeMyRetweet, identifierP, nil]];
  494. }
  495. - (id)oembedTweet:(NSString *)identifier maxWidth:(float)maxWidth alignmentMode:(FHSTwitterEngineAlignMode)alignmentMode {
  496. if (identifier.length == 0) {
  497. return getBadRequestError();
  498. }
  499. NSString *language = [[NSLocale preferredLanguages]objectAtIndex:0];
  500. NSString *alignment = [[NSArray arrayWithObjects:@"left", @"right", @"center", @"none", nil]objectAtIndex:alignmentMode];
  501. NSURL *baseURL = [NSURL URLWithString:url_statuses_oembed];
  502. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  503. OARequestParameter *identifierP = [OARequestParameter requestParameterWithName:@"id" value:identifier];
  504. OARequestParameter *maxWidthP = [OARequestParameter requestParameterWithName:@"maxwidth" value:[NSString stringWithFormat:@"%f",maxWidth]];
  505. OARequestParameter *languageP= [OARequestParameter requestParameterWithName:@"lang" value:language];
  506. OARequestParameter *alignmentP = [OARequestParameter requestParameterWithName:@"align" value:alignment];
  507. return [self sendGETRequest:request withParameters:[NSArray arrayWithObjects:identifierP, maxWidthP, languageP,alignmentP, nil]];
  508. }
  509. - (NSError *)retweet:(NSString *)identifier {
  510. if (identifier.length == 0) {
  511. return getBadRequestError();
  512. }
  513. NSURL *baseURL = [NSURL URLWithString:[NSString stringWithFormat:@"https://api.twitter.com/1.1/statuses/retweet/%@.json",identifier]];
  514. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  515. return [self sendPOSTRequest:request withParameters:nil];
  516. }
  517. - (id)getTimelineForUser:(NSString *)user isID:(BOOL)isID count:(int)count {
  518. return [self getTimelineForUser:user isID:isID count:count sinceID:nil maxID:nil];
  519. }
  520. - (id)getTimelineForUser:(NSString *)user isID:(BOOL)isID count:(int)count sinceID:(NSString *)sinceID maxID:(NSString *)maxID {
  521. if (count == 0) {
  522. return nil;
  523. }
  524. if (user.length == 0) {
  525. return getBadRequestError();
  526. }
  527. NSURL *baseURL = [NSURL URLWithString:url_statuses_user_timeline];
  528. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  529. OARequestParameter *countP = [OARequestParameter requestParameterWithName:@"count" value:[NSString stringWithFormat:@"%d",count]];
  530. OARequestParameter *userP = [OARequestParameter requestParameterWithName:isID?@"user_id":@"screen_name" value:user];
  531. OARequestParameter *excludeRepliesP = [OARequestParameter requestParameterWithName:@"exclude_replies" value:@"false"];
  532. OARequestParameter *includeRTsP = [OARequestParameter requestParameterWithName:@"include_rts" value:@"true"];
  533. NSMutableArray *params = [NSMutableArray array];
  534. [params addObject:countP];
  535. [params addObject:userP];
  536. [params addObject:excludeRepliesP];
  537. [params addObject:includeRTsP];
  538. if (sinceID.length > 0) {
  539. [params addObject:[OARequestParameter requestParameterWithName:@"since_id" value:sinceID]];
  540. }
  541. if (maxID.length > 0) {
  542. [params addObject:[OARequestParameter requestParameterWithName:@"max_id" value:maxID]];
  543. }
  544. return [self sendGETRequest:request withParameters:params];
  545. }
  546. - (id)getProfileImageForUsername:(NSString *)username andSize:(FHSTwitterEngineImageSize)size {
  547. if (username.length == 0) {
  548. return getBadRequestError();
  549. }
  550. NSURL *baseURL = [NSURL URLWithString:url_users_show];
  551. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  552. OARequestParameter *usernameP = [OARequestParameter requestParameterWithName:@"screen_name" value:username];
  553. NSArray *params = [NSArray arrayWithObjects:usernameP, nil];
  554. id userShowReturn = [self sendGETRequest:request withParameters:params];
  555. if ([userShowReturn isKindOfClass:[NSError class]]) {
  556. return [NSError errorWithDomain:[(NSError *)userShowReturn domain] code:[(NSError *)userShowReturn code] userInfo:[NSDictionary dictionaryWithObject:request forKey:@"request"]];
  557. } else if ([userShowReturn isKindOfClass:[NSDictionary class]]) {
  558. NSString *url = [userShowReturn objectForKey:@"profile_image_url"]; // normal
  559. if (size == 0) { // mini
  560. url = [url stringByReplacingOccurrencesOfString:@"_normal" withString:@"_mini"];
  561. } else if (size == 2) { // bigger
  562. url = [url stringByReplacingOccurrencesOfString:@"_normal" withString:@"_bigger"];
  563. } else if (size == 3) { // original
  564. url = [url stringByReplacingOccurrencesOfString:@"_normal" withString:@""];
  565. }
  566. id ret = [self sendRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]];
  567. if ([ret isKindOfClass:[NSData class]]) {
  568. return [UIImage imageWithData:(NSData *)ret];
  569. }
  570. return ret;
  571. }
  572. return [NSError errorWithDomain:@"Bad Request: The request you attempted to make messed up royally." code:400 userInfo:nil];
  573. }
  574. - (id)authenticatedUserIsBlocking:(NSString *)user isID:(BOOL)isID {
  575. if (user.length == 0) {
  576. return getBadRequestError();
  577. }
  578. NSURL *baseURL = [NSURL URLWithString:url_blocks_exists];
  579. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  580. OARequestParameter *userP = [OARequestParameter requestParameterWithName:isID?@"user_id":@"screen_name" value:user];
  581. OARequestParameter *skipstatusP = [OARequestParameter requestParameterWithName:@"skip_status" value:@"true"];
  582. id obj = [self sendGETRequest:request withParameters:[NSArray arrayWithObjects:skipstatusP, userP, nil]];
  583. if ([obj isKindOfClass:[NSError class]]) {
  584. return obj;
  585. } else if ([obj isKindOfClass:[NSDictionary class]]) {
  586. if ([[obj objectForKey:@"error"]isEqualToString:@"You are not blocking this user."]) {
  587. return @"NO";
  588. } else {
  589. return @"YES";
  590. }
  591. }
  592. return getBadRequestError();
  593. }
  594. - (id)listBlockedUsers {
  595. NSURL *baseURL = [NSURL URLWithString:url_blocks_blocking];
  596. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  597. OARequestParameter *skipstatusP = [OARequestParameter requestParameterWithName:@"skip_status" value:@"true"];
  598. return [self sendGETRequest:request withParameters:[NSArray arrayWithObjects:skipstatusP, nil]];
  599. }
  600. - (id)listBlockedIDs {
  601. NSURL *baseURL = [NSURL URLWithString:url_blocks_blocking_ids];
  602. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  603. OARequestParameter *stringifyIDsP = [OARequestParameter requestParameterWithName:@"stringify_ids" value:@"true"];
  604. id object = [self sendGETRequest:request withParameters:[NSArray arrayWithObjects:stringifyIDsP, nil]];
  605. if ([object isKindOfClass:[NSDictionary class]]) {
  606. return [(NSDictionary *)object objectForKey:@"ids"];
  607. }
  608. return object;
  609. }
  610. - (id)getLanguages {
  611. NSURL *baseURL = [NSURL URLWithString:url_help_languages];
  612. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  613. return [self sendGETRequest:request withParameters:nil];
  614. }
  615. - (id)getConfiguration {
  616. NSURL *baseURL = [NSURL URLWithString:url_help_configuration];
  617. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  618. return [self sendGETRequest:request withParameters:nil];
  619. }
  620. - (NSError *)reportUserAsSpam:(NSString *)user isID:(BOOL)isID {
  621. if (user.length == 0) {
  622. return getBadRequestError();
  623. }
  624. NSURL *baseURL = [NSURL URLWithString:url_users_report_spam];
  625. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  626. OARequestParameter *userP = [OARequestParameter requestParameterWithName:isID?@"user_id":@"screen_name" value:user];
  627. return [self sendPOSTRequest:request withParameters:[NSArray arrayWithObjects:userP, nil]];
  628. }
  629. - (id)showDirectMessage:(NSString *)messageID {
  630. if (messageID.length == 0) {
  631. return getBadRequestError();
  632. }
  633. NSURL *baseURL = [NSURL URLWithString:url_direct_messages_show];
  634. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  635. OARequestParameter *id_ = [OARequestParameter requestParameterWithName:@"id" value:messageID];
  636. return [self sendGETRequest:request withParameters:[NSArray arrayWithObjects:id_, nil]];
  637. }
  638. - (NSError *)sendDirectMessage:(NSString *)body toUser:(NSString *)user isID:(BOOL)isID {
  639. if (user.length == 0) {
  640. return getBadRequestError();
  641. }
  642. if (body.length == 0) {
  643. return getBadRequestError();
  644. }
  645. NSURL *baseURL = [NSURL URLWithString:url_direct_messages_new];
  646. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  647. OARequestParameter *bodyP = [OARequestParameter requestParameterWithName:@"text" value:[body fhs_trimForTwitter]];
  648. OARequestParameter *userP = [OARequestParameter requestParameterWithName:isID?@"user_id":@"screen_name" value:user];
  649. return [self sendPOSTRequest:request withParameters:[NSArray arrayWithObjects:userP, bodyP, nil]];
  650. }
  651. - (id)getSentDirectMessages:(int)count {
  652. if (count == 0) {
  653. return nil;
  654. }
  655. NSURL *baseURL = [NSURL URLWithString:url_direct_messages_sent];
  656. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  657. OARequestParameter *countP = [OARequestParameter requestParameterWithName:@"count" value:[NSString stringWithFormat:@"%d",count]];
  658. return [self sendGETRequest:request withParameters:[NSArray arrayWithObjects:countP, nil]];
  659. }
  660. - (NSError *)deleteDirectMessage:(NSString *)messageID {
  661. if (messageID.length == 0) {
  662. return getBadRequestError();
  663. }
  664. NSURL *baseURL = [NSURL URLWithString:url_direct_messages_destroy];
  665. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  666. OARequestParameter *identitifierP = [OARequestParameter requestParameterWithName:@"id" value:messageID];
  667. OARequestParameter *includeEntitiesP = [OARequestParameter requestParameterWithName:@"include_entities" value:self.includeEntities?@"true":@"false"];
  668. return [self sendPOSTRequest:request withParameters:[NSArray arrayWithObjects:identitifierP, includeEntitiesP, nil]];
  669. }
  670. - (id)getDirectMessages:(int)count {
  671. if (count == 0) {
  672. return nil;
  673. }
  674. NSURL *baseURL = [NSURL URLWithString:url_direct_messages];
  675. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  676. OARequestParameter *countP = [OARequestParameter requestParameterWithName:@"count" value:[NSString stringWithFormat:@"%d",count]];
  677. OARequestParameter *skipStatusP = [OARequestParameter requestParameterWithName:@"skip_status" value:@"true"];
  678. return [self sendGETRequest:request withParameters:[NSArray arrayWithObjects:countP, skipStatusP, nil]];
  679. }
  680. - (id)getPrivacyPolicy {
  681. NSURL *baseURL = [NSURL URLWithString:url_help_privacy];
  682. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  683. id object = [self sendGETRequest:request withParameters:nil];
  684. if ([object isKindOfClass:[NSDictionary class]]) {
  685. NSDictionary *dict = (NSDictionary *)object;
  686. if ([dict.allKeys containsObject:@"privacy"]) {
  687. return [dict objectForKey:@"privacy"];
  688. }
  689. }
  690. return object;
  691. }
  692. - (id)getTermsOfService {
  693. NSURL *baseURL = [NSURL URLWithString:url_help_tos];
  694. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  695. id object = [self sendGETRequest:request withParameters:nil];
  696. if ([object isKindOfClass:[NSDictionary class]]) {
  697. NSDictionary *dict = (NSDictionary *)object;
  698. if ([dict.allKeys containsObject:@"tos"]) {
  699. return [dict objectForKey:@"tos"];
  700. }
  701. }
  702. return object;
  703. }
  704. - (id)getNoRetweetIDs {
  705. NSURL *baseURL = [NSURL URLWithString:url_friendships_no_retweets_ids];
  706. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  707. OARequestParameter *stringifyIDsP = [OARequestParameter requestParameterWithName:@"stringify_ids" value:@"true"];
  708. return [self sendGETRequest:request withParameters:[NSArray arrayWithObjects:stringifyIDsP, nil]];
  709. }
  710. - (NSError *)enableRetweets:(BOOL)enableRTs andDeviceNotifs:(BOOL)devNotifs forUser:(NSString *)user isID:(BOOL)isID {
  711. if (user.length == 0) {
  712. return getBadRequestError();
  713. }
  714. NSURL *baseURL = [NSURL URLWithString:url_friendships_update];
  715. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  716. OARequestParameter *userP = [OARequestParameter requestParameterWithName:isID?@"user_id":@"screen_name" value:user];
  717. OARequestParameter *retweetsP = [OARequestParameter requestParameterWithName:@"retweets" value:enableRTs?@"true":@"false"];
  718. OARequestParameter *deviceP = [OARequestParameter requestParameterWithName:@"device" value:devNotifs?@"true":@"false"];
  719. return [self sendPOSTRequest:request withParameters:[NSArray arrayWithObjects:userP, retweetsP, deviceP, nil]];
  720. }
  721. - (id)getPendingOutgoingFollowers {
  722. NSURL *baseURL = [NSURL URLWithString:url_friendships_outgoing];
  723. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  724. OARequestParameter *stringifyIDsP = [OARequestParameter requestParameterWithName:@"stringify_ids" value:@"true"];
  725. id object = [self sendGETRequest:request withParameters:[NSArray arrayWithObjects:stringifyIDsP, nil]];
  726. if ([object isKindOfClass:[NSDictionary class]]) {
  727. NSDictionary *dict = (NSDictionary *)object;
  728. if ([dict.allKeys containsObject:@"ids"]) {
  729. return [dict objectForKey:@"ids"];
  730. }
  731. }
  732. return object;
  733. }
  734. - (id)getPendingIncomingFollowers {
  735. NSURL *baseURL = [NSURL URLWithString:url_friendships_incoming];
  736. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  737. OARequestParameter *stringifyIDsP = [OARequestParameter requestParameterWithName:@"stringify_ids" value:@"true"];
  738. id object = [self sendGETRequest:request withParameters:[NSArray arrayWithObjects:stringifyIDsP, nil]];
  739. if ([object isKindOfClass:[NSDictionary class]]) {
  740. NSDictionary *dict = (NSDictionary *)object;
  741. if ([dict.allKeys containsObject:@"ids"]) {
  742. return [dict objectForKey:@"ids"];
  743. }
  744. }
  745. return object;
  746. }
  747. - (id)lookupFriendshipStatusForUsers:(NSArray *)users areIDs:(BOOL)areIDs {
  748. if (users.count == 0) {
  749. return nil;
  750. }
  751. NSMutableArray *returnedDictionaries = [NSMutableArray array];
  752. NSArray *reqStrings = [self generateRequestStringsFromArray:users];
  753. NSURL *baseURL = [NSURL URLWithString:url_friendships_lookup];
  754. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  755. for (NSString *reqString in reqStrings) {
  756. OARequestParameter *userP = [OARequestParameter requestParameterWithName:areIDs?@"user_id":@"screen_name" value:reqString];
  757. id retObj = [self sendGETRequest:request withParameters:[NSArray arrayWithObjects:userP, nil]];
  758. if ([retObj isKindOfClass:[NSArray class]]) {
  759. [returnedDictionaries addObjectsFromArray:(NSArray *)retObj];
  760. } else if ([retObj isKindOfClass:[NSError class]]) {
  761. return retObj;
  762. }
  763. }
  764. return returnedDictionaries;
  765. }
  766. - (NSError *)unfollowUser:(NSString *)user isID:(BOOL)isID {
  767. if (user.length == 0) {
  768. return getBadRequestError();
  769. }
  770. NSURL *baseURL = [NSURL URLWithString:url_friendships_destroy];
  771. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  772. OARequestParameter *userP = [OARequestParameter requestParameterWithName:isID?@"user_id":@"screen_name" value:user];
  773. return [self sendPOSTRequest:request withParameters:[NSArray arrayWithObjects:userP, nil]];
  774. }
  775. - (NSError *)followUser:(NSString *)user isID:(BOOL)isID {
  776. if (user.length == 0) {
  777. return getBadRequestError();
  778. }
  779. NSURL *baseURL = [NSURL URLWithString:url_friendships_create];
  780. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  781. OARequestParameter *userP = [OARequestParameter requestParameterWithName:isID?@"user_id":@"screen_name" value:user];
  782. return [self sendPOSTRequest:request withParameters:[NSArray arrayWithObjects:userP, nil]];
  783. }
  784. - (id)verifyCredentials {
  785. NSURL *baseURL = [NSURL URLWithString:url_account_verify_credentials];
  786. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  787. return [self sendGETRequest:request withParameters:nil];
  788. }
  789. - (id)getFavoritesForUser:(NSString *)user isID:(BOOL)isID andCount:(int)count {
  790. return [self getFavoritesForUser:user isID:isID andCount:count sinceID:nil maxID:nil];
  791. }
  792. - (id)getFavoritesForUser:(NSString *)user isID:(BOOL)isID andCount:(int)count sinceID:(NSString *)sinceID maxID:(NSString *)maxID {
  793. if (count == 0) {
  794. return nil;
  795. }
  796. if (user.length == 0) {
  797. return getBadRequestError();
  798. }
  799. NSURL *baseURL = [NSURL URLWithString:url_favorites_list];
  800. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  801. OARequestParameter *countP = [OARequestParameter requestParameterWithName:@"count" value:[NSString stringWithFormat:@"%d",count]];
  802. OARequestParameter *userP = [OARequestParameter requestParameterWithName:isID?@"user_id":@"screen_name" value:user];
  803. OARequestParameter *includeEntitiesP = [OARequestParameter requestParameterWithName:@"include_entities" value:self.includeEntities?@"true":@"false"];
  804. NSMutableArray *params = [NSMutableArray arrayWithObjects:countP, userP, includeEntitiesP, nil];
  805. if (sinceID.length > 0) {
  806. [params addObject:[OARequestParameter requestParameterWithName:@"since_id" value:sinceID]];
  807. }
  808. if (maxID.length > 0) {
  809. [params addObject:[OARequestParameter requestParameterWithName:@"max_id" value:maxID]];
  810. }
  811. return [self sendGETRequest:request withParameters:params];
  812. }
  813. - (NSError *)markTweet:(NSString *)tweetID asFavorite:(BOOL)flag {
  814. if (tweetID.length == 0) {
  815. return getBadRequestError();
  816. }
  817. NSURL *baseURL = [NSURL URLWithString:flag?url_favorites_create:url_favorites_destroy];
  818. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  819. OARequestParameter *idP = [OARequestParameter requestParameterWithName:@"id" value:tweetID];
  820. return [self sendPOSTRequest:request withParameters:[NSArray arrayWithObjects:idP, nil]];
  821. }
  822. - (id)getRateLimitStatus {
  823. NSURL *baseURL = [NSURL URLWithString:url_application_rate_limit_status];
  824. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  825. return [self sendGETRequest:request withParameters:nil];
  826. }
  827. - (NSError *)updateProfileColorsWithDictionary:(NSDictionary *)dictionary {
  828. // profile_background_color - hex color
  829. // profile_link_color - hex color
  830. // profile_sidebar_border_color - hex color
  831. // profile_sidebar_fill_color - hex color
  832. // profile_text_color - hex color
  833. NSString *profile_background_color = nil;
  834. NSString *profile_link_color = nil;
  835. NSString *profile_sidebar_border_color = nil;
  836. NSString *profile_sidebar_fill_color = nil;
  837. NSString *profile_text_color = nil;
  838. if (!dictionary) {
  839. profile_background_color = @"C0DEED";
  840. profile_link_color = @"0084B4";
  841. profile_sidebar_border_color = @"C0DEED";
  842. profile_sidebar_fill_color = @"DDEEF6";
  843. profile_text_color = @"333333";
  844. } else {
  845. profile_background_color = [dictionary objectForKey:FHSProfileBackgroundColorKey];
  846. profile_link_color = [dictionary objectForKey:FHSProfileLinkColorKey];
  847. profile_sidebar_border_color = [dictionary objectForKey:FHSProfileSidebarBorderColorKey];
  848. profile_sidebar_fill_color = [dictionary objectForKey:FHSProfileSidebarFillColorKey];
  849. profile_text_color = [dictionary objectForKey:FHSProfileTextColorKey];
  850. }
  851. NSURL *baseURL = [NSURL URLWithString:url_account_update_profile_colors];
  852. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  853. OARequestParameter *skipStatus = [OARequestParameter requestParameterWithName:@"skip_status" value:@"true"];
  854. NSMutableArray *params = [NSMutableArray arrayWithObjects:skipStatus, nil];
  855. if (profile_background_color.length > 0) {
  856. [params addObject:[OARequestParameter requestParameterWithName:@"profile_background_color" value:profile_background_color]];
  857. }
  858. if (profile_link_color.length > 0) {
  859. [params addObject:[OARequestParameter requestParameterWithName:@"profile_link_color" value:profile_link_color]];
  860. }
  861. if (profile_sidebar_border_color.length > 0) {
  862. [params addObject:[OARequestParameter requestParameterWithName:@"profile_sidebar_border_color" value:profile_sidebar_border_color]];
  863. }
  864. if (profile_sidebar_fill_color.length > 0) {
  865. [params addObject:[OARequestParameter requestParameterWithName:@"profile_sidebar_fill_color" value:profile_sidebar_fill_color]];
  866. }
  867. if (profile_text_color.length > 0) {
  868. [params addObject:[OARequestParameter requestParameterWithName:@"profile_text_color" value:profile_text_color]];
  869. }
  870. return [self sendPOSTRequest:request withParameters:params];
  871. }
  872. - (NSError *)setUseProfileBackgroundImage:(BOOL)shouldUseBGImg {
  873. NSURL *baseURL = [NSURL URLWithString:url_account_update_profile_background_image];
  874. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  875. OARequestParameter *skipStatus = [OARequestParameter requestParameterWithName:@"skip_status" value:@"true"];
  876. OARequestParameter *useImage = [OARequestParameter requestParameterWithName:@"profile_use_background_image" value:shouldUseBGImg?@"true":@"false"];
  877. return [self sendPOSTRequest:request withParameters:[NSArray arrayWithObjects:skipStatus, useImage, nil]];
  878. }
  879. - (NSError *)setProfileBackgroundImageWithImageData:(NSData *)data tiled:(BOOL)isTiled {
  880. if (data.length == 0) {
  881. return getBadRequestError();
  882. }
  883. if (data.length >= 800000) {
  884. return [NSError errorWithDomain:@"The image you are trying to upload is too large." code:422 userInfo:nil];
  885. }
  886. NSURL *baseURL = [NSURL URLWithString:url_account_update_profile_background_image];
  887. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  888. OARequestParameter *tiled = [OARequestParameter requestParameterWithName:@"tiled" value:isTiled?@"true":@"false"];
  889. OARequestParameter *skipStatus = [OARequestParameter requestParameterWithName:@"skip_status" value:@"true"];
  890. OARequestParameter *useImage = [OARequestParameter requestParameterWithName:@"profile_use_background_image" value:@"true"];
  891. OARequestParameter *image = [OARequestParameter requestParameterWithName:@"image" value:[data base64EncodingWithLineLength:0]];
  892. return [self sendPOSTRequest:request withParameters:[NSArray arrayWithObjects:tiled, skipStatus, useImage, image, nil]];
  893. }
  894. - (NSError *)setProfileBackgroundImageWithImageAtPath:(NSString *)file tiled:(BOOL)isTiled {
  895. return [self setProfileBackgroundImageWithImageData:[NSData dataWithContentsOfFile:file] tiled:isTiled];
  896. }
  897. - (NSError *)setProfileImageWithImageData:(NSData *)data {
  898. if (data.length == 0) {
  899. return getBadRequestError();
  900. }
  901. if (data.length >= 700000) {
  902. return [NSError errorWithDomain:@"The image you are trying to upload is too large." code:422 userInfo:nil];
  903. }
  904. NSURL *baseURL = [NSURL URLWithString:url_account_update_profile_image];
  905. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  906. OARequestParameter *skipStatus = [OARequestParameter requestParameterWithName:@"skip_status" value:@"true"];
  907. OARequestParameter *image = [OARequestParameter requestParameterWithName:@"image" value:[data base64EncodingWithLineLength:0]];
  908. return [self sendPOSTRequest:request withParameters:[NSArray arrayWithObjects:image, skipStatus, nil]];
  909. }
  910. - (NSError *)setProfileImageWithImageAtPath:(NSString *)file {
  911. return [self setProfileImageWithImageData:[NSData dataWithContentsOfFile:file]];
  912. }
  913. - (id)getUserSettings {
  914. NSURL *baseURL = [NSURL URLWithString:url_account_settings];
  915. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  916. return [self sendGETRequest:request withParameters:nil];
  917. }
  918. - (NSError *)updateUserProfileWithDictionary:(NSDictionary *)settings {
  919. if (!settings) {
  920. return getBadRequestError();
  921. }
  922. // all of the values are just non-normalized strings. They appear:
  923. // setting - length in characters
  924. // name - 20
  925. // url - 100
  926. // location - 30
  927. // description - 160
  928. NSString *name = [settings objectForKey:FHSProfileNameKey];
  929. NSString *url = [settings objectForKey:FHSProfileURLKey];
  930. NSString *location = [settings objectForKey:FHSProfileLocationKey];
  931. NSString *description = [settings objectForKey:FHSProfileDescriptionKey];
  932. NSURL *baseURL = [NSURL URLWithString:url_account_update_profile];
  933. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  934. OARequestParameter *skipStatus = [OARequestParameter requestParameterWithName:@"skip_status" value:@"true"];
  935. NSMutableArray *params = [NSMutableArray arrayWithObjects:skipStatus, nil];
  936. if (name.length > 0) {
  937. [params addObject:[OARequestParameter requestParameterWithName:@"name" value:name]];
  938. }
  939. if (url.length > 0) {
  940. [params addObject:[OARequestParameter requestParameterWithName:@"url" value:url]];
  941. }
  942. if (location.length > 0) {
  943. [params addObject:[OARequestParameter requestParameterWithName:@"location" value:location]];
  944. }
  945. if (description.length > 0) {
  946. [params addObject:[OARequestParameter requestParameterWithName:@"description" value:description]];
  947. }
  948. return [self sendPOSTRequest:request withParameters:params];
  949. }
  950. - (NSError *)updateSettingsWithDictionary:(NSDictionary *)settings {
  951. if (!settings) {
  952. return getBadRequestError();
  953. }
  954. // Dictionary with keys:
  955. // All strings... You could have guessed that.
  956. // sleep_time_enabled - true/false
  957. // start_sleep_time - UTC time
  958. // end_sleep_time - UTC time
  959. // time_zone - Europe/Copenhagen, Pacific/Tongatapu
  960. // lang - en, it, es
  961. NSString *sleep_time_enabled = [settings objectForKey:@"sleep_time_enabled"];
  962. NSString *start_sleep_time = [settings objectForKey:@"start_sleep_time"];
  963. NSString *end_sleep_time = [settings objectForKey:@"end_sleep_time"];
  964. NSString *time_zone = [settings objectForKey:@"time_zone"];
  965. NSString *lang = [settings objectForKey:@"lang"];
  966. NSURL *baseURL = [NSURL URLWithString:url_account_settings];
  967. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  968. NSMutableArray *params = [NSMutableArray array];
  969. if (sleep_time_enabled.length > 0) {
  970. [params addObject:[OARequestParameter requestParameterWithName:@"sleep_time_enabled" value:sleep_time_enabled]];
  971. }
  972. if (start_sleep_time.length > 0) {
  973. [params addObject:[OARequestParameter requestParameterWithName:@"start_sleep_time" value:start_sleep_time]];
  974. }
  975. if (end_sleep_time.length > 0) {
  976. [params addObject:[OARequestParameter requestParameterWithName:@"end_sleep_time" value:end_sleep_time]];
  977. }
  978. if (time_zone.length > 0) {
  979. [params addObject:[OARequestParameter requestParameterWithName:@"time_zone" value:time_zone]];
  980. }
  981. if (lang.length > 0) {
  982. [params addObject:[OARequestParameter requestParameterWithName:@"lang" value:lang]];
  983. }
  984. return [self sendPOSTRequest:request withParameters:params];
  985. }
  986. - (id)lookupUsers:(NSArray *)users areIDs:(BOOL)areIDs {
  987. if (users.count == 0) {
  988. return nil;
  989. }
  990. if (users.count > 100) {
  991. return getBadRequestError();
  992. }
  993. NSURL *baseURL = [NSURL URLWithString:url_users_lookup];
  994. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  995. OARequestParameter *usernames = [OARequestParameter requestParameterWithName:areIDs?@"user_id":@"screen_name" value:[users componentsJoinedByString:@","]];
  996. return [self sendGETRequest:request withParameters:[NSArray arrayWithObjects:usernames, nil]];
  997. }
  998. - (NSError *)unblock:(NSString *)username {
  999. if (username.length == 0) {
  1000. return getBadRequestError();
  1001. }
  1002. NSURL *baseURL = [NSURL URLWithString:url_blocks_destroy];
  1003. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  1004. OARequestParameter *usernameP = [OARequestParameter requestParameterWithName:@"screen_name" value:username];
  1005. return [self sendPOSTRequest:request withParameters:[NSArray arrayWithObjects:usernameP, nil]];
  1006. }
  1007. - (NSError *)block:(NSString *)username {
  1008. if (username.length == 0) {
  1009. return getBadRequestError();
  1010. }
  1011. NSURL *baseURL = [NSURL URLWithString:url_blocks_create];
  1012. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  1013. OARequestParameter *usernameP = [OARequestParameter requestParameterWithName:@"screen_name" value:username];
  1014. return [self sendPOSTRequest:request withParameters:[NSArray arrayWithObjects:usernameP, nil]];
  1015. }
  1016. - (id)testService {
  1017. NSURL *baseURL = [NSURL URLWithString:url_help_test];
  1018. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  1019. id retValue = [self sendGETRequest:request withParameters:nil];
  1020. if ([retValue isKindOfClass:[NSString class]]) {
  1021. if ([(NSString *)retValue isEqualToString:@"ok"]) {
  1022. return @"YES";
  1023. } else {
  1024. return @"NO";
  1025. }
  1026. } else if ([retValue isKindOfClass:[NSError class]]) {
  1027. return retValue;
  1028. }
  1029. return getBadRequestError();
  1030. }
  1031. - (id)getHomeTimelineSinceID:(NSString *)sinceID count:(int)count {
  1032. if (count == 0) {
  1033. return nil;
  1034. }
  1035. NSURL *baseURL = [NSURL URLWithString:url_statuses_home_timeline];
  1036. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  1037. OARequestParameter *countParam = [OARequestParameter requestParameterWithName:@"count" value:[NSString stringWithFormat:@"%d", count]];
  1038. NSMutableArray *params = [NSMutableArray arrayWithObjects:countParam, nil];
  1039. if (sinceID.length > 0) {
  1040. [params addObject:[OARequestParameter requestParameterWithName:@"since_id" value:sinceID]];
  1041. }
  1042. return [self sendGETRequest:request withParameters:params];
  1043. }
  1044. - (NSError *)postTweet:(NSString *)tweetString inReplyTo:(NSString *)inReplyToString {
  1045. if (tweetString.length == 0) {
  1046. return getBadRequestError();
  1047. }
  1048. NSURL *baseURL = [NSURL URLWithString:url_statuses_update];
  1049. OARequestParameter *status = [OARequestParameter requestParameterWithName:@"status" value:[tweetString fhs_trimForTwitter]];
  1050. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  1051. NSMutableArray *params = [NSMutableArray arrayWithObjects:status, nil];
  1052. if (inReplyToString.length > 0) {
  1053. [params addObject:[OARequestParameter requestParameterWithName:@"in_reply_to_status_id" value:inReplyToString]];
  1054. }
  1055. return [self sendPOSTRequest:request withParameters:params];
  1056. }
  1057. - (NSError *)postTweet:(NSString *)tweetString {
  1058. return [self postTweet:tweetString inReplyTo:nil];
  1059. }
  1060. - (id)getFollowersIDs {
  1061. NSURL *baseURL = [NSURL URLWithString:url_followers_ids];
  1062. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  1063. OARequestParameter *param = [OARequestParameter requestParameterWithName:@"screen_name" value:self.loggedInUsername];
  1064. OARequestParameter *stringify_ids = [OARequestParameter requestParameterWithName:@"stringify_ids" value:@"true"];
  1065. return [self sendGETRequest:request withParameters:[NSArray arrayWithObjects:param, stringify_ids, nil]];
  1066. }
  1067. - (id)getFriendsIDs {
  1068. NSURL *baseURL = [NSURL URLWithString:url_friends_ids];
  1069. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:self.accessToken];
  1070. OARequestParameter *param = [OARequestParameter requestParameterWithName:@"screen_name" value:self.loggedInUsername];
  1071. OARequestParameter *stringify_ids = [OARequestParameter requestParameterWithName:@"stringify_ids" value:@"true"];
  1072. return [self sendGETRequest:request withParameters:[NSArray arrayWithObjects:param, stringify_ids, nil]];
  1073. }
  1074. - (id)init {
  1075. self = [super init];
  1076. if (self) {
  1077. // Twitter API datestamps are UTC
  1078. // Don't question this code.
  1079. self.dateFormatter = [[[NSDateFormatter alloc]init]autorelease];
  1080. _dateFormatter.locale = [[[NSLocale alloc]initWithLocaleIdentifier:@"en_US"]autorelease];
  1081. _dateFormatter.dateStyle = NSDateFormatterLongStyle;
  1082. _dateFormatter.formatterBehavior = NSDateFormatterBehavior10_4;
  1083. _dateFormatter.dateFormat = @"EEE MMM dd HH:mm:ss ZZZZ yyyy";
  1084. }
  1085. return self;
  1086. }
  1087. // The shared* class method
  1088. + (FHSTwitterEngine *)sharedEngine {
  1089. @synchronized (self) {
  1090. if (sharedInstance == nil) {
  1091. [[self alloc]init];
  1092. }
  1093. }
  1094. return sharedInstance;
  1095. }
  1096. // Override stuff to make sure that the singleton is never dealloc'd. Fun.
  1097. + (id)allocWithZone:(NSZone *)zone {
  1098. @synchronized(self) {
  1099. if (sharedInstance == nil) {
  1100. sharedInstance = [super allocWithZone:zone];
  1101. return sharedInstance;
  1102. }
  1103. }
  1104. return nil;
  1105. }
  1106. - (id)retain {
  1107. return self;
  1108. }
  1109. - (oneway void)release {
  1110. // Do nothing
  1111. }
  1112. - (id)autorelease {
  1113. return self;
  1114. }
  1115. - (NSUInteger)retainCount {
  1116. return NSUIntegerMax;
  1117. }
  1118. - (NSArray *)generateRequestStringsFromArray:(NSArray *)array {
  1119. NSString *initialString = [array componentsJoinedByString:@","];
  1120. if (array.count <= 100) {
  1121. return [NSArray arrayWithObjects:initialString, nil];
  1122. }
  1123. int offset = 0;
  1124. int remainder = fmod(array.count, 100);
  1125. int numberOfStrings = (int)((array.count-remainder)/100);
  1126. NSMutableArray *reqStrs = [NSMutableArray array];
  1127. for (int i = 1; i <= numberOfStrings; ++i) {
  1128. NSString *ninetyNinththItem = (NSString *)[array objectAtIndex:(i*100)-1];
  1129. NSRange range = [initialString rangeOfString:ninetyNinththItem];
  1130. int endOffset = (int)(range.location+range.length);
  1131. NSRange rangeOfAString = NSMakeRange(offset, endOffset-offset);
  1132. offset = endOffset;
  1133. NSString *endResult = [initialString fhs_stringWithRange:rangeOfAString];
  1134. if ([[endResult substringToIndex:1]isEqualToString:@","]) {
  1135. endResult = [endResult substringFromIndex:1];
  1136. }
  1137. [reqStrs addObject:endResult];
  1138. }
  1139. NSString *remainderString = [initialString stringByReplacingOccurrencesOfString:[reqStrs componentsJoinedByString:@","] withString:@""];
  1140. if ([[remainderString substringToIndex:1]isEqualToString:@","]) {
  1141. remainderString = [remainderString substringFromIndex:1];
  1142. }
  1143. [reqStrs addObject:remainderString];
  1144. return reqStrs;
  1145. }
  1146. //
  1147. // XAuth
  1148. //
  1149. - (NSError *)getXAuthAccessTokenForUsername:(NSString *)username password:(NSString *)password {
  1150. if (password.length == 0) {
  1151. return [NSError errorWithDomain:@"Bad Request: The request you are trying to make is missing parameters." code:400 userInfo:nil];
  1152. }
  1153. if (username.length == 0) {
  1154. return [NSError errorWithDomain:@"Bad Request: The request you are trying to make is missing parameters." code:400 userInfo:nil];
  1155. }
  1156. NSURL *baseURL = [NSURL URLWithString:@"https://api.twitter.com/oauth/access_token"];
  1157. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:baseURL consumer:self.consumer token:nil];
  1158. OARequestParameter *x_auth_mode = [OARequestParameter requestParameterWithName:@"x_auth_mode" value:@"client_auth"];
  1159. OARequestParameter *x_auth_username = [OARequestParameter requestParameterWithName:@"x_auth_username" value:username];
  1160. OARequestParameter *x_auth_password = [OARequestParameter requestParameterWithName:@"x_auth_password" value:password];
  1161. [request setHTTPMethod:@"POST"];
  1162. [request setParameters:[NSArray arrayWithObjects:x_auth_mode, x_auth_username, x_auth_password, nil]];
  1163. [request prepare];
  1164. if (self.shouldClearConsumer) {
  1165. self.shouldClearConsumer = NO;
  1166. self.consumer = nil;
  1167. }
  1168. id ret = [self sendRequest:request];
  1169. if ([ret isKindOfClass:[NSError class]]) {
  1170. return ret;
  1171. }
  1172. NSString *httpBody = [[[NSString alloc]initWithData:(NSData *)ret encoding:NSUTF8StringEncoding]autorelease];
  1173. if (httpBody.length > 0) {
  1174. [self storeAccessToken:httpBody];
  1175. } else {
  1176. [self storeAccessToken:nil];
  1177. return [NSError errorWithDomain:@"Twitter messed up and did not return anything for some reason. Please try again later." code:500 userInfo:nil];
  1178. }
  1179. return nil;
  1180. }
  1181. //
  1182. // sendRequest:
  1183. //
  1184. - (id)sendRequest:(NSURLRequest *)request {
  1185. NSHTTPURLResponse *response = nil;
  1186. NSError *error = nil;
  1187. NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
  1188. if (error) {
  1189. return error;
  1190. }
  1191. if (response == nil) {
  1192. return error;
  1193. }
  1194. if (response.statusCode >= 304) {
  1195. return error;
  1196. }
  1197. if (data.length == 0) {
  1198. return error;
  1199. }
  1200. return data;
  1201. }
  1202. - (NSError *)sendPOSTRequest:(OAMutableURLRequest *)request withParameters:(NSArray *)params {
  1203. if (![self isAuthorized]) {
  1204. [self loadAccessToken];
  1205. if (![self isAuthorized]) {
  1206. return [NSError errorWithDomain:@"You are not authorized via OAuth" code:401 userInfo:[NSDictionary dictionaryWithObject:request forKey:@"request"]];
  1207. }
  1208. }
  1209. [request setHTTPMethod:@"POST"];
  1210. [request setParameters:params];
  1211. [request prepare];
  1212. if (_shouldClearConsumer) {
  1213. self.shouldClearConsumer = NO;
  1214. self.consumer = nil;
  1215. }
  1216. id retobj = [self sendRequest:request];
  1217. if (retobj == nil) {
  1218. return getNilReturnLengthError();
  1219. }
  1220. if ([retobj isKindOfClass:[NSError class]]) {
  1221. return retobj;
  1222. }
  1223. id parsedJSONResponse = removeNull([NSJSONSerialization JSONObjectWithData:(NSData *)retobj options:NSJSONReadingMutableContainers error:nil]);
  1224. if ([parsedJSONResponse isKindOfClass:[NSDictionary class]]) {
  1225. NSString *errorMessage = [parsedJSONResponse objectForKey:@"error"];
  1226. NSArray *errorArray = [parsedJSONResponse objectForKey:@"errors"];
  1227. if (errorMessage.length > 0) {
  1228. return [NSError errorWithDomain:errorMessage code:[[parsedJSONResponse objectForKey:@"code"]intValue] userInfo:[NSDictionary dictionaryWithObject:request forKey:@"request"]];
  1229. } else if (errorArray.count > 0) {
  1230. if (errorArray.count > 1) {
  1231. return [NSError errorWithDomain:@"Multiple Errors" code:1337 userInfo:[NSDictionary dictionaryWithObject:request forKey:@"request"]];
  1232. } else {
  1233. NSDictionary *theError = [errorArray objectAtIndex:0];
  1234. return [NSError errorWithDomain:[theError objectForKey:@"message"] code:[[theError objectForKey:@"code"]integerValue] userInfo:[NSDictionary dictionaryWithObject:request forKey:@"request"]];
  1235. }
  1236. }
  1237. }
  1238. return nil;
  1239. }
  1240. - (id)sendGETRequest:(OAMutableURLRequest *)request withParameters:(NSArray *)params {
  1241. if (![self isAuthorized]) {
  1242. [self loadAccessToken];
  1243. if (![self isAuthorized]) {
  1244. return [NSError errorWithDomain:@"You are not authorized via OAuth" code:401 userInfo:[NSDictionary dictionaryWithObject:request forKey:@"request"]];
  1245. }
  1246. }
  1247. [request setHTTPMethod:@"GET"];
  1248. [request setParameters:params];
  1249. [request prepare];
  1250. if (_shouldClearConsumer) {
  1251. self.shouldClearConsumer = NO;
  1252. self.consumer = nil;
  1253. }
  1254. id retobj = [self sendRequest:request];
  1255. if (retobj == nil) {
  1256. return getNilReturnLengthError();
  1257. }
  1258. if ([retobj isKindOfClass:[NSError class]]) {
  1259. return retobj;
  1260. }
  1261. id parsedJSONResponse = removeNull([NSJSONSerialization JSONObjectWithData:(NSData *)retobj options:NSJSONReadingMutableContainers error:nil]);
  1262. if ([parsedJSONResponse isKindOfClass:[NSDictionary class]]) {
  1263. NSString *errorMessage = [parsedJSONResponse objectForKey:@"error"];
  1264. NSArray *errorArray = [parsedJSONResponse objectForKey:@"errors"];
  1265. if (errorMessage.length > 0) {
  1266. return [NSError errorWithDomain:errorMessage code:[[parsedJSONResponse objectForKey:@"code"]intValue] userInfo:[NSDictionary dictionaryWithObject:request forKey:@"request"]];
  1267. } else if (errorArray.count > 0) {
  1268. if (errorArray.count > 1) {
  1269. return [NSError errorWithDomain:@"Multiple Errors" code:1337 userInfo:[NSDictionary dictionaryWithObject:request forKey:@"request"]];
  1270. } else {
  1271. NSDictionary *theError = [errorArray objectAtIndex:0];
  1272. return [NSError errorWithDomain:[theError objectForKey:@"message"] code:[[theError objectForKey:@"code"]integerValue] userInfo:[NSDictionary dictionaryWithObject:request forKey:@"request"]];
  1273. }
  1274. }
  1275. }
  1276. return parsedJSONResponse;
  1277. }
  1278. //
  1279. // OAuth
  1280. //
  1281. - (NSString *)getRequestTokenString {
  1282. NSURL *url = [NSURL URLWithString:@"https://api.twitter.com/oauth/request_token"];
  1283. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:url consumer:self.consumer token:nil];
  1284. [request setHTTPMethod:@"POST"];
  1285. [request prepare];
  1286. id retobj = [self sendRequest:request];
  1287. if ([retobj isKindOfClass:[NSData class]]) {
  1288. return [[[NSString alloc]initWithData:(NSData *)retobj encoding:NSUTF8StringEncoding]autorelease];
  1289. }
  1290. return nil;
  1291. }
  1292. - (BOOL)finishAuthWithRequestToken:(OAToken *)reqToken {
  1293. NSURL *url = [NSURL URLWithString:@"https://api.twitter.com/oauth/access_token"];
  1294. OAMutableURLRequest *request = [OAMutableURLRequest requestWithURL:url consumer:self.consumer token:reqToken];
  1295. [request setHTTPMethod:@"POST"];
  1296. [request prepare];
  1297. if (_shouldClearConsumer) {
  1298. self.shouldClearConsumer = NO;
  1299. self.consumer = nil;
  1300. }
  1301. id retobj = [self sendRequest:request];
  1302. if ([retobj isKindOfClass:[NSError class]]) {
  1303. return NO;
  1304. }
  1305. NSString *response = [[[NSString alloc]initWithData:(NSData *)retobj encoding:NSUTF8StringEncoding]autorelease];
  1306. if (response.length == 0) {
  1307. return NO;
  1308. }
  1309. [self storeAccessToken:response];
  1310. return YES;
  1311. }
  1312. //
  1313. // Access Token Management
  1314. //
  1315. - (void)loadAccessToken {
  1316. NSString *savedHttpBody = nil;
  1317. if (self.delegate && [self.delegate respondsToSelector:@selector(loadAccessToken)]) {
  1318. savedHttpBody = [self.delegate loadAccessToken];
  1319. } else {
  1320. savedHttpBody = [[NSUserDefaults standardUserDefaults]objectForKey:@"SavedAccessHTTPBody"];
  1321. }
  1322. self.accessToken = [OAToken tokenWithHTTPResponseBody:savedHttpBody];
  1323. self.loggedInUsername = [self extractValueForKey:@"screen_name" fromHTTPBody:savedHttpBody];
  1324. self.loggedInID = [self extractValueForKey:@"user_id" fromHTTPBody:savedHttpBody];
  1325. }
  1326. - (void)storeAccessToken:(NSString *)accessTokenZ {
  1327. self.accessToken = [OAToken tokenWithHTTPResponseBody:accessTokenZ];
  1328. self.loggedInUsername = [self extractValueForKey:@"screen_name" fromHTTPBody:accessTokenZ];
  1329. self.loggedInID = [self extractValueForKey:@"user_id" fromHTTPBody:accessTokenZ];
  1330. if ([self.delegate respondsToSelector:@selector(storeAccessToken:)]) {
  1331. [self.delegate storeAccessToken:accessTokenZ];
  1332. } else {
  1333. [[NSUserDefaults standardUserDefaults]setObject:accessTokenZ forKey:@"SavedAccessHTTPBody"];
  1334. }
  1335. }
  1336. - (NSString *)extractValueForKey:(NSString *)target fromHTTPBody:(NSString *)body {
  1337. if (body.length == 0) {
  1338. return nil;
  1339. }
  1340. if (target.length == 0) {
  1341. return nil;
  1342. }
  1343. NSArray *tuples = [body componentsSeparatedByString:@"&"];
  1344. if (tuples.count < 1) {
  1345. return nil;
  1346. }
  1347. for (NSString *tuple in tuples) {
  1348. NSArray *keyValueArray = [tuple componentsSeparatedByString:@"="];
  1349. if (keyValueArray.count >= 2) {
  1350. NSString *key = [keyValueArray objectAtIndex:0];
  1351. NSString *value = [keyValueArray objectAtIndex:1];
  1352. if ([key isEqualToString:target]) {
  1353. return value;
  1354. }
  1355. }
  1356. }
  1357. return nil;
  1358. }
  1359. - (BOOL)isAuthorized {
  1360. if (!self.consumer) {
  1361. return NO;
  1362. }
  1363. if (self.accessToken.key && self.accessToken.secret) {
  1364. if (self.accessToken.key.length > 0 && self.accessToken.secret.length > 0) {
  1365. return YES;
  1366. }
  1367. }
  1368. return NO;
  1369. }
  1370. - (void)clearAccessToken {
  1371. [self storeAccessToken:@""];
  1372. self.accessToken = nil;
  1373. self.loggedInUsername = nil;
  1374. }
  1375. - (NSDate *)getDateFromTwitterCreatedAt:(NSString *)twitterDate {
  1376. return [self.dateFormatter dateFromString:twitterDate];
  1377. }
  1378. - (void)clearConsumer {
  1379. self.consumer = nil;
  1380. }
  1381. - (void)permanentlySetConsumerKey:(NSString *)consumerKey andSecret:(NSString *)consumerSecret {
  1382. self.shouldClearConsumer = NO;
  1383. self.consumer = [OAConsumer consumerWithKey:consumerKey secret:consumerSecret];
  1384. }
  1385. - (void)temporarilySetConsumerKey:(NSString *)consumerKey andSecret:(NSString *)consumerSecret {
  1386. self.shouldClearConsumer = YES;
  1387. self.consumer = [OAConsumer consumerWithKey:consumerKey secret:consumerSecret];
  1388. }
  1389. - (void)showOAuthLoginControllerFromViewController:(UIViewController *)sender {
  1390. [self showOAuthLoginControllerFromViewController:sender withCompletion:nil];
  1391. }
  1392. - (void)showOAuthLoginControllerFromViewController:(UIViewController *)sender withCompletion:(void(^)(BOOL success))block {
  1393. FHSTwitterEngineController *vc = [[[FHSTwitterEngineController alloc]init]autorelease];
  1394. vc.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
  1395. objc_setAssociatedObject(authBlockKey, "FHSTwitterEngineOAuthCompletion", block, OBJC_ASSOCIATION_COPY_NONATOMIC);
  1396. [sender presentModalViewController:vc animated:YES];
  1397. }
  1398. + (BOOL)isConnectedToInternet {
  1399. struct sockaddr_in zeroAddress;
  1400. bzero(&zeroAddress, sizeof(zeroAddress));
  1401. zeroAddress.sin_len = sizeof(zeroAddress);
  1402. zeroAddress.sin_family = AF_INET;
  1403. SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)&zeroAddress);
  1404. if (reachability) {
  1405. SCNetworkReachabilityFlags flags;
  1406. BOOL worked = SCNetworkReachabilityGetFlags(reachability, &flags);
  1407. CFRelease(reachability);
  1408. if (worked) {
  1409. if ((flags & kSCNetworkReachabilityFlagsReachable) == 0) {
  1410. return NO;
  1411. }
  1412. if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0) {
  1413. return YES;
  1414. }
  1415. if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand) != 0) || (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0)) {
  1416. if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0) {
  1417. return YES;
  1418. }
  1419. }
  1420. if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN) {
  1421. return YES;
  1422. }
  1423. }
  1424. }
  1425. return NO;
  1426. }
  1427. - (void)dealloc {
  1428. [self setConsumer:nil];
  1429. [self setDateFormatter:nil];
  1430. [self setLoggedInUsername:nil];
  1431. [self setLoggedInID:nil];
  1432. [self setDelegate:nil];
  1433. [self setAccessToken:nil];
  1434. [super dealloc];
  1435. }
  1436. @end
  1437. @implementation FHSTwitterEngineController
  1438. static NSString * const newPinJS = @"var d = document.getElementById('oauth-pin'); if (d == null) d = document.getElementById('oauth_pin'); if (d) { var d2 = d.getElementsByTagName('code'); if (d2.length > 0) d2[0].innerHTML; }";
  1439. static NSString * const oldPinJS = @"var d = document.getElementById('oauth-pin'); if (d == null) d = document.getElementById('oauth_pin'); if (d) d = d.innerHTML; d;";
  1440. - (void)loadView {
  1441. [super loadView];
  1442. [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(pasteboardChanged:) name:UIPasteboardChangedNotification object:nil];
  1443. self.view = [[[UIView alloc]initWithFrame:CGRectMake(0, 0, 320, 460)]autorelease];
  1444. self.view.backgroundColor = [UIColor grayColor];
  1445. self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  1446. self.theWebView = [[[UIWebView alloc]initWithFrame:CGRectMake(0, 44, 320, 416)]autorelease];
  1447. _theWebView.hidden = YES;
  1448. _theWebView.delegate = self;
  1449. _theWebView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  1450. _theWebView.dataDetectorTypes = UIDataDetectorTypeNone;
  1451. _theWebView.backgroundColor = [UIColor darkGrayColor];
  1452. self.navBar = [[[UINavigationBar alloc]initWithFrame:CGRectMake(0, 0, 320, 44)]autorelease];
  1453. _navBar.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin;
  1454. [self.view addSubview:_theWebView];
  1455. [self.view addSubview:_navBar];
  1456. self.blockerView = [[[UIView alloc]initWithFrame:CGRectMake(0, 0, 200, 60)]autorelease];
  1457. _blockerView.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.8];
  1458. _blockerView.center = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2);
  1459. _blockerView.clipsToBounds = YES;
  1460. _blockerView.layer.cornerRadius = 10;
  1461. self.pinCopyBar = [[[UIToolbar alloc]initWithFrame:CGRectMake(0, 44, self.view.bounds.size.width, 44)]autorelease];
  1462. _pinCopyBar.barStyle = UIBarStyleBlackTranslucent;
  1463. _pinCopyBar.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin;
  1464. _pinCopyBar.items = [NSArray arrayWithObjects:[[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]autorelease], [[[UIBarButtonItem alloc]initWithTitle:@"Select and Copy the PIN" style: UIBarButtonItemStylePlain target:nil action: nil]autorelease], [[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]autorelease], nil];
  1465. UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(0, 5, _blockerView.bounds.size.width, 15)];
  1466. label.text = @"Please Wait...";
  1467. label.backgroundColor = [UIColor clearColor];
  1468. label.textColor = [UIColor whiteColor];
  1469. label.textAlignment = UITextAlignmentCenter;
  1470. label.font = [UIFont boldSystemFontOfSize:15];
  1471. [_blockerView addSubview:label];
  1472. [label release];
  1473. UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
  1474. spinner.center = CGPointMake(_blockerView.bounds.size.width/2, (_blockerView.bounds.size.height/2)+10);
  1475. [_blockerView addSubview:spinner];
  1476. [self.view addSubview:_blockerView];
  1477. [spinner startAnimating];
  1478. [spinner release];
  1479. UINavigationItem *navItem = [[[UINavigationItem alloc]initWithTitle:@"Twitter Login"]autorelease];
  1480. navItem.leftBarButtonItem = [[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(close)]autorelease];
  1481. [_navBar pushNavigationItem:navItem animated:NO];
  1482. [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
  1483. dispatch_async(GCDBackgroundThread, ^{
  1484. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
  1485. NSString *reqString = [[FHSTwitterEngine sharedEngine]getRequestTokenString];
  1486. if (reqString.length == 0) {
  1487. [self dismissModalViewControllerAnimated:YES];
  1488. [pool release];
  1489. return;
  1490. }
  1491. self.requestToken = [OAToken tokenWithHTTPResponseBody:reqString];
  1492. NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"https://api.twitter.com/oauth/authorize?oauth_token=%@",_requestToken.key]]];
  1493. dispatch_sync(GCDMainThread, ^{
  1494. NSAutoreleasePool *poolTwo = [[NSAutoreleasePool alloc]init];
  1495. [_theWebView loadRequest:request];
  1496. [poolTwo release];
  1497. });
  1498. [pool release];
  1499. });
  1500. }
  1501. - (void)gotPin:(NSString *)pin {
  1502. _requestToken.verifier = pin;
  1503. BOOL ret = [[FHSTwitterEngine sharedEngine]finishAuthWithRequestToken:_requestToken];
  1504. void(^block)(BOOL success) = objc_getAssociatedObject(authBlockKey, "FHSTwitterEngineOAuthCompletion");
  1505. objc_removeAssociatedObjects(authBlockKey);
  1506. if (block) {
  1507. block(ret);
  1508. }
  1509. [self dismissModalViewControllerAnimated:YES];
  1510. }
  1511. - (void)pasteboardChanged:(NSNotification *)note {
  1512. if (![note.userInfo objectForKey:UIPasteboardChangedTypesAddedKey]) {
  1513. return;
  1514. }
  1515. NSString *string = [[UIPasteboard generalPasteboard]string];
  1516. if (string.length != 7 || !string.fhs_isNumeric) {
  1517. return;
  1518. }
  1519. [self gotPin:string];
  1520. }
  1521. - (NSString *)locatePin {
  1522. NSString *pin = [[_theWebView stringByEvaluatingJavaScriptFromString:newPinJS]stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
  1523. if (pin.length == 7) {
  1524. return pin;
  1525. } else {
  1526. pin = [[_theWebView stringByEvaluatingJavaScriptFromString:oldPinJS]stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
  1527. if (pin.length == 7) {
  1528. return pin;
  1529. }
  1530. }
  1531. return nil;
  1532. }
  1533. - (void)webViewDidFinishLoad:(UIWebView *)webView {
  1534. _theWebView.userInteractionEnabled = YES;
  1535. NSString *authPin = [self locatePin];
  1536. if (authPin.length > 0) {
  1537. [self gotPin:authPin];
  1538. return;
  1539. }
  1540. NSString *formCount = [webView stringByEvaluatingJavaScriptFromString:@"document.forms.length"];
  1541. if ([formCount isEqualToString:@"0"]) {
  1542. [self showPinCopyPrompt];
  1543. }
  1544. [UIView beginAnimations:nil context:nil];
  1545. _blockerView.hidden = YES;
  1546. [UIView commitAnimations];
  1547. [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
  1548. _theWebView.hidden = NO;
  1549. }
  1550. - (void)showPinCopyPrompt {
  1551. if (_pinCopyBar.superview) {
  1552. return;
  1553. }
  1554. _pinCopyBar.center = CGPointMake(_pinCopyBar.bounds.size.width/2, _pinCopyBar.bounds.size.height/2);
  1555. [self.view insertSubview:_pinCopyBar belowSubview:_navBar];
  1556. [UIView beginAnimations:nil context:nil];
  1557. _pinCopyBar.center = CGPointMake(_pinCopyBar.bounds.size.width/2, _navBar.bounds.size.height+_pinCopyBar.bounds.size.height/2);
  1558. [UIView commitAnimations];
  1559. }
  1560. - (void)webViewDidStartLoad:(UIWebView *)webView {
  1561. _theWebView.userInteractionEnabled = NO;
  1562. [_theWebView setHidden:YES];
  1563. [_blockerView setHidden:NO];
  1564. [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
  1565. }
  1566. - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
  1567. if (strstr([request.URL.absoluteString UTF8String], "denied=")) {
  1568. [self dismissModalViewControllerAnimated:YES];
  1569. return NO;
  1570. }
  1571. NSData *data = request.HTTPBody;
  1572. char *raw = data?(char *)[data bytes]:"";
  1573. if (raw && (strstr(raw, "cancel=") || strstr(raw, "deny="))) {
  1574. [self dismissModalViewControllerAnimated:YES];
  1575. return NO;
  1576. }
  1577. return YES;
  1578. }
  1579. - (void)close {
  1580. [self dismissModalViewControllerAnimated:YES];
  1581. }
  1582. - (void)dismissModalViewControllerAnimated:(BOOL)animated {
  1583. [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
  1584. [[NSNotificationCenter defaultCenter]removeObserver:self];
  1585. [_theWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@""]]];
  1586. [super dismissModalViewControllerAnimated:animated];
  1587. }
  1588. - (void)dealloc {
  1589. [self setNavBar:nil];
  1590. [self setBlockerView:nil];
  1591. [self setPinCopyBar:nil];
  1592. [self setTheWebView:nil];
  1593. [self setRequestToken:nil];
  1594. [super dealloc];
  1595. }
  1596. @end
  1597. static char const encodingTable[64] = {
  1598. 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
  1599. 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
  1600. 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
  1601. 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' };
  1602. @implementation NSData (Base64)
  1603. + (NSData *)dataWithBase64EncodedString:(NSString *)string {
  1604. return [[[NSData alloc]initWithBase64EncodedString:string]autorelease];
  1605. }
  1606. - (id)initWithBase64EncodedString:(NSString *)string {
  1607. NSMutableData *mutableData = nil;
  1608. if (string) {
  1609. unsigned long ixtext = 0;
  1610. unsigned char ch = 0;
  1611. unsigned char inbuf[4] = {0,0,0,0};
  1612. unsigned char outbuf[3] = {0,0,0};
  1613. short ixinbuf = 0;
  1614. BOOL flignore = NO;
  1615. BOOL flendtext = NO;
  1616. NSData *base64Data = [string dataUsingEncoding:NSASCIIStringEncoding];
  1617. const unsigned char *base64Bytes = [base64Data bytes];
  1618. mutableData = [NSMutableData dataWithCapacity:base64Data.length];
  1619. unsigned long lentext = [base64Data length];
  1620. while (!(ixtext >= lentext)) {
  1621. ch = base64Bytes[ixtext++];
  1622. flignore = NO;
  1623. if ((ch >= 'A') && (ch <= 'Z')) {
  1624. ch = ch - 'A';
  1625. } else if ((ch >= 'a') && (ch <= 'z')) {
  1626. ch = ch - 'a' + 26;
  1627. } else if ((ch >= '0') && (ch <= '9')) {
  1628. ch = ch - '0' + 52;
  1629. } else if (ch == '+') {
  1630. ch = 62;
  1631. } else if (ch == '=') {
  1632. flendtext = YES;
  1633. } else if (ch == '/') {
  1634. ch = 63;
  1635. } else {
  1636. flignore = YES;
  1637. }
  1638. if (!flignore) {
  1639. short ctcharsinbuf = 3;
  1640. BOOL flbreak = NO;
  1641. if (flendtext) {
  1642. if (!ixinbuf) {
  1643. break;
  1644. }
  1645. if (ixinbuf == 1 || ixinbuf == 2) {
  1646. ctcharsinbuf = 1;
  1647. } else {
  1648. ctcharsinbuf = 2;
  1649. }
  1650. ixinbuf = 3;
  1651. flbreak = YES;
  1652. }
  1653. inbuf[ixinbuf++] = ch;
  1654. if (ixinbuf == 4) {
  1655. ixinbuf = 0;
  1656. outbuf[0] = (inbuf[0] << 2) | ((inbuf[1] & 0x30) >> 4);
  1657. outbuf[1] = ((inbuf[1] & 0x0F) << 4) | ((inbuf[2] & 0x3C) >> 2);
  1658. outbuf[2] = ((inbuf[2] & 0x03) << 6) | (inbuf[3] & 0x3F);
  1659. for (int i = 0; i < ctcharsinbuf; i++) {
  1660. [mutableData appendBytes:&outbuf[i] length:1];
  1661. }
  1662. }
  1663. if (flbreak) {
  1664. break;
  1665. }
  1666. }
  1667. }
  1668. }
  1669. self = [self initWithData:mutableData];
  1670. return self;
  1671. }
  1672. - (NSString *)base64EncodingWithLineLength:(unsigned int)lineLength {
  1673. const unsigned char *bytes = [self bytes];
  1674. unsigned long ixtext = 0;
  1675. unsigned long lentext = [self length];
  1676. long ctremaining = 0;
  1677. unsigned char inbuf[3] = {0,0,0};
  1678. unsigned char outbuf[4] = {0,0,0,0};
  1679. short charsonline = 0;
  1680. short ctcopy = 0;
  1681. unsigned long ix = 0;
  1682. NSMutableString *result = [NSMutableString stringWithCapacity:lentext];
  1683. while (YES) {
  1684. ctremaining = lentext-ixtext;
  1685. if (ctremaining <= 0) {
  1686. break;
  1687. }
  1688. for (int i = 0; i < 3; i++) {
  1689. ix = ixtext + i;
  1690. inbuf[i] = (ix < lentext)?bytes[ix]:0;
  1691. }
  1692. outbuf[0] = (inbuf[0] & 0xFC) >> 2;
  1693. outbuf[1] = ((inbuf[0] & 0x03) << 4) | ((inbuf[1] & 0xF0) >> 4);
  1694. outbuf[2] = ((inbuf[1] & 0x0F) << 2) | ((inbuf[2] & 0xC0) >> 6);
  1695. outbuf[3] = inbuf[2] & 0x3F;
  1696. switch (ctremaining) {
  1697. case 1:
  1698. ctcopy = 2;
  1699. break;
  1700. case 2:
  1701. ctcopy = 3;
  1702. break;
  1703. default:
  1704. ctcopy = 4;
  1705. break;
  1706. }
  1707. for (int i = 0; i < ctcopy; i++) {
  1708. [result appendFormat:@"%c",encodingTable[outbuf[i]]];
  1709. }
  1710. for (int i = ctcopy; i < 4; i++) {
  1711. [result appendString:@"="];
  1712. }
  1713. ixtext += 3;
  1714. charsonline += 4;
  1715. if (lineLength > 0) {
  1716. if (charsonline >= lineLength) {
  1717. charsonline = 0;
  1718. [result appendString:@"\n"];
  1719. }
  1720. }
  1721. }
  1722. return result;
  1723. }
  1724. @end