Building a Java-Based AIM Bot to Stay Up-To-Date on Your Flickr Activity
By Byron Johnson
January 27, 2008
Introduction
Social networking is currently one of the hot topics on the Internet. For some, it's synonymous with "Web 2.0." Although mega-social networking sites offer exciting new ways for people to connect online, one drawback is that now Internet users have more places to maintain various pieces of information such as photos, comments, tags, and contacts.
What if you want to stay in touch with what happens on a site without actually opening a browser and going to the site? In addition, say you want your instant messenger application to notify you if there are changes to the content you've posted or accumulated on Flickr? Or you might even want to post and retrieve updates from Twitter.
Some social networking sites--for example, Flickr--make their APIs available so that users can create fun and exciting web-enabled services such as mashups that help them better manage their online content. Instead of having to regularly check multiple sites for different pieces of information, you can use AOL Instant Messenger (AIM®) as the "glue" to connect social networking sites together. Using AIM and AOL bots, you can create a service that can access a social networking site through its API and send updates or activity reports back to AIM. For example, as your content changes on Flickr, you can automatically retrieve updates using AIM and display the updates in the AIM notification window. Cool, eh?
In this article, I show you how to use the AIM software developer kit (SDK) and an AOL bot as a notification system for Flickr. You'll create a mashup of the Flickr API with the AIM SDK to establish a basic but valuable notification system. I also show you how the bot can check for updates on a set interval or on demand from an instant message (IM).
The AIM SDK
The AIM SDK provides a standardized programming interface that allows users to mash-up AOL's AIM instant messaging functionality with other web services to create a new service for applications or personal use. Developers can use the SDK to create a full range of communication options, including notification, text messaging, and file transfer.
Several useful articles have been written about using the AIM SDK and AOL bots. I will skip the basics of building and configuring a bot, because some good information has already been published on this topic. To get up to speed on creating and configuring a basic bot, and using the AIM SDK, I recommend a three-part series written by James Turner and published on the AOL Developer Network:
- Creating AIM-Enabled Applications in Java, Part 1
- Creating AIM-Enabled Applications in Java, Part 2
- Creating AIM-Enabled Applications in Java, Part 3
The AIM SDK contains numerous sample bots and AOL Custom Clients in the samples subdirectory. In most cases, you can get a sample bot up and running within 10 minutes after meeting a few prerequisites.
In the following section, I'll discuss configuring the sample bot accjbot, which you can find in the AIM SDK samples subdirectory, to use various Flickr API method calls to create a powerful AIM/Flickr mashup.
Building Basic Bot Functionality
As mentioned in James Turner's three-part article, you must install some prerequisites before mashing the AIM SDK with the Flickr API:
- AIM SDK - Download the latest version of the SDK at http://developer.aim.com. Follow the instructions from the first tutorial by James Turner to set up the SDK and request an API Key from AOL.
- Java Development Kit (JDK) - Download the latest JDK at http://java.sun.com. Install this package in any local directory. See the article by James Turner on bot configuration.
- Apache Ant tool - Download the latest version of Apache's Ant tool. Install this package in any local directory. This installation is not necessarily a prerequisite to executing a class file, but the tool makes it very easy to compile, build, and deploy any sample code in the AIM SDK. I highly recommend using this tool to speed up the process of building your bots.
- Flickr API - Download one of the latest versions of the Java API for Flickr. In this article, we will use Flickrj, a Java-based API for Flickr. You can download the API at http://flickrj.sourceforge.net/. Install this package in any local directory. To use the Flickr API you must first request an API Key from Flickr.
- Environment Configuration - At this stage, use a Windows .cmd file called use_java.cmd to contain the path directories of all your tools. You will use this file to set up a DOS environment after executing
cmd.exe. Create a .bat or .cmd file that can easily set up your environment if you are not using an integrated development environment (IDE). Following is an example .bat file:

