soeren says

More status updating goodness

August 6th, 2007

Regarding my script from two weeks ago that updates your status on instant messengers and web services simultaneously, a few amendments:

  1. As Zib points out in a comment, it is indeed possible to adjust the script for fewer services. For example, removing the tell application "Skype" block will leave out the Skype aspect, and only update Adium, Facebook and Twitter.
  2. It is also possible to integrate this with Growl, so you can get some feedback on when it’s done updating each service. Note that this isn’t necessarily a guarantee that this update actually succeeded; for instance, I at first had a bug where Growl would happily report having updated Facebook’s status, even when Facebook showed no change at all: this notification only refers to a finished attempt.
  3. And finally, I admittedly did something lazy by not properly fetching a post_form_id from Facebook, instead opting to hardcode it. That worked fine for me for a few days, but I eventually had to fix it on my end, and this may very well be the reason it never worked for Christopher. My revised version, as posted below, should remedy this (it has worked for me since).

So with all that said, here’s my current version of the script:

using terms from application "Quicksilver"
	on process text NewStatus
		tell application "GrowlHelperApp" to register as application "StatusUpdater" all notifications {"AdiumStatusUpdated", "SkypeStatusUpdated", "TwitterStatusUpdated", "FacebookStatusUpdated"} default notifications {"AdiumStatusUpdated", "SkypeStatusUpdated", "TwitterStatusUpdated", "FacebookStatusUpdated"}
		tell application "Adium" to set my status message to NewStatus
		tell application "GrowlHelperApp" to notify with name "AdiumStatusUpdated" title "Adium status updated" description NewStatus application name "StatusUpdater" icon of application "Adium"
		tell application "Skype" to send command "SET PROFILE MOOD_TEXT " & NewStatus script name "Quicksilver StatusUpdater"
		tell application "GrowlHelperApp" to notify with name "SkypeStatusUpdated" title "Skype status updated" description NewStatus application name "StatusUpdater" icon of application "Skype"
		tell application "Keychain Scripting"
			set twitter_key to first Internet key of current keychain whose server is "twitter.com"
			set twitter_login to quoted form of (account of twitter_key & ":" & password of twitter_key)
		end tell
		set twitter_status to quoted form of ("status=" & NewStatus)
		do shell script "curl --user " & twitter_login & " --data-binary " & twitter_status & " http://twitter.com/statuses/update.json"
		tell application "GrowlHelperApp" to notify with name "TwitterStatusUpdated" title "Twitter status updated" description NewStatus application name "StatusUpdater" icon of application "Twitterrific"
		tell application "Keychain Scripting"
			set facebook_key to first Internet key of current keychain whose server is "login.facebook.com"
			set facebook_username to account of facebook_key
			set facebook_password to password of facebook_key
		end tell
		set facebook_form_id to do shell script "curl -v -k -d email='" & facebook_username & "' -d pass='" & facebook_password & "' -d login=Login -L 'https://login.facebook.com/login.php?m&next=http%3A%2F%2Fm.facebook.com%2Fhome.php' -A 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.3) Gecko/20070309 Firefox/2.0.0.3' --cookie-jar /tmp/facebook-cookie.txt | tr '\\"' '
' | grep -A 2 post_form_id | tail -1"
		do shell script "curl -v -d post_form_id=" & facebook_form_id & " -d status='" & NewStatus & "' -d update='Update' -A 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.3) Gecko/20070309 Firefox/2.0.0.3' --cookie /tmp/facebook-cookie.txt 'http://m.facebook.com/home.php' -L"
		tell application "GrowlHelperApp" to notify with name "FacebookStatusUpdated" title "Facebook status updated" description NewStatus application name "StatusUpdater" icon of application "FMenu"
	end process text
end using terms from

If you don’t want Growl support (shouldn’t make much of a performance difference, however) or don’t have Growl installed, you’ll want to cut out each line starting with tell application "GrowlHelperApp", like so:

