The Deep End

go to graworks.com! email!

Thursday, February 24, 2005

Thunderbird Extensions: Navigating Folders

Your Thunderbird extension may need, at some point, to find a folder, delete a folder, get the messages in a folder, or whatever. And here's how!
var view = GetDBView();
view.doCommand(nsMsgViewCommandType.expandAll);
var fldrlocal = view.getFolderForViewIndex(0).rootFolder;
var fldrtemplates = fldrlocal.FindSubFolder("Templates");
var subfdr = fldrtemplates.FindSubFolder("Inferior Subfolder");

First of all, GetDBView() wasn't defined somewhere else in my .js file; apparently it is defined somewhere in the Thunderbird source, because you can use it and it works. Again, thanks to the DictionarySearch extension, because I got some of this code from there.

A major limitation of this code is that it assumes the folder at "View Index 0" has a root folder!!!

The only reason that this wouldn't work is if you had selected "Local Folders" instead of an actual folder. Normally this isn't a problem because by default "Inbox" is selected.

Something that I don't understand is the correlation between the list of folders in the Folders pane and the "ViewIndex". From prior experimentation I know that if you select a folder and then call getURIForViewIndex(int) then the uri returned will be the message uri of the somethingth message of whatever folder you have selected.

My guess is that a "ViewIndex" actually refers to a message, and getURI gets the message Uri while getFolder gets the parent folder. However, this may not be the case and should not be assumed to be true. If anyone really does know what a ViewIndex is, please feel free to comment or email me.

Once you've actually found a folder, moving around gets pretty easy. nsIMsgFolder is the XPCOM model of a folder, and there are some useful and self-explanatory functions at XulPlanet!

Thunderbird Extensions: Opening a Compose Window

Previous posts have made vague and mysterious allusions to "opening a compose window" and to clear that up, here's how:

There are actually a multitude of ways to open a compose window, but most of them will have nsIMsgComposeParams and nsIMsgComposeService.

The way I found it done was in the DictionarySearch extension (this is a little bit different from his version but not much):

function composeMessageWithText(subject,text,type,format){
var msgComposeType = Components.interfaces.nsIMsgCompType;
var msgComposFormat = Components.interfaces.nsIMsgCompFormat;
var msgComposeService = Components.classes['@mozilla.org/messengercompose;1'].getService();
msgComposeService = msgComposeService.QueryInterface(Components.interfaces.nsIMsgComposeService);

gAccountManager = Components.classes['@mozilla.org/messenger/account-manager;1'].
getService(Components.interfaces.nsIMsgAccountManager);

var params = Components.classes['@mozilla.org/messengercompose/composeparams;1'].
createInstance(Components.interfaces.nsIMsgComposeParams);
if (params)
{
params.type = msgComposeType.Template;
params.format = format;
var composeFields = Components.classes['@mozilla.org/messengercompose/composefields;1'].
createInstance(Components.interfaces.nsIMsgCompFields);
if (composeFields)
{
composeFields.to = "foo@foo.net"
text = trimAll(text);
composeFields.body = text;
composeFields.subject = subject;


params.composeFields = composeFields;

msgComposeService.OpenComposeWindowWithParams(null, params);
}
}
}

The other ways are all through msgComposeService; for the nsIMsgWindow parameter, type msgWindow. This works because...msgWindow is always the right menu? At any rate, it always works. Which is nice, even if it is complicated.

Thunderbird Extensions: How to Get the Body of A Message

The XPCOM component nsIMsgHdr is, as you would expect, the header of a message. Oddly there doesn't seem to be a component like "nsITheMsg" that includes the header--the header is, for all practical purposes, the message. This means that getting to the body of the message is really frustrating. So--to get the body:

First you'll need to create an nsIInputStream . Because the stream that you create will need to "contain" the message, the getOfflineFileStream method of nsIMsgFolder will return a relevant stream.

