Friday, January 2, 2015

Javascript for Automation of Messages In Yosemite

I have just spent a few hours trying to do something that should be very simple and was not, so I thought I would record my lessons learned in a post. With the release of OS X, Yosemite, Javascript is supposed to be a viable alternative scripting language for controlling applications. I have been dealing with Applescript for many years and I would never describe our relationship as a good one. I look at Applescript as about as useful at solving problems as Siri. Sure you can talk to it in plain english but it never seems to understand what you are trying to say. Now Javascript, I know very well and can get it to do just about anything so this seemed like great new alternative for me.

I had high hopes for this new integration so I set myself a simple task. Send a text message via the Messages app to an existing contact in my buddies list. Sounds reasonable, right? I had a lot to learn.

In the Script Editor application, all you have to do to send a message in applescript is:

and hit the play button. Keep in mind that over time, this syntax may change so if in the future, this script does not work for you, you will have to do some research on how Messages may have changed.

Now how to translate this simple command to JavaScript. The first step is to establish the ability to log things to a console since there is no debugger in Script Editor (for some, unknown reason).

According to the JavaScript for Automation Release Notes I should be able to access the Messages application like this.

So above you can see, I got an application object but it has no JavaScript properties or functions that I can discover. I will have to resort to the Library in the Window>Library>Messages menu to find out what I can do with this object. Here I see this description.

Which says I should have buddies and services properties. I want to send a message to an existing buddy over an existing service. I have multiple services since I use Messages as my primary chat tool. Lets see what services I have.

I have never seen an ArraySpecifier before but I try to treat it like a JavaScript Array and it works. Looks like is an indexed array of six elements. I can't enumerate the properties of any of the service objects because they are each ObjectSpecifier classes they don't have any properties or functions either. With some digging I discovered that ObjectSpecifier and ArraySpecifier are essentially classes that reference objects within the application. They are not the objects themselves and act as a proxy between you and the object you are trying to manipulate. They do not expose any information about what the object they represent can do, however, so you are dependent on the Library/Dictionary document the application exposes to know what you can do with them. Below is the entry describing a Service.

Services have a property called name which should help me tell one from another. Lets list them.

This was disappointing. I was expecting to see the names of the services but all I got was another ObjectSpecifier. After a little more digging I discovered that if you call the property you want to read as a function ( .name() ), you will get it value instead of its ObjectSpecifier. Now my list looks like this.

I can see that I have lots of services to choose from to send a message. I now know that there is an SMS service I can use. It turns out there is an easier way to get to it as well. The JavaScript for Automation Release Notes document says that ArraySpecifier supports a .byName() function that will use the name() property instead of the array index to search an array for a contained object. Using this we can now get to the SMS service like this.

Now lets try to find out what buddies I have listed under the SMS service. From what I can tell, it is drawing from the buddies it finds in your address book who have mobile numbers listed. I won't show the output (since they are my personal contacts) but here is the script that lists existing buddies.

Now you have a listing of all your buddies that you can reach via SMS in messenger. The property .handle() instead of .name() can be used to display the actual mobile number or account name used on the service. This will be required if the same name appears more than once in the list. I have shortened the script by using the .whose() function which is also documented in the JavaScript for Automation Release Notes to search the Service collection for an object whose .handle() property is the phone number I want to send a text message to. This allows me to eliminate duplicate .name() objects in the list. Note that .whose() returns a ArraySpecifier so we need to take the first element.

Now, choosing one buddy, we actually will send a message. Here the dictionary entry from Messages is just not very easy to understand. It was not initially obvious to me that I had to call messages.send() with the parameters above but once you see how to read the dictionary, future calls for any app are easy to figure out. Here is the dictionary entry for send().

So what is this trying to tell us? If you have ever worked with objective C you will know that the first argument of a function is actually named after the function itself. After that all the other arguments are named arguments. This translates into JavaScript as  .function(,{"secondargument":"value2","thirdArgument":"value3"}) or in this case .send("Text to send",{to:myBuddy}). That is how they model multiple, named parameters when making an Objective C call from javascript. This is what the entry above is trying to say. Note the absence of the : next to send above. This indicates the first parameter (The one outside the {}) is the message text.

So, where did this all start? I was trying to implement this ....

in JavaScript. Right now I am close but notice, I did not have to deal with any buddy list in the AppleScript command above. It just seemed to know to search the .handle() property to find the buddy. How can we simplify this even further based on what was learned so far? I managed to compact it down to a one line command that translates into the AppleScript shown above. Here it is...

It would have been longer if I actually referenced the SMS service. Here I search all buddy handles with a matching phone number and the one I got back already pointed at the SMS service. It would have been longer if I limited it to the SMS service buddies.

After this journey through using JavaScript instead of AppleScript to do a simple task, what did I learn?

  • Its very easy to make Script Editor lock up with a spinning beach ball when working in JavaScript. Just ask it to run a function on an ObjectSpecifier that does not exists. After that I just had to force quit it to run any more scripts. This seemed broken to me.
  • Even though AppleScript is not intuitive to use, it does create compact, readable code in this case when it finally works, compared to JavaScript. 
  • The JavaScript implementation of AppleScripting does not go one bit out of its way to appeal to experienced JavaScript users. Nothing you already know will really help you. Objective C knowledge turned out to be more helpful. 

I did not want what I went through tonight to go to waste so I hope writing down my observations and the steps I went through to make them can help other people out with whatever they might try and do with JavaScripting OS X applications. There was not much to be found on this topic when I was searching for answers and hopefully, this article will save you some time. If you find a better way to implement this simple command, please post a comment to let me know.