I am trying to fetch tweets from twitter using an NSURLConnection, and I keep hitting the maximum number of requests per hour (150). I switched to using the authenticated way of fetching tweets, like below, but still hit the maximum API calls - 150. How am I able to make more than 150 twitter requests per device?
ACAccountStore *store = [[ACAccountStore alloc] init];
ACAccountType *twitterAccountType =
[store accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
// Request permission from the user to access the available Twitter accounts
[store requestAccessToAccountsWithType:twitterAccountType
withCompletionHandler:^(BOOL granted, NSError *error) {
if (!granted) {
// The user rejected your request
NSLog(#"User rejected access to the account.");
}
else {
// Grab the available accounts
NSArray *twitterAccounts =
[store accountsWithAccountType:twitterAccountType];
if ([twitterAccounts count] > 0) {
// Use the first account for simplicity
ACAccount *account = [twitterAccounts objectAtIndex:0];
// Now make an authenticated request to our endpoint
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
[params setObject:#"1" forKey:#"include_entities"];
// The endpoint that we wish to call
NSURL *url =
[NSURL
URLWithString:#"https://api.twitter.com/1/statuses/user_timeline.json?include_entities=true&include_rts=true&screen_name=%#&count=5",someTwitterUserName];
// Build the request with our parameter
TWRequest *request =
[[TWRequest alloc] initWithURL:url
parameters:params
requestMethod:TWRequestMethodGET];
// Attach the account object to this request
[request setAccount:account];
[request performRequestWithHandler:
^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
if (!responseData) {
// inspect the contents of error
NSLog(#"%#", error);
}
else {
NSError *jsonError;
NSArray *timeline =
[NSJSONSerialization
JSONObjectWithData:responseData
options:NSJSONReadingMutableLeaves
error:&jsonError];
if (timeline) {
// at this point, we have an object that we can parse and grab tweet information from
NSLog(#"%#", timeline);
}
else {
// inspect the contents of jsonError
NSLog(#"%#", jsonError);
}
}
}];
} // if ([twitterAccounts count] > 0)
} // if (granted)
}];
Try see at this post
solution
Problem in your twitter app, who have a limit at default
Related
I believe that I have an incorrect flow for implementing Developer Authenticated Identities and I keep hearing and doing different things on the web. So I thought I'd present my entire flow and hear what the correct way of doing this was and present some questions and errors at the bottom.
Initially, I have a user login with a password and username (I'm using nsuserdefaults only temporarily, I will use KeyChain later).
Note: I also have a callback that goes all the way down to see if I properly authenticated a user.
Login Method:
-(void)loginBusyTimeUser:(void(^)(BOOL))callBack{
//initialize nsuserdefualts should be keychain later
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *post = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
[defaults objectForKey:#"username"], #"username",
[defaults objectForKey:#"password"], #"password",
nil];
NSError *error;
NSData *postData = [NSJSONSerialization dataWithJSONObject:post options:0 error:&error];
NSString *postLength = [NSString stringWithFormat:#"%lu", (unsigned long)[postData length]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:#"SOMELOGINURL"]];
[request setHTTPMethod:#"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setHTTPBody:postData];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
[[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *newJSON = [NSJSONSerialization JSONObjectWithData:data
options:0
error:&error];
if(!newJSON || [newJSON objectForKey:#"errorMessage"]){
NSLog(#"%#",newJSON);
callBack(false);
NSLog(#"DID NOT AUTHENTICATE");
}else{
NSLog(#"%#",newJSON);
[defaults setValue:[newJSON objectForKey:#"Token"] forKey:#"Token"];
[defaults setValue:[newJSON objectForKey:#"IdentityId"] forKey:#"IdentityId"];
[self authenticateUser:^(BOOL call){
callBack(call);
}];
}
}] resume];
}
If Everything is successful, I then authenticate my user with AWS Cognito with the developer authenticated flow:
-(void)authenticateUser:(void(^)(BOOL))callBack{
//Now after making sure that your user's credentials are sound, then initialize the IdentityProvider, in this case
//BusytimeAuthenticated
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
id<AWSCognitoIdentityProvider> identityProvider = [[BusytimeAuthenticated alloc] initWithRegionType:AWSRegionUSEast1
identityId:nil
identityPoolId:#"SOMEPOOLID"
logins:#{#"cognito-identity.amazonaws.com": [defaults objectForKey:#"Token"]}
providerName:#"cognito-identity.amazonaws.com"
];
credentialsProvider = [[AWSCognitoCredentialsProvider alloc] initWithRegionType:AWSRegionUSEast1
identityProvider:identityProvider
unauthRoleArn:nil
authRoleArn:nil];
configuration = [[AWSServiceConfiguration alloc] initWithRegion:AWSRegionUSEast1
credentialsProvider:self.credentialsProvider];
AWSServiceManager.defaultServiceManager.defaultServiceConfiguration = configuration;
[[credentialsProvider refresh] continueWithBlock:^id(AWSTask *task){
if([task isFaulted]){
callBack(false);
}else{
callBack(true);
}
return [defaults objectForKey:#"Token"];
}];
}
Then the refresh method causes some errors so I'll show my "BusytimeAuthenticated" class (.m)
//
// BusytimeAuthenticated.m
// BusyTime
//
// Created by akash kakumani on 10/14/15.
// Copyright (c) 2015 BusyTime. All rights reserved.
//
#import "BusytimeAuthenticated.h"
#interface BusytimeAuthenticated()
#property (strong, atomic) NSString *providerName;
#property (strong, atomic) NSString *token;
#end
#implementation BusytimeAuthenticated
#synthesize providerName=_providerName;
#synthesize token=_token;
- (instancetype)initWithRegionType:(AWSRegionType)regionType
identityId:(NSString *)identityId
identityPoolId:(NSString *)identityPoolId
logins:(NSDictionary *)logins
providerName:(NSString *)providerName{
if (self = [super initWithRegionType:regionType identityId:identityId accountId:nil identityPoolId:identityPoolId logins:logins]) {
self.providerName = providerName;
}
return self;
}
// Return the developer provider name which you choose while setting up the
// identity pool in the Amazon Cognito Console
- (BOOL)authenticatedWithProvider {
return [self.logins objectForKey:self.providerName] != nil;
}
// If the app has a valid identityId return it, otherwise get a valid
// identityId from your backend.
- (AWSTask *)getIdentityId {
// already cached the identity id, return it
if (self.identityId) {
return [AWSTask taskWithResult:nil];
}
// not authenticated with our developer provider
else if (![self authenticatedWithProvider]) {
return [super getIdentityId];
}
// authenticated with our developer provider, use refresh logic to get id/token pair
else {
return [[AWSTask taskWithResult:nil] continueWithBlock:^id(AWSTask *task) {
if (!self.identityId) {
return [self refresh];
}
return [AWSTask taskWithResult:self.identityId];
}];
}
}
// Use the refresh method to communicate with your backend to get an
// identityId and token.
- (AWSTask *)refresh {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if (![self authenticatedWithProvider]) {
return [super getIdentityId];
}else{
NSDictionary *post = [[NSDictionary alloc] initWithObjectsAndKeys:
[defaults objectForKey:#"username"], #"username",
[defaults objectForKey:#"password"], #"password",
nil];
NSError *error;
NSData *postData = [NSJSONSerialization dataWithJSONObject:post options:0 error:&error];
NSString *postLength = [NSString stringWithFormat:#"%lu", (unsigned long)[postData length]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:#"SOMELOGINURL"]];
[request setHTTPMethod:#"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setHTTPBody:postData];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
[[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *newJSON = [NSJSONSerialization JSONObjectWithData:data
options:0
error:&error];
if(!newJSON){
NSLog(#"Failure in refresh: %#",newJSON);
}else{
NSLog(#"The IdentityID in the refresh method: %#",[newJSON objectForKey:#"IdentityId" ]);
NSLog(#"The token in the refresh method: %#",[newJSON objectForKey:#"Token" ]);
self.identityId = [newJSON objectForKey:#"IdentityId" ];
self.token = [newJSON objectForKey:#"Token" ];
}
}] resume];
return [AWSTask taskWithResult:self.identityId];
}
return [AWSTask taskWithResult:self.identityId];
}
#end
Some Questions I had:
Is the DeveloperAuthenticationClient necessary to solve my problems? I saw the sample app using them but I found them too confusing.
Should I be using my ProviderName or should I be using "cognito-identity.amazonaws.com"
I sometimes face a timeout error and found out that it could be the fact that my implementation of login (using API Gateway and a lambda method) could have some cold-start issues. The way I solved this is by increasing the timeout time to 20 seconds. Is this the correct way to solve this?
I saw that in the sample app they use GetToken and Login as two separate things. I thought it would be easier if my login could also serve as my GetToken. Is this appropriate?
Finally, please address any problems that you see with my code if time permits.
Error:
[Error] AWSCredentialsProvider.m line:527 |
__40-[AWSCognitoCredentialsProvider refresh]_block_invoke352 | Unable to refresh. Error is [Error
Domain=com.amazonaws.AWSCognitoCredentialsProviderErrorDomain Code=1
"identityId shouldn't be nil"
UserInfo={NSLocalizedDescription=identityId shouldn't be nil}]
(I also found out the error above is related to the fact that self.identityId didn't get set because the request was in a block and other parts executed first and the solution is:
- (AWSTask *)refresh {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if (![self authenticatedWithProvider]) {
return [super getIdentityId];
}else{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *string = [defaults objectForKey:#"IdentityId"];
self.identityId = string;
return [AWSTask taskWithResult:[defaults objectForKey:#"IdentityId"]];
}
NSString *string = [defaults objectForKey:#"IdentityId"];
return [AWSTask taskWithResult:[defaults objectForKey:#"IdentityId"]];
}
But I believe that this isn't the correct implementation. )
I believe my code was working at a certain point but stopped working after I upgraded to the new SDK. However, it might just be the fact that I hadn't noticed the error initially.
Answers to your questions:
Yes you need to have some entity(client) which communicates with your backend system.
You are using cognito-identity.amazonaws.com in the logins map, but using the IdentityProvider pattern for refresh. This is why the first authentication succeeds but attempts to refresh fail. The logic in your refresh will never fire. Please look at our end to end sample on how to implement developer authenticated identities.
From my limited knowledge yes this is one way, but you may face performance issues. Please contact AWS Lambda via their forums for more guidance on this.
We highly recommend following the flow in the sample. The getToken doesn't need your authentication credentials if you have established trust, whereas login will always need the authentication credentials, so better not to mix these.
Thanks..
I am trying to pull data from the Twitter Streaming API using the SLRequest class.
When I use the endpoint and parameters documented in the code below the program "hangs"
and no JSON data is printed. I am using an endpoint based on an example from the twitter dev
website https://dev.twitter.com/docs/streaming-apis/parameters#with I am requesting
tweets at a certain location.
When I use this code to query my timeline using the REST API (the code and request is included but
commented out) the program does not hang and I get a valid response.
Is there something else in the code that I need to implement to access the data using the streaming API? What additional modifications or changes need to be made?
ACAccountStore * accountStore = [[ACAccountStore alloc] init];
ACAccountType * twitterAccountType =
[accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
// Ask the user permission to access his account
[accountStore requestAccessToAccountsWithType:twitterAccountType options:nil completion:^(BOOL granted, NSError *error) {
if (granted == NO) {
NSLog(#"-- error: %#", [error localizedDescription]);
}
if (granted == YES){
/***************** Create request using REST API*********************
***************** This URL is functional and returns valid data *****
NSURL * url = [NSURL URLWithString:#"https://userstream.twitter.com/1.1/user.json"];
SLRequest * request = [SLRequest requestForServiceType:SLServiceTypeTwitter requestMethod:SLRequestMethodGET URL:url parameters:#{#"screen_name": #"your_twitter_id"}];
***************************************************************/
// Create request using Streaming API Endpoint
NSURL * url = [NSURL URLWithString:#"https://stream.twitter.com/1.1/statuses/filter.json"];
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
[params setObject:#"track" forKey:#"twitter&"];
[params setObject:#"locations" forKey:#"-122.75,36.8,-121.75,37.8"];
SLRequest * request = [SLRequest requestForServiceType:SLServiceTypeTwitter requestMethod:SLRequestMethodPOST URL:url parameters:params];
NSArray * twitterAccounts = [accountStore accountsWithAccountType:twitterAccountType];
if ([twitterAccounts count] == 0) {
(NSLog(#"-- no accounts available"));
} else if ([twitterAccounts count] >0){
[request setAccount:[twitterAccounts lastObject]];
NSLog([request.account description]);
NSLog(#"Twitter handler of user is %#", request.account.username);
// Execute the request
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
NSError * jsonError = nil;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:&jsonError];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// NSLog(#"-- json Data is %#", json);
NSLog([json description]);
}];
}];
}
}
}];
SLRequest doesn't play well with the streaming API.
Here is how to do with STTwitter:
self.twitter = [STTwitterAPI twitterAPIOSWithAccount:account];
[_twitter verifyCredentialsWithSuccessBlock:^(NSString *username) {
NSLog(#"-- access granted for %#", username);
[_twitter postStatusesFilterUserIDs:nil
keywordsToTrack:#[#"twitter"]
locationBoundingBoxes:#[#"-122.75,36.8,-121.75,37.8"]
delimited:nil
stallWarnings:nil
progressBlock:^(id response) {
NSLog(#"-- %#", response);
} stallWarningBlock:^(NSString *code, NSString *message, NSUInteger percentFull) {
NSLog(#"-- stall warning");
} errorBlock:^(NSError *error) {
NSLog(#"-- %#", [error localizedDescription]);
}];
} errorBlock:^(NSError *error) {
NSLog(#"-- %#", [error localizedDescription]);
}];
Internally, STTwitter builds a NSURLConnection instance with the request from -[SLRequest preparedURLRequest]. You can replicate this trick in your code if you wish.
I'm writing a Facebook Parser for iOS6. Yesterday, I made a test project with only the parser. This worked fine. When I want to implement the parser in my bigger project (with exactly the same method code), a "Parse Error: Expected ]" is given. The imports for Social and Accounts are set.
This is the line that gives the error:
SLRequest *retrieveWall = [SLRequest requestForServiceType:SLServiceTypeFacebook requestMethod:SLRequestMethodGET URL:newsFeed parameters:nil];
This is the full method:
-(void)fetchFacebookFrom:(NSString*)userID withAppKey:(NSString*)appKey
{
ACAccountStore *accountStore = [[ACAccountStore alloc] init];
ACAccountType *accountType = [accountStore
accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierFacebook];
NSDictionary *options = #{
#"ACFacebookAppIdKey" : appKey,
#"ACFacebookPermissionsKey" : #[#"email"],
#"ACFacebookAudienceKey" : ACFacebookAudienceEveryone
};
[accountStore requestAccessToAccountsWithType:accountType
options:options completion:^(BOOL granted, NSError *error) {
if(granted)
{
NSArray *arrayOfAccounts = [accountStore
accountsWithAccountType:accountType];
if ([arrayOfAccounts count] > 0)
{
ACAccount *facebookAccount = [arrayOfAccounts lastObject];
NSURL *newsFeed = [NSURL URLWithString:#"https://graph.facebook.com/12345678901/feed"];
SLRequest *retrieveWall = [SLRequest requestForServiceType:SLServiceTypeFacebook requestMethod:SLRequestMethodGET URL:newsFeed parameters:nil];
retrieveWall.account = facebookAccount;
[retrieveWall performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error)
{
if (error)
{
NSLog(#"Error: %#", [error localizedDescription]);
}
else
{
// The output of the request is placed in the log.
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:nil];
NSArray *statuses = [jsonResponse objectForKey:#"data"];
}
}];
}
}
else
{
NSLog(#"Not granted");
}
}];
}
The error specificly points to "URL" in the method call..
This stupid error is driving me crazy for 3 hours already. I restarted XCode, cleaned the project and rebooted my Mac. Who sees the error?
EDIT: It seems that this particular code is not the problem. I added core data too, which has some functions with URL in method names.
Every function with URL in the method name now gives a Parse Error and points to "URL". Getting closer to the problem, but still not there!
We defined URL somewhere in a header file. Removing this fixed it. Thanks for thinking about a solution!
How would I loop through the JSON returned by a TWRequest to get the geo information of a tweet? I am using the code below - I have marked up the bit I am unsure about. the text component works fine, I'm just not sure how to create the array of geo data and access this...
- (void)fetchTweets
{
AppDelegate *delegate = (AppDelegate*)[[UIApplication sharedApplication]delegate];
//NSLog(#"phrase carried over is %#", delegate.a);
// Do a simple search, using the Twitter API
TWRequest *request = [[TWRequest alloc] initWithURL:[NSURL URLWithString:
[NSString stringWithFormat:#"http://search.twitter.com/search.json?q=%#", delegate.a]]
parameters:nil requestMethod:TWRequestMethodGET];
// Notice this is a block, it is the handler to process the response
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error)
{
if ([urlResponse statusCode] == 200)
{
// The response from Twitter is in JSON format
// Move the response into a dictionary and print
NSError *error;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
//NSLog(#"Twitter response: %#", dict);
NSArray *results = [dict objectForKey:#"results"];
//Loop through the results
for (NSDictionary *tweet in results) {
// Get the tweet
NSString *twittext = [tweet objectForKey:#"text"];
//added this one - need to check id NSString is ok??
NSString *twitlocation = [tweet objectForKey:#"geo"];
// Save the tweet to the twitterText array
[_twitterText addObject:twittext];
//this is the loop for the location
[twitterLocation addObject:twitlocation];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
else
NSLog(#"Twitter error, HTTP response: %i", [urlResponse statusCode]);
}];
}
"geo" is deprecated and probably not filled at all. I far as I remember it was deprecated in Twitter API v1.0 too. Try this code:
- (void)fetchTweets
{
AppDelegate *delegate = (AppDelegate*)[[UIApplication sharedApplication]delegate];
//NSLog(#"phrase carried over is %#", delegate.a);
// Do a simple search, using the Twitter API
TWRequest *request = [[TWRequest alloc] initWithURL:[NSURL URLWithString:
[NSString stringWithFormat:#"http://search.twitter.com/search.json?q=%#", delegate.a]]
parameters:nil requestMethod:TWRequestMethodGET];
// Notice this is a block, it is the handler to process the response
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error)
{
if ([urlResponse statusCode] == 200)
{
// The response from Twitter is in JSON format
// Move the response into a dictionary and print
NSError *error;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
//NSLog(#"Twitter response: %#", dict);
NSArray *results = [dict objectForKey:#"results"];
//Loop through the results
for (NSDictionary *tweet in results) {
// Get the tweet
NSString *twittext = [tweet objectForKey:#"text"];
//added this one - need to check id NSString is ok??
id jsonResult = [tweet valueForKeyPath:#"coordinates.coordinates"];
if ([NSNull null] != jsonResult) {
if (2 == [jsonResult count]) {
NSDecimalNumber* longitude = [jsonResult objectAtIndex:0];
NSDecimalNumber* latitude = [jsonResult objectAtIndex:1];
if (longitude && latitude) {
// here you have your coordinates do whatever you like
[twitterLocation addObject:[NSString stringWithFormat:#"%#,%#", latitude, longitude]];
}
else {
NSLog(#"Warning: bad coordinates: %#", jsonResult);
}
}
else {
NSLog(#"Warning: bad coordinates: %#", jsonResult);
}
}
// Save the tweet to the twitterText array
[_twitterText addObject:twittext];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
else
NSLog(#"Twitter error, HTTP response: %i", [urlResponse statusCode]);
}];
}
I was using some of Apple's example code to write the Twitter integration for my app.
However, I get a whopping amount of errors (mostly being Semantic and parse errors).
How can this be solved?
-(IBAction)TWButton:(id)sender {
ACAccountStore *accountstore = [[ACAccountStore alloc] init];
//Make sure to retrive twitter accounts
ACAccountType *accountType = [accountstore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
[accountstore requestAccessToAccountsWithType:accountType withCompletionHandler:^(BOOL granted, NSError *error) {
if (granted) [{
NSArray *accountsArray = [accountstore accountsWithAccountType:accountType];
if ([accountsArray count] > 0) {
ACAccount *twitterAccount = [accountsArray objectAtIndex:0];
TWRequest *postRequest = [[TWRequest alloc] initWithURL:[NSURL URLWithString:#"http://api.twitter.com/1/statuses/update.json"] parameters:[NSDictionary dictionaryWithObject:[#"Tweeted from iBrowser" forKey:#"status"] requestMethod:TWRequestMethodPOST];
[postRequest setAccount:twitterAccount];
[postRequest preformRequestWithHandeler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
NSString *output = [NSString stringWithFormat:#"HTTP response status: %i", [urlResponse statusCode]];
[self preformSelectorOnMainThread:#selector(displaytext:) withObject:output waitUntilDone:NO];
}];
}
}];
}
//Now lets see if we can actually tweet
-(void)canTweetStatus {
if ([TWTweetComposeViewController canSendTweet]) {
self.TWButton.enabled = YES
self.TWButton.alpha = 1.0f;
}else{
self.TWButton.enabled = NO
self.TWButton.alpha = 0.5f;
}
}
The first error I see is easy to get rid of.
Objective C convention is to make the first letters of each method name lower case.
Use makeKeyAndVisible in your AppDelegate.m
The other errors we'd probably need to see where (in your code) the errors are being thrown, not just what kind of errors.