using terms from application "Quicksilver"
	on process text NewStatus
		tell application "Adium" to set my status message to NewStatus
		tell application "Skype" to send command "SET PROFILE MOOD_TEXT " & NewStatus script name "Quicksilver StatusUpdater"
		tell application "Keychain Scripting"
			set twitter_key to first Internet key of current keychain whose server is "twitter.com"
			set twitter_login to quoted form of (account of twitter_key & ":" & password of twitter_key)
		end tell
		set twitter_status to quoted form of ("status=" & NewStatus)
		do shell script "curl --user " & twitter_login & " --data-binary " & twitter_status & " http://twitter.com/statuses/update.json"
		tell application "Keychain Scripting"
			set facebook_key to first Internet key of current keychain whose server is "login.facebook.com"
			set facebook_username to account of facebook_key
			set facebook_password to password of facebook_key
		end tell
		set facebook_form_id to do shell script "curl -v -k -d email='" & facebook_username & "' -d pass='" & facebook_password & "' -d login=Login -L 'https://login.facebook.com/login.php?m&next=http%3A%2F%2Fm.facebook.com%2Fhome.php' -A 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.3) Gecko/20070309 Firefox/2.0.0.3' --cookie-jar /tmp/facebook-cookie.txt | tr '\\"' '
' | grep -A 2 post_form_id | tail -1"
		do shell script "curl -v -d post_form_id=" & facebook_form_id & " -d status='" & NewStatus & "' -d update='Update' -A 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.3) Gecko/20070309 Firefox/2.0.0.3' --cookie /tmp/facebook-cookie.txt 'http://m.facebook.com/home.php' -L"
	end process text
end using terms from

If you want Skype removed, you get rid of this line:

tell application "Skype" to send command "SET PROFILE MOOD_TEXT " & NewStatus script name "Quicksilver StatusUpdater"

(And the Growl one underneath no longer makes sense either, of course.)
In case of removing Twitter or Facebook, you’ll also want to remove the respective tell application "Keychain Scripting" preceding it, up until and including end tell.

Enjoy!

Posted in AppleScript, Mac, Programming, Web

Share | 16 Comments

Updating your Adium, Facebook, Skype and Twitter status all at once

July 23rd, 2007

Since Facebook puzzlingly doesn’t provide an API for this, you have to go through some hoops. My solution is based on the trick described over at nexdot.net and basically amounts to logging in to Facebook’s mobile site (which, unlike the regular one, allows updating the status through HTTP POST, rather than only through AJAX trickery), storing the cookie, then reading it and posting the status message.

The Twitter, Quicksilver and Adium aspects are based on this little script as well as this one. Finally, the Skype part is easy to add once you understand their API a little.

The resulting script looks like this:

using terms from application "Quicksilver"
	on process text NewStatus
		tell application "Adium"
			set my status message to NewStatus
		end tell
		tell application "Skype"
			send command "SET PROFILE MOOD_TEXT " & NewStatus script name "Quicksilver StatusUpdater"
		end tell
		tell application "Keychain Scripting"
			set twitter_key to first Internet key of current keychain whose server is "twitter.com"
			set twitter_login to quoted form of (account of twitter_key & ":" & password of twitter_key)
		end tell
		set twitter_status to quoted form of ("status=" & NewStatus)
		do shell script "curl --user " & twitter_login & " --data-binary " & twitter_status & " http://twitter.com/statuses/update.json"
		tell application "Keychain Scripting"
			set facebook_key to first Internet key of current keychain whose server is "login.facebook.com"
			set facebook_username to account of facebook_key
			set facebook_password to password of facebook_key
		end tell
		do shell script "curl -v -k -d email='" & facebook_username & "' -d pass='" & facebook_password & "' -d login=Login -L 'https://login.facebook.com/login.php?m&next=http%3A%2F%2Fm.facebook.com%2Fhome.php' -A 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.3) Gecko/20070309 Firefox/2.0.0.3' --cookie-jar /tmp/facebook-cookie.txt"
		do shell script "curl -v -d post_form_id=006f908f4d524763335c4613e2dadd42 -d status='" & NewStatus & "' -d update='Update' -A 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.3) Gecko/20070309 Firefox/2.0.0.3' --cookie /tmp/facebook-cookie.txt 'http://m.facebook.com/home.php' -L"
	end process text
end using terms from

For both Twitter and Facebook, I use Keychain to get the username and password, which means that there’ll be a bit of a delay the first time you use this (and you’ll have a few dialog boxes to confirm), but which also means the password isn’t lying around hardcoded and unencrypted (but it is transmitted in unencrypted form to twitter!).

To use this, open Script Editor, save it as, say, StatusUpdater.scpt in ~/Library/Application Support/Quicksilver/Actions (you may have to create that folder; it did not exist in my case) and relaunch Quicksilver. Then, hit your Quicksilver trigger (such as ctrl-space), type . (a period), enter your status, hit tab and choose the script (typically by typing something like s-t). As you hit enter, all four should update your statuses, and everyone will know what you’ve just eaten. Isn’t Internet life wonderful?

