Fun with AppleScript
Volume Number: 19 (2003)
Issue Number: 5
Column Tag: Administrivia
Fun with AppleScript
A fairly useless detour that nonetheless shows you some tricks with iTunes and Entourage v.X
by John C. Welch
Welcome
Okay, so my last couple of articles have been technical, (no surprise, this is Mac*TECH*, after all), but nonetheless, they were pretty dry to not only read, but write as well. (The USB2 spec is a great cure for insomnia) So this time, I thought I'd take a bit of a turn, and share a fun bit of AppleScript that I had done with iTunes and Microsoft Entourage for Mac OS X.
Get Sig From iTunes
Someone had shown me a neat application that was designed to get the name of the current track from iTunes, and insert the track information into your Entourage email message. It was well put together, but there were some things that I didn't like about it. First, it was a background application polling two other applications. That seemed a bit kludgy to me, as this script didn't really need to run that way. To my way of thinking, it is more of an Entourage script, so it should run inside of Entourage. Since Entourage is extremely scriptable, and has support for schedules that let you schedule, among other things, AppleScripts, it seemed to me that there was a way to have this run in Entourage's application space. A few hours of work, and 'voila', it was done.
If you want to just download the script, it's available from:
<http://web.mit.edu/jwelch/www/files/Mac OS X Scripts/EntourageX/SigFromiTunes.hqx>
The most recent versions are there, and it is fairly well commented, so if you don't want to type this all in manually, you can just download it from there.
Main functions of the script
As I saw it, the script had to do a few things, which made writing it pretty easy. It had to be able to get the current track information from iTunes, which I defined as the Track Name, the Track Artist, and the Track Album. If iTunes wasn't playing, then it had to substitute a signature from the user's list of random signatures. If there weren't any random signatures to use, then it had to insert a default signature. I ended up finding some interesting challenges in this, including one bug that could only happen in such a specific configuration, that it got its own name, "The La Grua Bug".
The script then had to take this iTunes information, and create a signature out of it, and insert this text into a predefined signature in Entourage. I ended up attaching the script to a schedule that runs every minute, so the track info is current. As of this article, it's been running constantly whenever I run Entourage, (which is pretty much 24x7), and no real problems.
Like most things with AppleScript, you end up getting help from the people in the community, both with the coding and with the testing. For the coding, I want to thank the folks on the Entourage mailing list,<mailto:Entourage-Talk-on@lists.letterrip.com> specifically Paul Berkowitz, and Dan Crevier, (who also works for the MacBU and is a major reason for the AppleScript quality in things like Outlook Express and Entourage.). For testing, besides the aforementioned (Jeff) La Grua, I'd like to thank the members of the Your Mac Life mailing list, <http://lists.mymacgroups.com/mailman/listinfo/yml>, who were quite helpful during the testing of this script.
Some caveats:
1) This has been tested by me and most others in Entourage v.X and iTunes 2/3 in Mac OS X only. I have had reports of it working correctly in Mac OS 9 with the native versions of the programs there. If it does, cool, but I'm not going to worry if it doesn't. If you wish to change things to fix that, excellent.
2) It only works with Entourage. I thought about Eudora, but the AppleScript dictionary for that Application makes my head hurt. If you want to get it to work with Eudora, please let me know when you do, I personally know a few people who will love you for it. It can't work with Mail at the moment, and Netscape's dictionary isn't even close to allowing it. I don't use PowerMail, or MailSmith, so I don't know if you could modify it for that. But anyone who wishes to may certainly use my code as a starting point.
3) I'm not an AppleScript genius. I'm a pickup truck programmer. Most of my solutions are simple enough that I can read them in six months and remember why I did it that way. If you care to optimize my code, please do, and then comment it so I can maybe pick up a pointer or two.
So enough of this, let's dig into the code!
The AppleScript Code
The first section of this sets up some basic variables, and checks to see if iTunes is running at all. If it is, then we set a flag to show this. Otherwise, we avoid trying to get the track information, and dump into the 'iTunes isn't playing' sections.
set isiTunesRunning to 0
set theNewSig to ""
set iTunesIsPlaying to 0
The first three lines are pretty simple. Two flags, 'isiTunesRunning' and 'iTunesIsPlaying' are set to 0, which is the safer value. 'theNewSig', the variable used to hold the signature that will be built is initialized here as well.
try
--the try allows for problems getting the process list from the finder without needing to
--pop dialogs in e'rage if this times out
tell application "Finder"
if (get name of every process) contains "iTunes" then
--if iTunes isn't running, then assume the user wants things this way, and leave things
--alone like a good boy
set isiTunesRunning to 1
end if
end tell
on error
quit
end try
This section asks the Finder if iTunes is running. If it isn't, then isiTunesRunning stays at 0, if it is, then it gets set to 1. The try block was needed because if Entourage was particularly busy, or the Finder was, or both were busy, then you'd get an error alert complaining that the script couldn't get the name of every process. If any errors occur, the script quits, and you just wait until the next run to update things.
if isiTunesRunning = 1 then
tell application "iTunes"
--start talking to iTunes
if player state is playing then
--now we want to see if iTunes is even playing
set iTunesIsPlaying to 1
set theName to the name of the current track
--get the track name
set theArtist to the artist of the current track
--get the artist name
set theAlbum to the album of the current track
--get the album name
if theAlbum is "" then set theAlbum to "Beats the hell out of me"
--default if no album name
if theArtist is "" then set theArtist to "Who the hell knows"
--default if no artist name
if theName is "" then set theName to "Too Zen for me"
--default if no track name
set theNewSig to "--
I'm listening to " & theName & " by " & theArtist & " off of " & theAlbum
--now we set the sig to the iTunes values. This has to
--happen here, otherwise we override other defaults, that would be bad
end if
end tell
--our iTunes work is done
if iTunesIsPlaying = 1 then
--iTunes is running AND playing
buildSig(theNewSig)
--build the signature from the iTunes data, and update the e'rage sig since we call
--the same code twice, let's make it a handler, and save space
else
--iTunes is running, but not playing
set theNewSig to buildSigWithoutiTunes(theNewSig)
--call the handler that builds a sig without iTunes running
buildSig(theNewSig)
--update the sent sig to some random sig created by the previous line
end if
else
--iTunes isn't running or playing
set theNewSig to buildSigWithoutiTunes(theNewSig)
--call the handler that builds a sig without iTunes running
buildSig(theNewSig)
--update the sent sig to some random sig created by the previous line
end if
The first line of this section tests to see if iTunes is running. If the flag, 'isiTunesRunning' is set to 1, then iTunes is running, and we start talking to iTunes. Otherwise, this section is skipped. The 'tell block' routes all the commands inside the 'tell block' to iTunes, and the first thing we need to know is the player state. ITunes could be on, but not playing. So the 'if player stated is playing' line asks iTunes for this information. There are five possible values for 'player state': stopped, playing, paused, rewinding, fast forwarding. Since we only care about playing, we use that as our test. If the 'player state' is playing, then 'iTunesIsPlaying' is set to 1.
Since iTunes is playing, we now get the names of the track, the artist, and the album the track comes from and put them into theName, theArtist, and theAlbum, respectively. We then test to see if any of these variables are blank, indicating that iTunes doesn't have this information, and if they are, we insert default values into them. Once this is done, we create the new signature text, and put it into 'theNewSig'. The reason why it looks a little odd is that I inserted returns so that we sould get a good structure in the email message. The code for these is '\r', but when you compile the script, AppleScript converts these to actual returns, so you get the breaks instead of the characters. The pre-compiled code for the signature text is:
"--\rI'm listening to " & theName & " by " & theArtist & " off of " & theAlbum
The '&' characters allow me to concatenate the data in the three track variables into the text string of the signature. Once that is done, we close the if statement with an 'end if', and the tell block with an 'end tell', and that's all we need to talk to iTunes for.
The next if statement checks the value of 'iTunesIsPlaying'. If it is 1, then we call the handler 'buildSig()' and pass it the variable theNewSig that we just created. This handler is pretty short, as seen below:
on buildSig(theSignature)
--handler/subroutine to set the sig in E'Rage
tell application "Microsoft Entourage"
--this actually ends up setting the signature *after* you send the mail,
--but until I get a proper signature property, it's the only non-kludgy way to do this
if exists signature "iTunesSig" then
--if iTunes Sig is there
set the content of signature "iTunesSig" to theSignature
--change the contents
else
--if it isn't there
make new signature with properties {name:"iTunesSig", content:theSignature, include in
random:false}
--create iTunes Sig, with the correct contents
end if
end tell
end buildSig
This handler tells Entourage to set a specific signature to the contents of 'theSignature', which is carrying the data from theNewSig. One quirk here is that you are actually changing the signature after the message is sent, as you can't directly manipulate the signature in an email message yet. The first 'if' inside the tell block asks Entourage if a signature named "iTunes Sig" exists. If it does, then the content of that signature is changed to the content of 'theSignature', and we're done with the handler. If "iTunes Sig" doesn't exist, then we use the 'make new signature' statement to create the signature, name it "iTunes Sig", set the content to 'theSignature', and make it a non-random signature. (that last bit is important. If "iTunes Sig" is random, you can start setting up for the La Grua bug.) Once that's done, we're done with the handler, and we jump back into the main program.
Now, at the beginning of the signature building block of code, we checked to see if iTunes was playing. If it wasn't , (if we got to here, then iTunes is running, but is not playing), then we have to create a different signature, so we set 'theNewSig' to the results of a different handler, 'buildSigWithoutiTunes()', and pass it 'theNewSig'. This handler's code is shown below:
on buildSigWithoutiTunes(theSignature)
tell application "Microsoft Entourage"
--here's where we use a random signature, thank Paul Berkowitz for the help here
set randomSigs to every signature whose include in random is true
if the length of randomSigs is equal to 1 then
(* this is to catch the La Grua bug, wherein you only have one sig set to random,
namely the iTunesSig. This creates a problem where instead of the not playing
message you get the name of the last tune to play until you run iTunes again. *)
set theSignature to the name of the first item of randomSigs
if theSignature is "iTunesSig" then
set theSignature to "--
itunes isn't playing right now"
end if
end if
if the length of randomSigs is greater than 1 then
--more than one signature in the random list
set theSignature to some item of randomSigs
--'this gives you the numerical ID of the selected sig
set theSignature to the content of theSignature
--this gives you the text content of the selected sig
else
--uh - oh, no random sigs
set theSignature to "--
itunes isn't playing right now"
end if
end tell
return theSignature
end buildSigWithoutiTunes
So now, we have to deal with creating a signature that has nothing to do with iTunes, since it isn't playing anything. We tell Entourage to create a list of signatures that have the random flag set to true. Next we avoid the La Grua bug, by checking to see if the number of items in this list is 1. If it is, we set 'theSignature' to the name of that single item. If that name is "iTunesSig" then we don't want to use potentially very old track information, so we change the contends of 'theSignature' to "--\r iTunes isn't playing right now", we return theSignature, and we're done with the handler.
If the number of items in randomSigs is greater than one, then we set 'theSignature' to 'some item' of 'randomSigs, (AppleScriptese for 'gimme a random selection from that list'). This gives us the index number for a random selection from that list. We then reset 'theSignature' to the contents of the signature that the index points to. Once that is finished, we return 'theSignature' and we're done. The 'else' statement is only there if the number of random signatures is not 1 and not greater than 1, so all that's left is 0 random signatures. In that case, we set 'theSignature' to "--\r iTunes isn't playing right now", we return 'theSignature' and we're done with the handler.
However, while we now have a signature ready to go, even though iTunes isn't playing, we still haven't told Entourage to actually change the signature. Well, we could just put that code into the buildSigWithoutiTunes(), but why duplicate code? As we saw earlier, we already have that code built and running, why not use it? After all, code reuse is 'a good thing'. So that's what we do. If we review two of the last lines in the main body of the program:
set theNewSig to buildSigWithoutiTunes(theNewSig) --call the handler that builds a
sig without iTunes running
buildSig(theNewSig) --update the sent sig to some rangom sig created by the previous line
We see that the first line is what calls the buildSigWithoutiTunes(), and sets 'theNewSig' to the returned value. The next line calls our previously discussed buildSig() handler, and passes it 'theNewSig'. That handler is what creates the new sig in iTunes. So we don't have to re-enter the same code. It's a nice way to keep your code listings shorter. The next line closes up the if statement that checked to see if iTunes was even running, and that's the end of the main program, and we're done. You'll want to save this as a compiled script, not as an application. You can save it anywhere, but I prefer to save it in ~/Documents/Microsoft User Data/Entourage Script Menu Items/, so I can run it manually from the Entourage Script menu if need be.
Setting Up Entourage
So now we have a script, how do we get Entourage to use this bit of script we have created? Well, first you'll want to create a schedule so it can run regularly. I've been running it every minute for about five months now without a problem. The schedule setup is pretty simple, as we can see below:
Setup for iTunes Script Schedule
You give it a name, set it to be a repeating schedule, and set the action to be 'Run AppleScript' and point it at the 'sigFromiTunes' script. Make sure the 'Enabled' checkbox is checked, click 'OK' and your schedule is set. The next step is to attach the signature that this schedule is going to be creating to an account. I chose my mac.com account, as I mostly use that for personal email. The setup for that is pretty easy as well. This account happens to be an IMAP account, but the setup for POP accounts is the same. Go to the 'Tools' menu, and select 'Accounts'. In the list of accounts, double-click the account you want this signature to attach to, and select the Options tab. Then in the signature pull down, pick the appropriate entry for the iTunes Signature, normally 'iTunesSig'. My setup is seen below:
Account setup for iTunesSig
Once that's done, click 'OK', and you're all set to wow your friends with your vast collection of legal MP3s.
Conclusion
Well, it's not a terribly serious or network-related use for AppleScript, but then again, you are supposed to have some fun with your Mac every once in a while. Otherwise, you may as well use Windows. Also, I have done some testing, and this should work on iTunes 3 as well as 2.X, but if it doesn't, by all means, feel free to fix it. Finally, here are some URLs that can help you with this, or any other AppleScripting projects you may have:
John Welch <jwelch@mit.edu> is a Consultant with MIT IS, and the Chief Know-It-All for TackyShirt. He has over fifteen years of experience at making computers work. John specializes in figuring out ways in which to make the Mac do what nobody thinks it can, showing that the Mac is the superior administrative platform, and teaching others how to use it in interesting, if sometimes frightening ways. He also does things that don't involve computertry on occasion, or at least that's the rumor.