So, a few weeks ago Twitter replaced Basic Auth for OAuth in their APIs. I was aware of this of course, but it never occurred to me to worry about it as I don't actually work on a Twitter client myself. However, I forgot that one of my sites, CFBloggers.org, makes use of the Twitter API to tweet new blog entries it aggregates. When I tweeted about this today I got a lot of recommendations, but for the most part, the advice, and the docs, focused on applications for humans. By that I mean, the assumption was that your Twitter automation was a tool that random users were using. The docs would explain how you could easily direct them to Twitter to authorize your application and how they would be sent back. That's great, but what about the case for Twitter bots? Nothing out there seemed to address that need. Luckily though I got some great help. In this blog post I'll explain exactly how I updated CFBloggers to post to Twitter. While this is a ColdFusion specific post it really could apply to anyone doing Twitter bots. Credit for this goes to Todd Rafferty, Vic Carter, and Rob O'Brien (and specifically his blog post here: Integrating Twitter and OAuth with ColdFusion)
Ok, so before I get into the exact steps, let me summarize what we are going to do here. We are going to switch to a Java based Twitter library that makes use of OAuth-wrapped calls to Twitter. That handles the sending of tweets. To handle "allow my web site to do this" we are going to use a temporary script. The temporary script is a one time process that we will use to get our tokens that are then fed to the Twitter client. Once we have that we should be gold.
Edited September 16: Reader Angela Haralson pointed out (see comments below) a great time saver. The creation of the temporary script to get AccessToken and Secret is not necessary. You can get those values from the Twitter web site. This makes things even easier! Please keep that in mind when reading below. Basically you can focus more on the Java library and your setup at Twitter.com.
- The first step is to get the Java library. At this time there are no ColdFusion Twitter clients that make use of OAuth, but if anyone knows of one, or creates one after this entry is released, please post it below. The Java library is called Twitter4J and may be found here: http://twitter4j.org/en/index.html
- You can put the Twitter4J jar in your ColdFusion class path, or do it the sexy way and make use of JavaLoader. I made use of JavaLoader. This is what I added to my Application.cfc:
<cfset var paths = [expandPath("./components/twitter4j-core-2.1.4-SNAPSHOT.jar")]>
<cfset application.javaloader = createObject("component", "components.javaloader.JavaLoader").init(paths)> <cfset application.Twitter = application.javaloader.create("twitter4j.Twitter")>
- This is the beginning of the one time process! We need to create an application on the Twitter web site. This is the application that represents our web site robot. Go to http://dev.twitter.com and login. You can login as your primary Twitter account or the robot's account. In the top nav click "Your Apps" and select Register a New app.
- The application name, description, and web site are not important. However, the application name is what folks will see when you robot tweets. I picked "CFBloggersRobot" for mine. For the application web site I just used CFBloggers.org. For the organization I said Me. It's a great organization but the benefits suck. Now for two critical parts. Application Type must be Browser. The call back URL is going to be a temporary script we will make in the next step. Notice that the call back URL can be a local url, by that I mean I used dev.coldfusionbloggers.org, which is only recognized by my local machine. I used this url: http://dev.coldfusionbloggers.org/ray.cfm?mode=1. The mode=1 is also critical and will make sense one you see the script. Finally, ensure you set access type to Read and Write. Otherwise you will not be able to send tweets.
- After you save the application you will go to a settings page. Notice that there are two values here you will need, the consumer key and the consumer secret. This will be used in step 6.
- Ok, now we are going to create the temporary script:
<cfif structKeyExists(url,'mode') IS FALSE> <!--- // 2. Authorize --->
<cfset RequestToken = Twitter.getOAuthRequestToken()>
<cfset Session.oAuthRequestToken = RequestToken.getToken()>
<cfset Session.oAuthRequestTokenSecret = RequestToken.getTokenSecret()>
<cflocation url="#RequestToken.getAuthorizationURL()#" addtoken="No"> <cfelse> <!--- // 3. Authenticate // --->
<cfset AccessToken = Twitter.getOAuthAccessToken(Session.oAuthRequestToken,Session.oAuthRequestTokenSecret)>
<cfset session.StoredAccessToken = AccessToken.getToken()>
<cfset session.StoredAccessSecret = AccessToken.getTokenSecret()>
<cfdump var="#session#"><cfabort> </cfif>
<cfset Twitter = application.javaloader.create("twitter4j.Twitter")>
<cfset Twitter.setOAuthConsumer('cosumer key','consumer secret')>
So the script begins by getting an instance of the Twitter Java library. I have this in Application scope already but as this is a one time script I wanted to keep it simple. Notice the two strings. Replace those with the real value. Now open this baby up in your browser - and to be clear, you can do this all locally just fine.
When you run this you will get sent to the Twitter authorization page. Obviously you want to allow your application. Twitter will then send you right back to the script with mode=1 in the URL. This will trigger the dump you see. Within that dump you want to grab the values for storedaccesstoken and storedaccesssecret.
- Return back to your application.cfc. You need to provide all 4 values to your Twitter object:
<cfset application.Twitter = application.javaloader.create("twitter4j.Twitter")>
<cfset application.Twitter.setOAuthConsumer('consumerkey',consumersecret')>
<cfset application.Twitter.setOAuthAccessToken("storedaccesstoken" ,"storedaccesssecret")>
<cfset var paths = [expandPath("./components/twitter4j-core-2.1.4-SNAPSHOT.jar")]>
<cfset application.javaloader = createObject("component", "components.javaloader.JavaLoader").init(paths)>
- The final step is to update your code from the old way of sending Tweets to the new way. Luckily this is very trivial. I changed:
<cfset twit_result = application.twitter.statuses_update(message)>
to
<cfset application.twitter.updateStatus(message)>
And that's it. Once things were explained to me the actual coding took approximately 5 minutes.
Archived Comments
Could this be applied to FacebookConnect?
No idea. It's been a long time since I played with the Facebook API.
re: facebook connect. Not really. And facebook connect is being depreciated in favor of "Graph"
Look at this guy's blog: blog.abusalah.info
And this open source project: facebookgraph.riaforge.org
Thanks for this great tutorial. It's somewhat complex to get set up, but once you do it, it's actually very simple.
My one question is that it appears that you can send a location with your status updates by simply adding a location parameter.
<cfset Twitter.updateStatus(statusUpdate, location)>
However, I'm not sure how to create a GeoLocation from ColdFusion. Haven't worked at all with Java API's, but it appears that GeoLocation is part of the twitter4j package...just don't know how to get to it.
Ray, I've been struggling with this for a few days and really appreciate you posting your solution. I had looked at twitter4j, but rejected it in favor of working with the OAuth project on RIAForge (http://oauth.riaforge.org/). When I saw your blog post I looked at twitter4j again, this time using javaloader and was able to solve my problem. I may still continue looking at OAuth, but my production app is back up and running. Thanks again! -Ed
I keep getting this error
401:Authentication credentials were missing or incorrect. {"request":"/1/statuses/update.json","error":"Invalid / expired Token"} null
How often do you need to validate the Token?
It was my understanding that it lasted forever. I've yet to see an app that used oauth ask me to do it again. Look at my app.cfc code - are you setting BOTH the OAuthConsumer and Access stuff?
Ray,
I have the same info that you have on the app.cfc
When I call the Twitter.test() it does return OK... but any other function like getScreenName(), or updateStatus()... I get that error.
I do have my storedaccesstoken and storedaccesssecret saved on my DB... and if I try to authorize the application again I get the same token that I have on my DB.
Not sure what to recommend then. You may want to check the support options at twitter4j. I know it is still working fine for CFBloggers.
@Mike: You can't get geolocation from CF itself. The browser has to help. Google for geolocation and javascript for help there. As for doing geolocation of addresses - you can use a Google service for that and there is an open source CFC for it on RIAForge.
I figured out the Geolocation issue after doing a little more investigation on how to create Java objects with ColdFusion. I just needed to create the GeoLocation object then init it with the lat/long.
<cfset location = createObject("java", "twitter4j.GeoLocation").init(Lat, Lon)>
<cfset Twitter.updateStatus(statusUpdate, location)>
Since my particular app is all server based and gets the lat/long from a GPS, there is no need to use any lookups. I just needed to figure out how to send it to Twitter along with the update. Worked great yesterday, but today it doesn't. I think that Twitter has disabled location tweets today, likely for a technical issue, since I don't see anybody else with locations on them either.
You actually don't need the temporary script, you can get your access token by going to
http://dev.twitter.com/apps/
Click on your application. The page that comes up has your Consumer Key and Consumer Secret, but if you look at the nav options to the right, there should be an option "My Access Token", that page will give you your Access Token and Access Token Secret.
Here is a twitter link that describes how to use OAuth for single users:
http://dev.twitter.com/page...
Interesting. So if you are right, that means this entire entry is now probably worthless - which I'm ok with if it means we can skip a lot of work, and just make use of the Java library.
Is that right?
Well, you don't need the temporary script like I said, but all the other information was useful to me. I had previously looked at the Twitter4J library and the Rob O'Brien post, but it took reading through your blog post for me to put it all together and get it working!
Thanks also for the tip on using JavaLoader!
@Angela - and anyone - I updated the text above to make it clear we can skip stuff. I didn't want to wholesale edit the entry because I thought it would get a bit messy.
Is it clear?
Thank you again, Angela.
Thanks Ray and Angela.
For anyone else that stumbles across this - once you sign up at dev.twitter.com you'll need to grab the Consumer key and the consumer secret from the application details page then the oauth_token and oauth_token_secret from the my access tokens page.
just skip to step 7 once you get those.
<cfset application.Twitter.setOAuthConsumer('REPLACE_consumerkey','REPLACE_consumersecret')>
<cfset application.Twitter.setOAuthAccessToken("REPLACE_oauth_token" ,"REPLACE_oauth_token_secret")>
I wonder if we in the ColdFusion community (meaning you Ray), could do something like this:
http://projects.flowingdata...
Check my blog entry on OpenAmplify: http://www.coldfusionjedi.c...
It would let you to the textual analysis of the twitter text.
Just in case anyone else runs into this... if your server is quickly running out of memory and you have copied and pasted this code verbatim, change the application scoped variables to server. This being due to garbage collection and URLClassLoaders. See the link http://goo.gl/0eGnN or http://goo.gl/GWYKa .
Thanks for the warning Esmeralda!
Hi Ray, great tutorial but I'm having some problems running on MX7, any reasons for this that you may know of? I've found multiple examples but none of them work it's driving me mad :)
What kind of problems?
I've installed the JAR files, but I get the "Error Occurred While Processing Request" message for twitter4j.Twitter
It tells me
java.lang.InstantiationException: twitter4j.Twitter
Any ideas? Would greatly appreciate any help you can give me with this. It seems to work for everyone else which is why it's driving me so crazy.
You all are genuises. But for some of us, this is more complicated than it needs to be. What I need/want is for a user to authenticate via twitter, make a tweet from my app. So MY OWN token is not what I need, I need their token, and that's not going to be stored in an application var.
Here is my BUTT AS* simple App (old school style), registering the class under the old style (just cause it's gonna stay there, so why not?) instead of using the dynamic Java loader.
3 files.
Applicaction.cfm CONTENTS:::
<!--- 1. your app token and secret --->
<cfparam name="session.oAuthRequestToken" default="">
<cfparam name="session.oAuthRequestTokenSecret" default="">
<!--- 2. the user tokens --->
<cfparam name="session.userAccessToken" default="">
<cfparam name="session.userAccessSecret" default="">
index.cfm file CONTENTS::
<cfsilent>
<!--- always create the java object --->
<cfset Twitter = createObject("java", "twitter4j.Twitter")>
<cfset Twitter.setOAuthConsumer(application.twitterConsumerKey, application.twitterConsumerSecret)>
</cfsilent>
<cfif structKeyExists(url,'mode') IS FALSE>
<cfset requestToken = Twitter.getOAuthRequestToken()>
<cfset session.oAuthRequestToken = RequestToken.getToken()>
<cfset session.oAuthRequestTokenSecret = RequestToken.getTokenSecret()>
<cflocation url="#RequestToken.getAuthorizationURL()#" addtoken="No">
<cfelse>
<cfset accessToken = Twitter.getOAuthAccessToken(session.oAuthRequestToken,session.oAuthRequestTokenSecret)>
<cfset session.userAccessToken = accessToken.getToken()>
<cfset session.userAccessSecret = accessToken.getTokenSecret()>
</cfif>
demo.cfm CONTENTS::
<cfset Twitter = createObject("java", "twitter4j.Twitter")>
<cfset Twitter.setOAuthConsumer(#application.twitterConsumerKey#, #application.twitterConsumerSecret#)>
<cfset Twitter.setOAuthAccessToken(#session.StoredAccessToken# ,#session.StoredAccessSecret#)>
<cfset Twitter.updateStatus("Test: thank you to Rob OBrien and Ray Camden and Twitter4J!")>
Sorry, one mistake, I set my own application's (that is, my Twitter appliction API keys in my CF Application file as well. So the application file also has these three lines:
<cfset application.twitterApiKey="abcdefg">
<cfset application.twitterConsumerKey="hijklmnop">
<cfset application.twitterConsumerSecret="qrstuvwxyz">
Any plans to update this for the latest edition of twitter4j?
My blog entry? If their API didn't change, there isn't much need - is there?
Well Twitter's API didn't change but the java methods have changed for Twitter4j. You don't have to do anything, obviously, it's your call. Just asking.
I wasn't aware it _did_ change. :)
Yeah. I'm using javaloader:
<cfset begintwitter = application.javaloader.create("twitter4j.TwitterFactory")>
<cfset Twitter = begintwitter.getinstance()>
<cfset Twitter.setOAuthConsumer('key','secret')>
I am able to properly redirect to twitter, login and send the oauth back, but for some reason I can't seem to figure out why I'm getting this:
The screen name / password combination seems to be invalid. null <br>The error occurred on line 36.
Line 36 is:
<cfset session.StoredAccessSecret = AccessToken.getTokenSecret()>
I don't know if getTokenSecret() is deprecated or not. I can't find it in the class wiki.
Correction, line 36 is:
<cfset AccessToken = Twitter.getOAuthAccessToken(session.oAuthRequestToken,session.oAuthRequestTokenSecret)>
Did you try skipping the step as the commenter said earlier - ie just get the consumer values via the site?
I went through this myself for jquerybloggers this past week and it worked fine - but I used the Java code from my zip.
Hi Ray,
I've been trying to figure this out all day and I am stumped. Firstly it seems the setOAuthConsumer method is not used anymore in the latest release of twitter4j. - it says it's VOID.
I have opened the jar file as a zip and I don't even see the method in there to call. So I was able to find version twitter4j-core-2.1.4-SNAPSHOT.jar online and I opened that and the setOAuthConsumer method isn't in there either. Am I missing something?
I get the following error:
//////////////////////////////////////////
The setOAuthConsumer method was not found.
Either there are no methods with the specified method name and argument types or the setOAuthConsumer method is overloaded with argument types that ColdFusion cannot decipher reliably. ColdFusion found 0 methods that match the provided arguments. If this is a Java object and you verified that the method exists, use the javacast function to reduce ambiguity.
///////////////////////////////////////
I have tried on CF7, CF8, and CF9. My main requirement is to get it up on CF7 as a client of mine is using that version. I initially got Matt's monkeyTweets working and it's awesome but unfortunately it breaks on CF7.
Any ideas? Thx.
What about trying the version from CFB's repo:
https://github.com/cfjedima...
Grab the JAR there.
Ray - YOU ROCK! Thanks man. My saga has ended.
Initially I was getting the same issue as Ennio above:
401:Authentication credentials were missing or incorrect.
I then dug around a little more and found out that my dev server's clock was not accurate which was causing that error. It said it was the year 2001, and the time was wrong too. Once I fixed that, I was successfully forwarded on.
http://blainegarrett.com/20...
This is CF7 btw, antiquated, I know but the client didn't want to change.
Clock issues. Sigh. Sometimes it's the smallest thing...
Thanks everyone for this post. It saved me a load of time. Initially I had the same error as Rob (missing method using latest version of twitter4j - replaced it with the older version as per Ray's suggestion). Then saw the 401 authentication errors - I changed the access to my twitter app to client rather than web and regenerated the token and then everything worked fine. Also took on board the suggestion about using the server scope. <cftwitter /> in X anyone?
Just found this post while working on setting up twitter4j. Thanks again for another great post!
Can anyone confirm the memory leak problems with using the application scope? Just wondering if I should make the change to the server scope?
I couldn't get twitter4j 2.2.x to work with CF - the getOAuthAccessToken() method is too overloaded and CF doesn't type well enough. However, I was able to download version 2.1.12 (http://twitter4j.org/en/twi... and it works great.
I also had the server time issues - 26 minutes off and it would not work.
Thanks for this, you guys are awesome.
The current version of twitter4j is 2.2 and they changed the Twitter object from a class to an interface. You can no longer get an instance of the implementing class by invoking the Twitter object. If you change <cfset application.Twitter = application.javaloader.create("twitter4j.Twitter")> to <cfset application.Twitter = application.javaloader.create("twitter4j.TwitterFactory").getInstance()> it should work.
Great post Ray, and nice meeting you at NCDEVCON this weekend.
Same here - and thanks for that fix!
My comment above is incomplete and I just wanted to point people to a more verbose explanation with a code snippet of what I was referring to about version 2.2.x of twitter4j.
I post a very short blog entry here: http://techblog.troywebcons...
Hi Ray,
If we cont use javaloader and put the jar in the class file how does the java code change
cheers
Um - you can't do it at all I believe.
Ray's correct. In this case, ColdFusion isn't interacting with Twitter. It's really interacting with the twitter java library (the jar file). It's the library that does the heavy lifting. Without the ability to add the library to your classpath or ability to use the classloader, you're unfortunately out of luck :(
twitter requested developers to migrate to API V1.1 and now I cannot post using your method. Any suggestions of how to resolve this issue now ?
We are having the same problem as Talal. Twitter retired API V1.0, we not getting any new twitter post after 6/12. I'm hoping someone will have a resolution soon.