Some of you may realize I’m a bit of an odd personality: when I get an idea, I don’t want to let it go until I at least prove it’s possible … but then I have a tendency to abandon things or leave them for others to finish. Having said all that: here’s my latest one-off script module. I’m probably done playing with it, and it’s definitely not a finished, release-quality project … but it works, it does something neat, and maybe you could learn something from it.
Dave Winer was blogging about wanting a web-based command-line app for twitter, and although I’m not a big fan of web apps, it did make me thing about how it would be interesting to have a command-line twitter application…
So, I grabbed a TwitterLib.dll from Witty, which in turn depends on TweetSharp … stuffed all of those into a new folder “Twitter” inside my Documents\WindowsPowerShell\Modules, and started experimenting in the console:
Incidentally, I’m using Get-Constructor (it’s on PoshCode) to enumerate the constructors, and the output of that GetFriendsTimeline method was really verbose. Way more information than I wanted, but, it did have the information I needed.
Now, what I wanted was to have the most recent tweets show up as part of my prompt, but I couldn’t be waiting around while it fetched it every time, so the next thing I did was write a little Start-Job script to run that stuff in a background thread, where I could just Receive-Job to get the data when I was ready for it. I also had to implement something to make sure I wasn’t going to get the same tweets over and over again (multiple calls to GetFriendsTimeline() return the most recent 20 tweets or so, without regard for whether you’ve seen them or not):
With that done, I can easily fetch all the tweets since the last time I fetched them by just calling Receive-Job Twitter. The one thing you’ll notice in that is that I had to add a Hash in the Format-Table to get the “ScreenName” property to equal the users actual ScreenName. The reason for that is that the TwitterLib.TwitterUser class isn’t properly serializable — so when I tried doing the Format-Table stuff on the receiving end, I was getting “TwitterLib.TwitterUser” as the value for the User property when I did Receive-Job later. To fix it, I extracted the information I wanted into a string property while the class was still in the “remote” runspace —thus it would serialize easier — at first I did it just the way it’s written there, but eventually I realized I should still return the actual object, so I changed it to use Add-Member.
In the end, the module I’ll release is somewhat more complete: it includes a few functions for tweeting and replying and following and even unfollowing, and I wrote a Format file to hide the extra data, and added my own URL un-shortener to resolve long URLs for display. If I was going to use this full time, I would need to get some support for creating a search that would be permanent too (ie: so I could pull in anything “public” about PowerShell — I think I’d probably try using the new Bing twitter interface for that.
If anyone wants to play with it, I’ve put it up here for download [new: 11/9/2009], and you’re welcome to use it however you like. I actually tweaked the source code to the Witty TwittlerLib a bit (I’ll make that available shortly), adding methods for removing friends, and for getting a user by their username instead of by their ID. I also happened to notice, while I was mucking around in the source, there’s already support in TwitterLib and TweetSharp for several cool things:
Get-ChildItem (aka: dir or ls), so it’s trivial to implement).Ok, I polished this up a little more while I was using it, and ended up with a pretty nice client — it uses Growl for Windows to pop up notices (if it’s available), and resolves shortened urls, and it now caches better, and has functions for searching/filtering that cache, and opening links from posts, etc. That is … it’s basically usable as a client now
. The download includes those two dependent modules, but not Growl for Windows.
I also renamed it, after I finally remembered where I had heard PowerTwitter before. I’ve registered “PoshTweet” with twitter as an app name to make sure it was unique, so maybe I’ll add OAuth support just so it can show up property and advertise itself.
So a lot of people seem to be taking the latest missteps by Twitter’s management (and the accompanying admission of bad design) as an opportunity to try out some alternatives. Many of them seem to be coming over to FriendFeed (which has been better than Twitter for a long time, but nevermind that) ... so I thought I’d update and release a PowerShell 2.0 script I wrote to create imaginary friends out of your friends that stay on Twitter.
The first part of it is a WatiN script (that automates your browser) called New-ImaginaryFriend which takes three parameters: a name for the imaginary friend, a url for an avatar for the friend, and a HashTable… Of course, we sort-of cheat by using the HashTable … it’s basically a bunch of key-value pairs of remote services and user names. You can use it to add twitter ID’s like twitter="jsnover" or blogs like blog="http://HuddledMasses.org/" etc. You can even add multiple sources (eg: twitter + diigo, two blogs, etc) to a single new imaginary friend 8)...
This script is done using WatiN because the FriendFeed API doesn’t support creating imaginary friends yet, and as a result it’s slow, and requires IE (and doesn’t seem to work very well with IE8 — at least, I couldn’t get it to set the avatars using IE 8 on Windows 7, so I commented out the avatar part of the next-to-last line).
The other part of the script is a pair of functions: the first is Get-FriendFeedFriends which retrieves profile information for all your friends in a slick format that includes all their services and such … you may find other uses for this later
, the second is Get-TwitterFriends … Both have an -Exclude parameter so you can pass it a list of people to ignore.
When you put these three functions together, you can just import the FriendFeed module, and start creating friends (don’t forget this version of the scripts only works with IE6 or IE7 for the purpose of avatars, as WatiN can’t seem to set the file upload value in IE8 yet).
You can download all of the required modules at once (7z), or grab the latest versions of them from PoshCode: FriendFeed, HttpRest, and WatiN … but if you do that, you’ll still need to get the binaries separately :-/
Well, over the weekend I stole a few moments from thinking about PoshCode 2.0 to think about PoshCode 1.0 … and I added two things:
I’ve replaced the misused “comment” box on the website with a full GetSatisfaction.com widget, so you can get your voice heard and get feedback (in the past it’s been impossible for me to respond to comments).
PoshCode now posts new contributions to twitter on it’s own PoshCode account. Feel free to follow along. Of course, the information on there is severely truncated, by comparison with the PoshCode RSS feed … and only includes the link to the website, whereas the RSS feed also includes direct download links … but I figured some of you would appreciate it anyway, and I aim to please.
Of course, now in PoshCode 2 I’ll have to make sure we have users enter their twitter id’s in the profile page so we can be sure to cite you properly. Oh what a tangled web we weave…
There are a few programming projects that just have to be re-written in each and every programming language. You might think it’s a waste of time, but because they’re used primarily by programmers and geeks, and because using them usually involves heavily modifying them, everyone wants one that was written in the language (s)he’s most familiar with, and writing them can be a good learning experience. One such project is the IRC bot.
I wrote a bot in PowerShell awhile back that we called PowerBot … the script using XMPP (Jabber), and could connect to IRC through a gateway. Although it could do the thing a bot should be able to do (join channels, respond to messages, etc), and had bonus features like announcing new posts from PoshCode.org, and following channel members on Twitter … it was technically a Jabber bot, not an IRC bot (and thus, a little slow to respond on IRC).
In any case, I’ve created a very simple framework for people to write PowerShell IRC decided to rectify that now by publishing a new bot based on the SmartIrc4Net library. Of course, it’s not a “pure” PowerShell bot (since I’m using an IRC library), but this is .Net after all, and we don’t really believe in rewriting things that already work just fine. SmartIrc4net is a very nice bit of work, and it’s a single dll you just have to plop in a folder with the script, and it’s LGPL (2.1) so you can reuse it at will with very few restrictions.
The full script is available on PoshCode but I wanted to go through and explain a few pieces of it, so I’ll break it up in pieces here. Incidentally, if you run this command on that version of the script, you’ll see there are exactly 39 lines of code, including loading the irc library, and the initial Hello World command.
The first section of the script loads the assembly and contains the Start-PowerBot script. I’ve wrapped things onto multiple lines here to fit in my blog a bit better and for readability:
The most important thing to notice here is that the SmartIrc4Net library is event driven, so you just have to Add scriptblock event handlers to things, and it will call them when something happens. You can see I’ve handled the error condition directly in the scriptblock, but for the other handlers I’ve called out functions which I will specify later.
In this simple bot I’ve only handled errors, private messages (a QueryMessage is a message sent directly to the bot, instead of to the channel), and channel messages. The “channel” is where you all hang out and chat — you young’uns may know this as a “chat room” ... but on IRC we still like to pretend we’re on HAM radio, so we have channels and go by weird aliases like “Gaurhoth” and “Gnopeg” and “SmellyHippy” and consider it impolite to ask what people’s real names are (we’ll probably kick-ban you if you ask “A/S/L?”).
You do NOT need a password for the $irc.login call, but if you have registered your bot’s nick (which I highly recommend if your IRC network supports it), then you should pass it’s password to the function. Finally, the Resume-Powerbot call at the end is a call to our simplest function. This is essentially the “message loop” of our program:
Resume-PowerBot is the simplest possible loop: it will exit if you press any key while the PowerShell window has focus. You might want to beef up the loop, and add the ability to type while the script is running (see my demo script). Putting that together with this would let you implement a simple interactive PowerShell IRC client … which is scriptable to the Nth degree.
When I call ListenOnce($false) I pass the optional boolean parameter $false to specify that I don’t want ListenOnce to block — otherwise, ListenOnce() will block until it actually receives a message: it’s basically like the difference between writing if($Host.UI.RawUI.KeyAvailable){$Host.UI.RawUI.ReadKey()} and just writing $Host.UI.RawUI.ReadKey() …
Stop-PowerBot actually sends a quite message and then disconnects from IRC, and if you call it, you’ll need to call Start-PowerBot before you can actually use the $irc variable again…
Event handlers in PowerShell are really very frustrating. Instead of just passing the arguments to the event handler, thus allowing your event handler to match “any” event signature, PowerShell takes exactly two arguments to it’s event handlers: the first is the $this parameter which represents what is triggering the event, and the second is the $_ parameter which represents the EventArgs. In any case, although that is sometimes limiting when developer use non-standard events, it does represent the usual CLR-compliant event signature, and luckily, most of the events in SmartIrc4net match it.
The two handlers I’ve written in this demo handle private (Query) messages and public channel messages. They are virtually identical, except that they send the return message differently
Finally, the actual commands for this bot are stored as a hash. You “register” a command by adding a key to the hash, where the key is a single word that matches the hash (it has to be the first word in the message) and the value is a scriptblock that will be executed. Generally, you just need to return a string (or an object) that you want to reply with, but you can, obviously, take any action you want.
Unlike the event handlers, the powerbot commands receive parameters: the first is a convenience parameter: it is the rest of the message that came after the keyword. The second parameter is the full “Data” member from SmartIrc4Net, and includes all the text that was in the message. Although the first parameter is a little redundant, most commands only need to handle the first parameter, so I’m keeping it there.