Creating an AIM Lite Plugin to Send a Text File - Part 2

Enable the Subscriptions block here!
AIM Lite Plugin

by Doug Schwartz
May 18, 2007

Introduction

In part 1 of this series I explained how to create an AIM Lite plugin that received a text file as part of an IM. In this article I show how to create the sending plugin. If you have not read part 1, this article will not make much sense. Since you are still with me, I am going to jump right into creating the sending plugin.

The sending plugin lets the user select a text file and sends that text file to their current buddy.

We are going to create the plugin in the following order:

  1. Create the plugin.xml file.
  2. Create the main.box file.
  3. Create the main.js file.

Creating the plugin.xml File

The plugin.xml file contains a single widget element. This element can contain a number of sub-elements, but the only required sub-element is the uuid, which is a developer or deployment key. The only differences between this plugin.xml file and the one for the receiving plugin are the uuid, name, and description. Don't forget to replace YOUR_DEVELOPER_KEY with your developer key in the following code.

<?xml version="1.0" encoding="utf-8" ?>
<widget
    uuid = "{YOUR_DEVELOPER_KEY}"
    version = "1.0"
    name = "SendTextFile"
    description = "This plugin sends a text file"
    vendor = "Me, Myself, and I Inc."
/>

Creating the main.box File

This file is exactly the same as the main.box for the receiving plugin. The main.box file contains a single window element. This element defines the user interface (UI) for the plugin. A plugin can be visible, invisible (no UI), or visible only in response to an event. This plugin is invisible, and main.box contains the following text:

<?xml version="1.0" encoding="utf-8" ?>
<window
    xmlns="http://www.aol.com/boxely/box.xsd"
    xmlns:s="http://www.aol.com/boxely/style.xsd"
    xmlns:on="http://www.aol.com/boxely/reaction.xsd"
    on:constructed="onConstructed();"
    on:destroyed="onDestroyed();"
    s:height="0"
    s:width="0"
    hidden="true"
    collapsed="true"
    floating="true"
>
  <code id="main" language="jscript" src="main.js"/>
</window>

You can easily tell the plugin is invisible by the hidden="true" element.

Creating the main.js File

The main difference in this plugin versus the receiving plugin is that in this plugin the IM is not intercepted, but created in a new function, sendTextFile, which is invoked from the exec function when the plugin is selected.

The first four lines of main.js contain four global variables.

var kCommandId = 0;
var gSession;
var sendTo;
var maxChars = 64;

Where:

kCommandId
Defines the integer value of the plugin command.
gSession
Caches the session object.
sendTo
Contains the name of the current buddy.
maxChars
Limits the number of characters we send in the IM.

Other than the cmd.text assignment, the onConstructed function is the same in the sending plugin as the receiving.

function onConstructed() {
var dict = scene.paramsDictionary;
gSession = dict.valueForKey("session");

var pInfo = dict.valueForKey("pluginInfo");
var cmd = pInfo.addCommand(kCommandId);
cmd.text = "Send text file to current buddy";

scene.connectObject(gSession, "session_");
dict.setValueForKey(new commandListener(), "commandTarget");
}

The sending plugin does trap the IM to get the name of the current buddy. It could do this in a number of places, but I chose to intercept the IM in BeforeImSend. It saves the name of the current buddy in the sendTo variable. We will use this later to create the IM we send.

function session_BeforeImSend(session, imSession, recipient, im) {
    sendTo = recipient.Name;
}

The Exec function only checks whether the incoming id is that of our command, kCommandId. If it is, Exec calls the sendTextFile function. Here is the Exec function.

commandListener.prototype.Exec=function(id, users) {
    if (id == kCommandId) {
        sendTextFile();
    }
}

The sendTextFile function is where the action is. It performs the following.

  1. Creates the Open File dialog box so the text file can be selected.
  2. Splits the filename from the path.
  3. Reads the file into a variable.
  4. Truncates the text if it is too long.
  5. Encodes the text.
  6. Creates a new IM.
  7. Gets the session with the current buddy.
  8. Sends the IM.

Here is the code of the sendTextFile function.

function sendTextFile() {
   var filter = "Text Files (*.txt)|*.txt||";
   var fileList = appUtils.openFileDialog(this.scene,
                                          "Open File",
                                          null,
                                          null,
                                          filter,
                                          false);
    
   if (fileList) {
      // Split off path from filename
      var pathFile = fileList.getValue(0);
      var last = pathFile.lastIndexOf("\\") + 1;
      var path = pathFile.substring(0, last);
      var fName = pathFile.substring(last);          
        
      // Open file and read the whole thing in
      var basics = shell.serviceManager.basics;
      var fs = basics.fileStream;
      var sr = basics.rawStreamReader;
      fs.openForRead(pathFile);
      sr.stream = fs;
      var myFile = sr.readStringTillEndOfStream("");
      fs.close();
        
      // Get the # of chars in the file
      var numChars = myFile.length;
        
      if (numChars > maxChars) {
         numChars = maxChars;
         myFile = myFile.substr(0, maxChars);
      }
        
      // Just in case file has some chars that
      // AIM Lite cannot digest directly
      myFile = encodeURIComponent(myFile);
        
      // Send filename, # of chars, then file
      var prefix = fName    +
                   ":"      +
                   numChars +
                   ":"      +
                   myFile;
        
        // Create IM
        var im = gSession.createIM(prefix);
        
        // Get session with current buddy
        // sendTo is 
        var imSess = gSession.CreateImSession(sendTo);
        
        // Send the IM containing the file
        imSess.SendIm(im);
    }
}

Figure 1 shows an example of the Open File dialog box.

The open file dialog
Figure 1. The Open File dialog box

As with the receiving plugin, the sending plugin also needs to create the QueryStatus function. Here is the code for this function and the commandListener object.

function commandListener() {}

commandListener.prototype.QueryStatus=function(id,
                                               users) {
   return (id == kCommandId) ? true : false;
}

Testing the Plugin

There is no easy way to test this plugin besides deploying it. So zip up plugin.xml, content/main.box, and content/main.js, change the extension of the ZIP file to ".awi", double-click the AWI file, open two AIM Lite clients, create an IM session between the two, select the ReceiveTextFile plugin in one, select the SendTextFile plugin in the other, and finally, send the text file.

Conclusion

Now you understand how to create an IM and use it to send a short text file. The next step would be to remove the constraints of 64 characters and text only. I envision some sort of handshake protocol between the sending plugin and the receiving plugin. That way the sending plugin would be assured that the buddy on the other end has installed the receiving plugin. I also imagine buffering larger files as IMs, with the receiving plugin storing the intermediate chunks and concatenating them when the final piece arrives. Also, by encoding the original file and decoding it on the receiving end, there is no reason that non-text files could not be sent.

Resources

SendTextFileCode.zip contains the complete source code for this article. If you want to see this plugin at work:

  1. Install AIM Lite.
  2. Unzip SendTextFileCode.zip to get the source files.
  3. Open the plugin.xml file.
  4. Change YOUR_DEVELOPER_KEY to your developer key.
  5. Zip the files back up.
  6. Change the file extension to ".awi".
  7. Double-click the AWI file to install the plugin.

References

Here are a number of web pages where you can get further information about AIM Lite, plugins, and the source code for this article.