Posted in AppleScript, Mac, Programming, Web

Share | 5 Comments

Quicksilver + Twitter + iChat Status (1 update)

January 30th, 2007

A user of Twittereeze, Seth Dimbert, asked me if it would be possible to have its iChat syncing, but not Twitterrific as a Twitter client.

Specifically, he preferred to use Quicksilver instead, and pointed me to an AppleScript that already gets the Twitter part done.

Luckily, that script already has a variable (tweet) that contains the message we wish to set. So all we need is one line to add the iChat info:

using terms from application "Quicksilver"
  on process text tweet
    tell application "Keychain Scripting"
      set twitter_key to first Internet key of current keychain whose server is "twitter.com"
      set twitter_login to quoted form of (account of twitter_key & ":" & password of twitter_key)
    end tell
    set twitter_status to quoted form of ("status=" & tweet)
    set results to do shell script "curl --user " & twitter_login & " --data-binary " & twitter_status & " http://twitter.com/statuses/update.json"
    -- display dialog results

    tell application "iChat" to set status message to tweet

    return nothing
  end process text
end using terms from

That line beginning with tell application "iChat" is all that’s new.

There’s a minor nig with this; specifically, due to AppleScript limitations, Quicksilver might stall for a few seconds while sending the information to iChat. This is particularly jarring when you use a Quicksilver display that is right in the middle of the screen and always on top. :-) Fixing that, however, would probably amount to a far more complicated setup, and the above should be good enough for most purposes.

I don’t personally use Quicksilver so I can’t test it, but I’m told it works exactly as expected.

You can do the same with Skype and Adium as well, but not with MS Messenger (it has no external way I know of to change the status) nor Yahoo! Messenger (it does have a way, but it involves Services, and I don’t know whether or how it’s possible to interface with them inside AppleScript).

Update: He has since posted about it over at his blog.

Posted in AppleScript, Chuckellania, Internet, Programming, Twittereeze, Web

Share | 1 Comment

Twitting in the Morning Sun, part 2: Twittereeze 1.0

January 23rd, 2007

All that said, the release of Twitterrific intrigued me enough to figure out if this was for me.

Twitterific

Now, Twitterific is to Twitter what Flickr Uploadr is to Flickr, or what Pukka is to del.icio.us: a desktop client to a web application (or, perhaps more appropriately, a web service). This is great for me: I’m generally not too fond of the inferior user interface web applications can provide (no menu bar, very few advanced controls, very little integration with the operating system, a monolithic application – the browser – underneath, etc.) but do understand its perks (no installation, near-instant access). Having a web client as a fallback option while having a powerful desktop client as an alternative to one and the same service is a great combination, and one of the things I love the most about Flickr.

With Twitterific, this connection goes two ways: not only can you “upload”, i.e. send your tweets (thus changing the status message other Twitter users see as your latest); you can also see the latest tweets from others; either everyone, or only “friends” of yours. (Arguably, “friends” is a lot more loosely defined than is the case on Flickr, where you have a distinction between “contacts” and “friends”; Twitter’s friends are more akin to “contacts”.) As others change their status, you can choose to get notified through a window and/or a little alarm sound. This works through polling; the amount of data downloaded is lightweight enough that this is basically irrelevant; unlike with RSS feeds, there is little that speaks against contacting Twitter once every single minute – and indeed, Twitterrific offers this low interval.

It’s a little jarring to me, though, that this “notification window” is one and the same as the one you enter your own “tweets” in: while still very compact, it is needlessly complicated. It would make much more sense to use Growl in this regard. The relevant information is a picture (the user’s avatar), a headline (the user’s name), a few lines of text (the tweet) and, possibly, a link (to the user’s website). Link aside (which doesn’t exactly change anyway), this would fit greatly into the more typical Growl display styles such as “Bubbles”, “Smoke” or “Crystal”.

And indeed, someone has developed a little Ruby script that polls Twitter and then notifies Growl through its CLI growlnotify tool. One of Twitter’s own people, Alex, has enhanced this script.

Twitterrific uses the “HUD” style that Apple has been gradually introducing since Motion 1.0 (though they called it a “dashboard” back then, which was completely unrelated to 10.4 Tiger’s Dashboard); a palette-esque window with partial transparency, a black/dark grey chrome and completely custom GUI widgets. Far less obtrusive than a traditional palette, to the point where I hardly feel palette windows even have their merits any more. This notification window can be manually invoked (by clicking the icon in the menu bar’s status area), or, as said, be made to appear when new tweets have been received. The interface follows Twitter’s own maxime to be as simple as possible; you’d be hard-pressed to find something superfluous.