Now, with the prerequisites downloaded and installed, compile the sample/basic bot. Navigate to the accjbot subdirectory under samples. Compile the sample bot in the samples directory.
If you receive any errors at this point, revisit James Turner's article on building the bot.
Before you execute the compiled class, make some slight changes to the sample accjbot.java file. Change the functionality of the class to read from a properties file rather than from the command line:
try {
in = getClass().getResourceAsStream("/setup.properties");
properties = new Properties();
properties.load(in);
} finally {
in.close();
}
With the properties file in place, remove the code to change the arguments passed on the command line. Replace this code:
if(args.length != 2) {
System.out.print("usage: java accjbot username password");
return;
}
try {
new accjbot(args[0], args[1]);
With this code:
System.out.println("Starting Bot...");
try {
new accjbot();
Add the new variables String username and String password to code and populate with values from setup.properties:
String username = properties.getProperty("username");
System.out.println("Setting Bot Identity to: "+ username);
session.setIdentity(username);
String password = properties.getProperty("password");
System.out.println("Setting Bot pass to : XXXXXX");
session.signOn(password);
Note: I added 'XXXXXX' to hide my password.
The final version of the method is listed next. Notice the addition of ParserConfigurationException, IOException, and SAXException. These classes are required because of the additional lines for the properties file. Obviously, these classes and their packages need to be added in the import statement.
public accjbot() throws AccException,ParserConfigurationException, IOException, SAXException
{
//Retrieve properties
InputStream in = null;
try {
in = getClass().getResourceAsStream("/setup.properties");
properties = new Properties();
properties.load(in);
} finally {
IOUtilities.close(in);
}
String username = properties.getProperty("username");
System.out.println("Setting Bot Identity to: "+ username);
String password = properties.getProperty("password");
System.out.println("Setting Bot pass to : XXXXXX");
// Create main session object
session = new AccSession();
// Add event listener
session.setEventListener(this);
// set key
AccClientInfo info = session.getClientInfo();
info.setDescription(key);
// set screen name
session.setIdentity(username);
// setup prefs so anyone can IM us, but not chats or DIMs
session.setPrefsHook(new Prefs());
AccPreferences prefs = session.getPrefs();
prefs.setValue("aimcc.im.chat.permissions.buddies", AccPermissions.RejectAll);
prefs.setValue("aimcc.im.chat.permissions.nonBuddies", AccPermissions.RejectAll);
prefs.setValue("aimcc.im.direct.permissions.buddies", AccPermissions.RejectAll);
prefs.setValue("aimcc.im.direct.permissions.nonBuddies", AccPermissions.RejectAll);
prefs.setValue("aimcc.im.standard.permissions.buddies", AccPermissions.AcceptAll);
prefs.setValue("aimcc.im.standard.permissions.nonBuddies", AccPermissions.AcceptAll);
session.signOn(password);
//msg pump
while( running )
{
try {
AccSession.pump(50);
} catch (Exception e) {
e.printStackTrace();
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
info = null;
session = null;
prefs = null;
System.gc();
System.runFinalization();
}
Everything else in the sample file accjbot.java should remain the same. Compile the bot to make sure all of your edits are successful. If any compilation errors occur, double-check your import statements to ensure you specified all the required packages and classes.
After a successful compilation, go to the dist\release directory to execute the bot and confirm there are no runtime exceptions.
Using an existing AIM session, send an IM to the bot. If everything works correctly, any text you type in the window will be echoed back.
AnyAOLID (1:04:22 PM): Hello World
FunwAimEx (1:04:27 PM): Hello World
Rounding Out the Basic Bot with On-Demand Features
Now that you have a basic bot that responds to commands in the IM window, you can add some more advanced features. To stay with a "keep it simple" model, there are three things you want to see returned back in the bot when you send an IM:
- A response to a stats command or some type of report.
- A toggle command to make the bot appear visible (online) or invisible (offline).
- A history of all commands sent to the bot.
The stats functionality is straightforward because this feature is part of the initial sample code. The only change you want to make is to place an exclamation mark (!) in front of the word "stats." Thus, the actual command to retrieve the stats would be "!stats." This functionally should be added back into the OnImReceived method:
if(msg.equals("!stats"))
{
long diff = (new Date()).getTime() - start;
diff /= 1000; // convert to seconds
long days = diff/86400;
diff = diff - 86400*days;
long hours = diff/3600;
diff = diff - 3600*hours;
long minutes = diff/60;
richText = "Running for "+days+" day"+ ((days!=1)?"s":"")+", "+hours+" hour"+((hours!=1)?"s":"")+", "+minutes+" minute"+((minutes!=1)?"s":"")+".";
}
Next, add a toggle (which actually will be two commands) to set the bot to appear either online or offline:
else if(msg.startsWith("!OfflineOn"))
{
richText = "Action command has been invoked: Setting Online";
session.setAppearOffline(true);
}
else if(msg.startsWith("!OfflineOff"))
{
richText = "Action command has been invoked: Setting Offline";
session.setAppearOffline(false);
}
Now, add functionality to keep track of all commands sent to the bot. Create a new add functionality to invoke a list of commands. Create a collections object--for example, called Vector:
Vector<String> cmdHistory = new Vector<String>();
Then, add the following to each action call in the method OnImReceived:
cmdHistory ("!offlineOn");
Mash Up the Flickr API with the AIM SDK and AOL Bot
Now that you have a basic bot with some simple features, you can begin integrating some Flickr features.
First, however, there are prerequisites to using the Flickr API. As I mentioned earlier, one prerequisite for using the Flickr API is to request an API Key from Flickr. The high-level steps of obtaining and using the Flickr API are:
- Obtain an API Key from Flickr.
- Configure your API Key to receive your Shared Secret (a secondary login key).
- Create a login link in your application that will use the API Key and Shared Secret.
I recommend reviewing Flickr Authentication API Web Applications How-To for further information on using the Flickr API. When you have obtained and configured your API Key, continue setting up the Flickr API classes.
The first step is to ensure that flickrapi is in your CLASSPATH. You do this by adding the following to your use_java.cmd file:
set FLICKR_HOME=E:\flickrapi-1.0 set CLASSPATH=%FLICKR_HOME%\flickrapi-1.0.jar;%ACC_SDK_HOME%\dist\release\accjwrap.jar
Next, specify the Flickr API Key, the Shared Secret, and your user ID--nsid, assigned by Flickr--in setup.properties:
apiKey = myapikey sharedSecret = mysharedSecret nsid = nsid@239 botuser = funwaimex botpass = XXXXXX
Then, add syntax to retrieve the values from the properties file:
String apiKey = properties.getProperty("apiKey");
System.out.println("Retrieved apiKey: "+ apiKey);
String sharedSecret = properties.getProperty("sharedSecret");
System.out.println("Retrieved sharedSecret: "+ sharedSecret);
String nsid = properties.getProperty("nsid");
System.out.println("Retrieved nsid: "+ nsid);
Create instance variables to hold the Flickr session, AuthStore, your nsid, and the Shared Secret:
private String nsid = null; private Flickr flickr = null; private AuthStore authStore = null; private String sharedSecret = null;
Within the public accjbot method, add functionality to retrieve and store Flickr information:
this.flickr = new Flickr(apiKey);
this.sharedSecret = sharedSecret;
this.nsid = nsid;
File authsDir = new File(System.getProperty("user.home") + File.separatorChar + ".flickrAuth");
System.out.println("AuthDir: "+ authsDir);
if (authsDir != null) {
this.authStore = new FileAuthStore(authsDir);
}
Add an authorization method that can be called during the first execution:
private void authorization() throws IOException, SAXException, FlickrException {
String frob = this.flickr.getAuthInterface().getFrob();
URL authUrl = this.flickr.getAuthInterface().buildAuthenticationUrl(Permission.READ, frob);
System.out.println("Authorization Required. Browse to URL: " + authUrl.toExternalForm() + " then, hit enter.");
System.in.read();
Auth token = this.flickr.getAuthInterface().getToken(frob);
RequestContext.getRequestContext().setAuth(token);
this.authStore.store(token);
System.out.println("");
}
Finally, add functionality that will return the activity updates from Flickr for the calling ID. Add the following code:
else if(msg.startsWith("!getActivity"))
{
RequestContext rc = RequestContext.getRequestContext();
rc.setSharedSecret(this.sharedSecret);
if (this.authStore != null) {
Auth auth = this.authStore.retrieve(this.nsid);
if (auth == null) this.authorization();
else rc.setAuth(auth);
}
ActivityInterface iface2 = flickr.getActivityInterface();
ItemList list = iface2.userPhotos(50, 0, "300d");
for (int j = 0; j < list.size(); j++) {
Item item = (Item) list.get(j);
richText="Item " + (j + 1) + "/" + list.size() + " type: " + item.getType()+"<br>";
richText="Item-id: " + item.getId() + "<br>";
ArrayList events = (ArrayList) item.getEvents();
for (int i = 0; i < events.size(); i++) {
richText="Event " + (i + 1) + "/" + events.size() + " of Item " + (j + 1)+"<br>";
richText="Event-type: " + ((Event) events.get(i)).getType()+"<br>";
if (((Event) events.get(i)).getType().equals("note")) {
richText="Note-id: " + ((Event) events.get(i)).getId()+"<br>";
} else if (((Event) events.get(i)).getType().equals("comment")){
richText="Comment-id: " + ((Event) events.get(i)).getId()+"<br>";
}
richText="User: " + ((Event) events.get(i)).getUser()+"<br>";
richText="Username: " + ((Event) events.get(i)).getUsername()+"<br>";
richText="Value: " + ((Event) events.get(i)).getValue()+"<br>";
richText="Dateadded: " + ((Event) events.get(i)).getDateadded() +"<br>"; }
}
}
When any request comes in with !getActivity, you'll receive an update from an IM of activity related to any of the photos you have posted on Flickr. Now, recompile to make sure all of your changes work.
Now, execute!
It looks like the bot is online. Now, test all of your feature enhancements:
AnyAOLID (6:32:46 PM): !stats
FunwAimEx (6:32:47 PM): Running for 0 days, 0 hours, 0 minutes.
AnyAOLID (6:33:24 PM): !setOffline
FunwAimEx (6:33:26 PM): Action command has been invoked: Setting offline
FunwAimEx signed off at 6:33:24 PM.
funwaimex is offline and will receive your IMs when signing back in.
AnyAOLID (6:37:56 PM): !setOnline
FunwAimEx (6:37:57 PM): Action command has been invoked: Setting online
FunwAimEx signed on at 6:37:56 PM
.
AnyAOLID (6:38:34 PM): !getActivity
FunwAimEx (6:38:36 PM): Item 1/1 type: photo
Item-id: 2182220504
Event 1/1 of Item 1
Event-type: comment
Comment-id: 22581189-2182220504-72157603709808953
User: NS833@N07
Username: my_username
Value: I really like this one.
Dateadded: Sun Jan 13 20:13:46 EST 2008
As you can see, the !getActivity method call returns a list (in this case, one item) of any events that have occurred against any of the photos on your site. This is exactly what you want. Now, with just a click of the mouse (and no logging into the site) you can get a list of the latest activity related to pictures in your Flickr account.
Review
We've only scratched the surface of what's available when mashing up the AIM SDK with the Flickr API. There are a number of features we could implement on the functionality side; for example, one important remaining task is code refactoring!
However, beyond code refactoring, there are tons of small but high-value implementations that would make this little bot more mature. For example:
- Retrieving the URL of photos from Flickr.
- Retrieving a buffered image of the URL and embedding it in the AIM window.
- Saving user names and command history, along with the date.
- Configuring the bot to respond only to users who are on its Buddy List.
- Reformatting the
!getActivitycommand to "beautify" the output a bit more.
Obviously, there are many ways to implement this feature. The goal of this article was to "keep it simple" and "keep it useful." I hope that, using this article as a stepping stone, other developers can provide tighter integration between the APIs and so create additional fun, useful web services.