The parameters of getOfflineFileStream are (1) the message key, which is a property of the header (yourHdr.messageKey); and two so-called "out" parameters--which is basically the way that javascript returns more than one thing. All that you need to do with these to make Thunderbird happy is to initialize them as Objects and then just pass them in. Like so:
var offset = new Object();
var messageSize = new Object();

try{


is = fdr.getOfflineFileStream(hdr.messageKey,offset,messageSize);

}catch(e){
alert("message: "+e.message);
}

fdr is the folder. If you're having trouble getting to the right folder/any folder, see the subsequent post on navigating folders with XPCOM.
Then, because nsIInputStream is a "frozen interface" (I'm not entirely sure what a frozen interface is, so I'm not going to guess) you'll need to use a safe, friendly wrapper class called nsIScriptableInputStream.
This code will get you the body and headers of the message (in other words the entire message):
try{


var factory = Components.classes["@mozilla.org/scriptableinputstream;1"];

var sis = factory.createInstance(nsIScriptableInputStream);
sis.init(is);

bodyAndHdr = sis.read(hdr.messageSize-10);

//the -10 gets rid of this really weird "From - Tue " thing
// (which is an exact quote). The reason why it shows "From - Tue" is
//that it's actually part of the next message's header, and in fact,
//the stream contains all of the messages in the folder.
//it's as yet unclear why hdr.messageSize should be off by 10, or why
//the out parameter to is (which is also called messageSize) always
//shows up as 0.


}catch(e){
alert("message: "+e.message);
}
Weirdly, offset (the out parameter "returned" from getOfflineFileStream) has the same value as hdr.messageSize. This may be a problem with the API, because offset and messageSize are entirely different things.

As a side note, if you want to do anything at all with offset, use offset.value because offset is an object.

The last part to successfully getting the body--which is really sort of optional--is to open the body in a compose window.

One easy way to separate the hdr from the body in bodyAndHdr is:
var hdrstr = bodyAndHdr.indexOf("\r\n\r\n"); //marks the end of the //headers
body = bodyAndHdr.substring(hdrstr+1,body.length);

Tuesday, February 22, 2005

Thunderbird Development: Extensions Introduction

What does this alt thing do


Well, this is my first post of any kind ever! I'm an intern and working on a project which requires the development of extensions for Thunderbird, Mozilla's mail client (see above picture.) Because there's not a lot of documentation for Mozilla--although apparently there are people out there who actually understand how it works--I've had to piece together a lot of juicy informational tidbits which sort of make sense as a cohesive whole.

Mozilla extensions (Firefox or Thunderbird) are composed of two main parts -- the .xul file, and the .js file (javascript). XUL is the xml-based graphical part of Mozilla, and there are extensive tutorials at XulPlanet. XulPlanet mostly just explains how xul can be used for gui's outside of Mozilla (and not specifically for Mozilla) so the best way that I found for learning how xul works is to download an extension and dissect all the files--this way, you can learn Mozilla-specific things like the name of the toolbar you're inserting a button into. Because my extension requires a button, I downloaded Buttons! by Maximum Extension. A great tutorial that explains the basics of extensions can be found on Eric Hamiter's blog , and I'll try not to repeat anything that's stated there.

The javascript is by far the most confusing half of writing an extension. While functions inherent to javascript like alert() are easy, most of the things that you'll want an extension to do are accomplished using XPCOM. An in-depth explanation and tutorial of xpcom can be found here at Mozilla's site. After the basics of all this, the most important part of writing an extension is finding the components/interfaces that you want, and you'll do that at xulplanet. Because xulplanet lists the components, properties, methods, etc., but doesn't actually explain what they mean, I soon developed an unhealthy love-hate relationship with that site, but the complete lack of meaningful information will develop your sixth sense to the uncanny point where you don't even need documentation--although it is xulplanet, not xpcomplanet.

Mozilla's site does have some interfaces documented; the ones that I found that were relevant to my purposes were found here and a few others which will be probably be posted up later. Poking around here would probably be helpful.

That's it for the introduction--later posts will be a lot more specific to certain components and interfaces (the ones that I'm using and happen to understand).