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 messages.services 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.

11 comments:

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

Unknown 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

sakthi said...

I found your blog while searching for the updates, I am happy to be here. Very useful content and also easily understandable providing.. Believe me I did wrote an post about tutorials for beginners with reference of your blog. 
best rpa training in bangalore
rpa training in bangalore
rpa course in bangalore
RPA training in bangalore
rpa training in chennai
rpa online training

Mounika said...

Well somehow I got to read lots of articles on your blog. It’s amazing how interesting it is for me to visit you very often.
Python Online certification training
python Training institute in Chennai
Python training institute in Bangalore

Anonymous said...

Great Post,really it was very helpful for us.
Thanks a lot for sharing!
I found this blog to be very useful!!
Python training in Bangalore

Keerthana said...

Concept involves in the contents are very beneficial, put the hands together for all your Effective works.To get More Beneficial Click on here...
python training in chennai | python training in annanagar | python training in omr | python training in porur | python training in tambaram | python training in velachery

radhika said...

Thanks for sharing like a wonderful blog’s learn more new information from your blog. Keep sharing the post like this…

AWS training in Chennai

AWS Online Training in Chennai

AWS training in Bangalore

AWS training in Hyderabad

AWS training in Coimbatore

AWS training

Techwriter said...

Very informative blog. I am very satisfied after reading your content. Keep it up.
DevOps Classes in Pune

iteducationcentre said...

Very valuable and useful article.
Java Classes in Nagpur