That also means, though, that some people may find it a little lacking, as did I.

Twittereeze

I’ve had some experience working with SIMBL before, on two separate projects, neither of which have been released thus far (and possibly never will). As Twitterrific doesn’t have a documented API or anything, doing something like this pretty much amounts to reverse-engineering, is bound to have its completely unexpected share of bugs, is even more likely to break completely in newer versions of the host app (Twitterrific in this case) and, most of all, is scorned upon by many developers. (In other words, don’t expect to get much sympathy on IRC channels like #macdev when you work on such a project.) It is also a great example of always completely misestimating the troubles you will run into.

My initial goal of Twittereeze was one single feature: as you post your status with Twitteriffic, have the status updated in your IM apps as well. iChat, Skype and Adium all have a simple AppleScript API to do this. Let’s say your status message is: “Zomg, PONIES!”; then the necessary AppleScript calls can all be done in one line each, like this:

tell application "iChat" to set status message to "Zomg, PONIES!"
tell application "Skype" to send command "SET PROFILE MOOD_TEXT Zomg, PONIES!"
tell application "Adium" to set my status message to "Zomg, PONIES!"

Of those, iChat evidently has the cleanest API, but the increased complexity in Adium’s and Skype’s cases is easily explained by their far more versatile abilities. Whatever the case, wrapping this into an AppleScript is trivial. Putting it into Objective-C code requires some escaping and wrapping; the NSAppleScript API helps here, but be aware of its limitations. Threads? Forget about it! It isn’t thread-safe, which normally means you’re not supposed to use one and the same object in multiple threads simultaneously (it wouldn’t be synchronized properly); in this particular case, for whatever reason, it means that you’re more likely to crash the app than to get any meaningful result, unless you do everything in the main thread.

As everyone knows, AppleScript is awfully slow. We can normally use pre-compiling to mitigate this, but not here: the status message, after all, changes each time; that’s the entire point. (We could, perhaps, compile a version without the status message itself, and pass it on as a parameter.) NSTask, while not thread-safe either, works just fine in its own thread. So I figured “hey, let’s use osascript and launch the script in its own process that way”. That would have been great, and wouldn’t even really have required threading at all. Problem:

NSTask converts both path and the strings in arguments to appropriate C-style strings (using fileSystemRepresentation) before passing them to the task via argv[]).

As you might guess, this means bye-bye to the full Unicode character set. Whereas Twitter nicely uses UTF-8, this would ‘downgrade’ our beloved tweek-turned-IM-status-message in lossy ways just to please the POSIX underpinnings; option-; never looked so ugly. So, while it nicely worked around AppleScript’s slowness, it was clearly a less than desirable option. (Not that it’s much of a surprise that abusing NSTask for this isn’t that great of an idea.)

Unfortunately, I have yet to find a better solution to this. When you experience “beachballing” in Twitterrific with Twittereeze while sending your tweet, this is why, and I’m aware of the problem. It should only take a few seconds, but if the target application (iChat, Adium or Skype) happens to be hanging for whatever reason, it can take up to three minutes, I believe (I think the timeout is one minute per script), though I have yet to experience such a massive hold-up. In fact, I have found this to be a very minor issue that barely distracts from the experience at all.

If you’re looking at the code, you may be curious why most of the methods are class methods, rather than instance ones (or why, for that matter, the category of NSApplication is currently named PostNibLoad, which is historic and ought to be changed). In the previous two SIMBL projects of mine I haven’t experienced this, and I’m still at a loss of why this is happening: not only can’t I create new instance methods in categories; I also can’t override (let alone swizzle) existing ones. At the same time, I can create/override/swizzle class methods just fine, so the category itself is clearly not at fault. My only explanation is that the bundle gets loaded too early, so its instance-based modifications don’t apply, but I can’t see any such problem anywhere on the web.

That meant I had to get a little creative with how I hooked into Twitterrific itself, but I think I managed to make the code just about readable and efficient enough to compensate this issue.

So the core functionality – keeping your Twitter tweets and your IM status messages in sync – was done within a day and a half.

More Features

