bad applescript shownotes

Bad AppleScript: Safari and curling URLs

I was listening to some podcasters I respect arguing about nuances in software development the other day, and thought to myself: I have no idea what these guys are talking about.

The pinnacle of my programming career was being asked to be one of the people on my high school’s team at some sort of programming contest. I only knew Applesoft BASIC, and half the problems required a more advanced language than that. Thus ended my life as a coder.

But thanks to Apple’s scripting language, AppleScript, I am just powerful enough to be dangerous. I have written many bad AppleScripts in my day. I will probably write many more.

What makes an AppleScript script bad? It's not that my scripts don't work—they do, generally, eventually. It's that they're hacked together by someone who is not a programmer by trade or even by hobby, who is not guided by anything other than his willingness to use any tool at his disposal to automate things on his computer to make his life easier.

And that‘s the thing: Even bad AppleScripts can save you a whole lot of time. I’ve saved myself (and in a few cases, my colleagues) countless hours of work by welding AppleScript, terminal commands, and regular expressions together in order to automate repetitive tasks.

Perhaps my bad AppleScripts can inspire you, too, to save some time and make your life a little bit easier. After all, if I can write some useful bad AppleScripts, so can you!

This particular example of bad AppleScript takes advantage of the Web to collect, process, and submit information. It uses the Safari browser as well as the built-in command-line tool curl. If you need to fiddle around in a browser to get or submit information, you might find something useful here—even if it's only inspiration.

The Affiliatizer

Building show notes for my podcast can be time-consuming, especially when I need to build an Amazon affiliate link, shorten it, and then add it to the podcast network’s content-management system. I spent a lot of time clicking around in my Web browser before realizing I could handle this chore with a bad AppleScript.

tell application "Safari"
	set longURL to URL of front document
	set theTitle to name of front document
end tell

set theASIN to (find text "/([A-Za-z0-9]{10})/" in longURL using "\1" with regexp and string result)

set theAmazonLink to ("http://www.amazon.com/gp/product/" & theASIN & "?tag=intertext")

set shellScript to ("curl --url \"http://api.bit.ly/shorten\" --data \"version=2.0.1&format=xml&longUrl=" & theAmazonLink & "&login=" & bitlyLogin & "&apiKey=" & bitlyAPI & "\" ")

set apiResult to (do shell script shellScript)

set shortURL to ((characters ((offset of "<shortUrl>" in apiResult) + 10) thru ((offset of "</shortUrl>" in apiResult) - 1) of apiResult) as string)

set encodedTitle to encode_text(theTitle, true, true)

set theposturl to ("http://5by5.tv/path?u=" & shortURL & "&t=" & encodedTitle)

tell application "Safari"
    open location theposturl
end tell

See what Safari sees

Let's break that down line by line.

tell application "Safari"
    set longURL to URL of front document
    set theTitle to name of front document
end tell

The script starts by finding out what webpage I’m currently browsing. In this part of the script, I’m grabbing two pieces of data—the page’s Web address and its title—and storing them in variables.

Match a regular expression

The webpage I’m on contains Amazon’s unique ID code, which is embedded in every product URL. I need it so I can build a new Amazon URL using that ID. To do this, I need to find a pattern: the Amazon ID is embedded in URLs as a ten-character code surrounded by slashes, like /043A046664/.

But here’s the tricky thing: As far as I can tell, AppleScript on its own doesn’t support basic grep-style regular expressions, which would make this sort of pattern-matching easy. (I use regular expressions all the time in BBEdit to save time while I’m editing text, but that’s another story.)

Fortunately, there’s a plug-in for AppleScript that adds support for grep-style regular expressions, Satimage osax. I downloaded and installed it, and then whipped up this code:

set theASIN to (find text "/([A-Za-z0-9]{10})/" in longURL using "\1" with regexp and string result)

This sets a new variable, theASIN, to the results of my regular-expression search for any combination of exactly ten letters and numbers, surrounded by slashes.

set theAmazonLink to ("http://www.amazon.com/gp/product/" & theASIN & "?tag=intertext")

This next bit of script builds my new Amazon URL, which is very simple: a link to the basic Amazon product URL, followed by the Amazon ID I just extracted, followed by an affiliate tag that indicates the link came from me.

Use Bitly to make a short URL

For various reasons, I need to shorten the Amazon URL before I can insert it in my podcast’s show notes. I use the Bitly service to do this. You can sign up for a free Bitly account that provides you with an API Key—a special code that’s linked to your account. You can use this key and Bitly’s Shorten Web service to turn a long URL into a short one.

It sounds technical, but it’s not so bad. Basically, if you send a URL you’d like to shorten to a special Bitly URL, Bitly will immediately reply with a shortened URL for you to use.

Doing stuff like this—sending an arbitrary URL somewhere and picking up the result—is best done by using AppleScript’s do shell script, because that lets you use the powerful command-line utility curl.

set shellScript to ("curl --url \"http://api.bit.ly/shorten\" --data \"version=2.0.1&format=xml&longUrl=" & theAmazonLink & "&login=" & bitlyLogin & "&apiKey=" & bitlyAPI & "\" ")

