Adventures in Eggdrop TCL Scripting - Part 2

This is the second part of my "Adventures in Eggdrop TCL Scripting" posts. In the first post I wrote down the solutions to some simple TCL specific issues I had. In this post I will explain how I solved some eggdrop related issues I faced.

Eggdrop IRC interaction
To make you eggdrop bot useful you want to be able to give commands that eggdrop will catch and process. This is done by using eggdrop binds like this:
bind msg - "start" start;

This will bind run the start method every time someone messaged the Eggdrop bot directly typing: start

Sometimes you want the bot to listen for commands in the channel where the Eggdrop bot is active. This is done with the following bind command:
bind pub - "!start" start;

The above example is a bit dumb but notice how it says "bind pub" instead of "bind msg". This bind will listen in all channels where the bot is active and run the start method whenever someone writes !start in the channel.

When you've issued a command to your Eggdrop bot, you would most likely like it to give you some sort of response. If you just want to respond to a specific channel where it resides you could do the following:
channel "#somechannel"
putserv "PRIVMSG $channel : Response String";

But sometimes you don't want it to respond to a channel but to you in a private message. To do so you would do something very similar like this:
nick "" putserv "PRIVMSG $nick : Response String";

Notice how it's almost identical to the above, but instead of a channel name it uses an IRC nick.

Eggdrop Timers
After building simple request/response kind of functionality it was time to try a new thing. We wanted some functionality to announce once new things happened in our guild. That required Eggdrop to periodically check for news and then announce those news.

For that purpose I utilized Eggdrop's timer functionality.
proc run_periodically {} {
  #DO SOME CODE HERE

  timer 10 run_periodically;
  return 1;
}

The above code will run some code (checking for news and output to channel) and the call the timer functionality. The above example will then wait 10 minutes and execute the same function again.

There are a few caveats to this though. You have to be careful how you call the run_periodically method the first time. If you just do it on load time of the script it will get called if you rehash eggdrop WITHOUT cancelling the previous timers. For this purpose I created some start and stop methods to control the execution of the periodically needed code.
proc hasTimers {} {
  set timerList [timers];
  return [llength $timerList];
}

proc start {nick uhost handle text} {
  if {[hasTimers] == 0} {
    #No current timer is active, meaning its safe to start running periodically
    run_periodically
  } else {
    #Do nothing as a timer is already active, meaning it is already running periodically
  }
}

proc stop {nick uhost handle text} {
  set timerList [timers];
  foreach timer $timerList {
    killtimer [lindex $timer 2];
  }
}

In the above code there is a few things to note. First is the hasTimers method which is a utility method for checking if we have any currently running timers. Notice the nick, uhost, handle and text parameters in the start and stop methods. Those parameters are needed by eggdrop as the start and stop methods are called using Eggdrop bind commands explained above. Also notice the killtimer command to kill a currently running timer. Having these methods allows the user to control whether or not the timed methods should be run or not.

Adventures in Eggdrop TCL Scripting - Part 1

During the last couple of weeks my spare time has gone into writing a few scripts for Eggdrop for use in my WoW guilds IRC channel. The idea was to add some functionality like looking up character information on the armory and announcing to channel when someone achieves something. We wanted some internal functionality for displaying upcoming events and signups for those events as well.

In this post I will write about some of the experiences I've made during development. I've never done any TCL scripting before so it was all new to me. So some of the solutions described below could most likely be done in better ways.

TCL HTTP Requests
The first thing I needed to figure out was how to create a HTTP request and do something sensible with the response. I ended up using the solution below:


proc armory::requestCharacter {characterName realm} {
  set url "http://someurl.com"

  if { [catch {set token [::http::geturl "$url" -timeout 6000]}] } {
    puts "Error making http request!"
  }

  if {([info exists token]) && ([::http::status $token] == "ok")} {
    set response [::http::data $token]
    return $response
  } else {
    return ""
  }


}


The code seems to be a fairly standard way of making a HTTP request and it works fine at least for my purpose. A slightly more advanced method could be to add the -useragent parameter using ::http::config

TCL URL Encoding
Once I started doing HTTP request I quickly found the need to create a method that could do URL encoding for me. I ended up using the below method:


proc urlencode {str} {
  set uStr [encoding convertto utf-8 $str]
  set chRE {[^-A-Za-z0-9._~\n]}; # Newline is special case!
  set replacement {%[format "%02X" [scan "\\\0" "%c"]]}
  return [string map {"\n" "%0A"} [subst [regsub -all $chRE $uStr $replacement]]]
}


The above code is a ripoff from something I found on the net somewhere (unfortunately I can't seem to find the reference now)


TCL Regular Expressions
As a big part of the script I built relies on data from either an HTML og JSON response I needed to fetch the relevant data from the responses. For that I rely heavily on TCL regex functionality. Basically I use a variation of the below to fetch lines from a result response. This fetches all lines with the below href tag in it.

set newsItems [regexp -all -inline {SEARCHSTRING.*?\.} $fileString]; 


The important thing to notice here is the regexp method. -all means get all hits and return them as a list of strings.

As I'm really poor at writing good regular expressions I opted to get some values using the good old fashioned way of getting substrings in the following manor:

set length [string length $search];
set begin [string first $search $response 0];
set end [string first "," $response $begin];
return [string range $response [expr int($begin+$length+2)] [expr int($end-1)]];


The idea here is to get the first occurence of the $search string. Then look for the next ',' sign after that occurence. I then do some calculations from those values to fetch the substring between those two pointers that I'm after. I know this seems stupid, but I thought I would mention it anyway. The dream is of course to someday rewrite this to use regex all the way, but that is something for another day.


So far I've only covered TCL specific things. None of this actually interacts with Eggdrop. Stay tuned for part two of this post where I will give some examples on how I used Eggdrops timer functionality to periodically execute some code. And how eggdrop interacts with IRC.