I quickly realized, though, that there were other things that bugged me about Twitterrific. Not “bugged” in the sense of “gee, that’s stupid on their part”; I’m quite fond of the app, and I’m sure it works great as-is for most people. But I found myself frequently having to switch between keyboard and mouse (whereby I use “mouse” interchangeably with “trackpad”, of course) to use the user interface:

  1. When the notification window pops up having received tweets, it isn’t frontmost, so you can’t just hit the esc key to dismiss it, and while you can set it to auto-disappear after an interval, the minimum for that is ten seconds. That’s too long! My Growl messages fade out after four seconds, and even that is a bit long sometimes (it turns out it’s very hard to find the sweet spot on such a thing). Because Twittereeze can’t be made front-most through cmd-tab (due to its LSUIElement setting), you effectively have to use the mouse to manually bring the window to the front (and then press esc), at which point you might as well just click the close button.
  2. Likewise, you can’t open the window with the keyboard either; you have to click the icon in the menu bar’s status area. This is especially jarring when you just want to quickly send a tweet; you simply cannot type right away.
  3. You can either have the list of tweets selected and navigate through them, but then not type in a tweet. Or, you can have the text field selected and then not select a different previous tweet. You need the mouse to change the focus manually.

To solve all of the above, I had to learn how to set up global hot keys, which amounts to at least some Carbon code, because Cocoa simply doesn’t have an API for this (yet? ever?). The ever-resourceful, ever-awesome CocoaDev wiki has several pages on this matter: HotKeys, SystemWideKeyboardShortcut, GlobalHotKeys and, as I discovered a few days later, RegisterEventHotKey. These point to several pre-factored solutions; two simpler ones as well as two far more complete ones including their own GUI to allow users to set their own hotkey. I plan on eventually moving one to the latter; for the time being, though, I hardcoded the relevant hotkeys (call me lazy).

I also had to learn NSEvent for the non-global hot keys.

Here they are:

  1. command-F11 toggles the notification. That is, if it’s visible, it gets hidden. If it’s invisible, it gets shown, Twitterrific gets “activated” (becomes the frontmost app), the notification window becomes “main” and “key”, and the text field “first responder”. In Cocoa-speak, those are all terms, effectively, for one and the same thing: being in focus/frontmost. What this means for you is that, if the window isn’t visible, you can hit the shortcut and type your new tweet right away.
  2. command-shift-F11 is the same with a twist; if the window is visible and front-most and focused on the text field and so on, it does nothing (it does not close the window). If the window isn’t visible and/or the text field not focused, however, it makes sure to do this. So if you have the window open (say, because of a notification), and want to respond quickly, you can use this shortcut to bring the text field in focus, regardless of the situation. (Both these shortcuts ignore any other application’s requests. They simply work.)
  3. ctrl-up and ctrl-down are used to scroll the list of previous tweets of you and others. This is different from using just the arrow keys, because those wouldn’t work while the text field is in focus. Because of this, I’m always focusing the text field; i.e., if you use the mouse to select a different previous tweet to view it, the focus will change back to the text field afterwards. An unusual behavior, but in my experience very useful.
  4. As you would expect, return or enter sends your tweet, and esc closes the window.

Only the first two are global; the rest only applies while the app and window are frontmost.

This is pretty much all that Twittereeze does, as of 1.0. I’ve had it more or less done for a while, but decided to polish the code a little and post it on Google Code, so we have a more permanent download link, a central source repository, an issue tracker, a mailing list, and even a wiki. I’ve also pointed to it from MacUpdate (here) as well as VersionTracker (here).

Within mere hours, Alex contacted me on AIM to thank me for this work, as well as subscribe to my blog ;-) , which I dare say shows just how amazing social networking on the Internet has become: it’s not as if I had launched some massive advertising campaign for the project or anything.

Alex proceeded to request a feature, which I’ll hopefully be working on soon-ish.

One can probably tell I’m fairly ecstatic about all this, which may not make much sense for a bundle whose binary is a mere 48 KBs. However, I’ve always found that a large, yet carefully selected number of small pieces of software amounts to a far greater user experience than a small number of huge, monolithic apps. In the truest fashion of “eat your own dogfood”, I wrote Twittereeze to fulfill a need that I have had for a long time, regardless of whether any of Twittereeze’s other potential users will: to keep my publicly visible status in sync. Twitter provides an excellent platform for this, and all I did was use it to its fullest.

Posted in AppleScript, Cocoa / Objective-C, Internet, Mac, Programming, Projects, Software, Twittereeze, Web

Share | 3 Comments