This is a whopper of a line, so let me break it down for you. We’re building the command that we’ll execute using do shell script, bit by bit. set shellScript to declares the shellScript variable we’re building. ("curl --url is the command that will hit a particular URL and return the results directly to us. Following —url is the URL we need to build: \"http://api.bit.ly/shorten\". That is the URL specified by Bitly’s Web services doc. The \" things are escaped quotation marks; since AppleScript uses quotation marks for other purposes (to build this string), you need to precede each quotation mark in the shell script with a backslash (\).

The --data in there indicates to curl that the rest of the stuff we’re passing is form data, which in URL terms gets passed as a series of pairs in the format [name]=[value], with additional pairs separated by the ampersand.

\"version=2.0.1&format=xml are the first two variables, and they’re specified in the Bitly API documentation. They provide the version of the API we’re using and the format we want to receive in response. You can see how form data is formatted, first by the name of what’s being submitted (version), then an equals sign, then the data itself (2.0.1), and finally an ampersand indicating that we’re moving on to the next pair.

&longUrl=" & theAmazonLink passes the variable longUrl to Bitly; this is where we need to insert our Amazon URL we want Bitly to shorten. & "&login=" & bitlyLogin & "&apiKey=" & bitlyAPI & "\" ") passes two more variables, my Bitly account name and API key. They’re stored as properties at the top of my script—get your own!

So that’s the end of that big hairy line.

set apiResult to (do shell script shellScript)

This line sets a variable, apiResult, to the contents of whatever is kicked back by curl when we run it using the do shell script command. Now we need to do something with this response.

set shortURL to ((characters ((offset of "<shortUrl>" in apiResult) + 10) thru ((offset of "</shortUrl>" in apiResult) - 1) of apiResult) as string)

Somewhere within the XML code kicked back by Bitly is the single thing we actually want: A shortened URL. It’s surrounded in <shorturl> tags. So I’m using a tried and true AppleScript method of parsing this result. Basically, this line is finding the characters between the last > symbol of <shorturl> and the first < symbol of </shorturl>. It works, shockingly.

Yes, I could’ve used the regular expression code I mentioned above, but the great thing about writing bad AppleScript is that you just keep stealing from previous scripts you’ve written. And that’s what I did here. Thinking about it now, I could have instead written this and probably gotten the same result: set shortURL to (find text "<shortUrl>(.+)</shorturl>" in longURL using "\1" with regexp and string result).

Inconsistency: Another seasoning that adds to the rich flavor of bad AppleScript!

Update: The new Bitly API

After writing this article, I noticed that Bitly had changed its API. The new one uses a special Oauth key instead of my user name and API key, and has slightly different parameters—as well as an option to return a plain-text result that's just the short form of the URL!

Here's the new code:

set shellScript to ("curl --url \"https://api-ssl.bitly.com/v3/shorten\" --data \"access_token=" & bitlyToken & "&format=txt&longUrl=" & theAmazonLink & "\" ")

Basically, the URL has changed, I'm now using a variable called bitlyToken instead of my API key. By requesting format=txt all I get back from Bitly is the short URL I want (meaning I don't have to use shenanigans to parse the XML code I used to get back from Bitly).

Submit the show notes

Next up is my podcast show notes. Similar to Bitly, I can submit a URL and title for any show link to the content-management system for the 5by5 podcast network via a URL. (Sorry—5by5 doesn’t have a public API document, so I’ll write this in generalities.)

The big problem here is, I need to pass the name of the webpage I’m saving via a URL, and webpage titles are full of characters like spaces and ampersands that aren’t allowed in URLs. So I have to use a character-encoding routine to convert the page title into something URL friendly:

set encodedTitle to encode_text(theTitle, true, true)

This calls a character-encoding subroutine that I got from the excellent Mac OS X Automation website and pasted into the bottom of my script. Put plain text in, get URL-encoded text out.

set theposturl to ("http://5by5.tv/path?u=" & shortURL & "&t=" & encodedTitle)

The format for 5by5’s link submission is simple: a specific path followed by u and a URL, and t and a title. I build this URL in the variable theposturl.

I don’t use curl to send it, mostly because of the way 5by5’s site works. To use this URL, I have to be logged in to 5by5’s system, and curl isn’t. But Safari is, so I’m going to open the URL I’ve just built in Safari.

tell application "Safari"
    open location theposturl
end tell

And that’s it. Now when we mention a book or movie or other product during the podcast, I visit its Amazon page, run my bad AppleScript, and that page’s URL is extracted, turned into an affiliate link, submitted to Bitly, and then sent to 5by5 for inclusion in the show notes. Before, that was a process that took numerous mouse clicks, lots of scrolling, and a bunch of selecting, copying, and pasting. Now all I need to do is run a single bad AppleScript.

What just happened?!

Now, unless you have a podcast on 5by5 and a lot of Amazon affiliate links, this specific application might not work for you. But maybe you need to submit some text via a URL form, or shorten a URL and put it on the clipboard. The good thing about bad AppleScript is that once you’ve assembled these scraps of code, you can re-use them anywhere they’ll be helpful, in any order, mixed in with other scraps of code.

But that’s a bad AppleScript story for another time.

Subscribe to the Create Newsletter

Comments