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.


OvernightCoder said...

William, thank you for your detailed article, I thought it was very interesting. I spent a few minutes trying to simplify the code you were working on and ended up with this:

with (Application("Messages")) { send("Hey", {to: services["SMS"].buddies["+15555555555"]}) }

I tried it with my own phone number, and it worked for me.

Shannon Love said...

You're not to see any benefit from using Javascript for Automation to do something you can already do in Applescript.

Applescript was never supposed to be a "language" it was supposed to be transport layer between data models for apps. It's staggeringly hard to do very basic things in Applescript that are trivial in literally every other language in existence.

I spend a lot of time using ApplescriptObjc or whatever they're calling it this week because I need the Applescript to get data out of other apps but I need Objective-c to do anything other that shove it around from app to have.

The real advantage to bringing Javascript to OSA is that Javascript provides very basic things that Applescript lacks e.g. Arrays, real string manipulation, regex, in fact almost everything a real language needs.

The hard parts of Applescript, combining two or more desperate data models and command sets written by two or more developers with only lip service given to standards, and nearly hallucinatory documentation, is never going to go away.

But just having basic collections and string functions will make Applescript ten times as useful as it is today.

That said, I doubt Apple will really put much effort in it. I think they created the technology for UI automation testing and then just released it into the wild.

Lot of grunt ahead for the community.

Joe Root said...

The data you have posted is extremely helpful. The locales you have alluded was great. A debt of gratitude is in order for sharing... 123eworld bulk sms pune

Karthi Swetha said...

collection of screenshots and refer images are guides with excellent informative that also you explaining clearly. but i need a sample program why javascripts are using here

Java J2ee Training in Chennai

Rosy Jack said...

Pretty article! I found some useful information in your blog, it was awesome to read, thanks for sharing this great content to my vision, keep sharing..
Digital Marketing SMS
Digital Marketing Text
Digital Mobile Marketing
Text Whitelabel Solutions
sms Whitelabel Solutions

louis philip said...

This article is very much helpful and i hope this will be an useful information for the needed one. Keep on updating these kinds of informative things...
Fitness SMS
Fitness Text
Salon SMS
Salon Text
Investor Relation SMS
Investor Relation Text

louis philip said...

I wondered upon your blog and wanted to say that I have really enjoyed reading your blog posts. Any way I’ll be subscribing to your feed and I hope you post again soon.

Fitness SMS
Fitness Text
Salon SMS
Salon Text
Investor Relation SMS
Investor Relation Text

louis philip said...

This article is very much helpful and i hope this will be an useful information for the needed one. Keep on updating these kinds of informative things...
Texting API
Text message marketing
Digital Mobile Marketing
Sms marketing