Updated 2014-06-01 20:08:34 by pooryorick

Richard Suchenwirth 2007-01-31: The code of picoIRC 0.2 is sweet and short, but a user asked for explanations on how and why things are done. So, here goes:

proc , args {#avoid clash with comment syntax highlighting on a wiki page}

, We will use the Tk GUI toolkit, so best tell it early on}
package require Tk

, Set some parameters (user may want to modify these)
set ::server irc.freenode.org
set ::chan   #tcl
set ::me     $tcl_platform(user)

, The GUI consists mostly of a text widget:
text .t -height 30 -wrap word -font {Arial 9}

, Some tags to make emphasized strings look nicer:
.t tag config bold   -font [linsert [.t cget -font] end bold]
.t tag config italic -font [linsert [.t cget -font] end italic]
.t tag config blue   -foreground blue

, Second UI element is an entry widget for typing messages
entry .cmd

, Brought to screen by pack (entry first, so it doesn't disappear on resizing)
pack .cmd -side bottom -fill x
pack .t -fill both -expand 1

, When <Return> is hit in the entry, the command post is executed
bind .cmd <Return> post

, But first for the receiving proc, which is called when the socket has new
, content:
proc recv {} {
    gets $::fd line ;#-- read a line from socket
    # handle PING messages from server
    if {[lindex [split $line] 0] eq {PING}} {
        send "PONG info hostname [lindex [split $line] 1]" 
    #-- Add some markup for messages and actions
    if {[regexp {:([^!]*)![^ ].* +PRIVMSG ([^ :]+) +:(.*)} $line -> \
        nick target msg]} {
        set tag {} 
        if {[regexp {\001ACTION(.+)\001} $msg -> msg]} {set tag italic}
        #-- suppress the names of two well-known bridge bots
        if {[in {azbridge ijchain} $nick]} {regexp {<([^>]+)>(.+)} $msg -> nick msg}
        #-- Display nick in bold, message as tagged above
       .t insert end $nick\t bold $msg\n $tag
    } else {.t insert end $line\n italic}
    #-- make sure the last part is visible
    .t yview end

, Just a little helper to check list membership
, In later versions of Tcl, the "in" operator of expr could be used instead
proc in {list element} {expr {[lsearch -exact $list $element]>=0}}

, This command is called on <Return>
proc post {} {
    set msg [.cmd get] ;#-- read what was typed in the entry
    #-- Lines beginning with /me are marked as actions:
    if [regexp {^/me (.+)} $msg -> action] {set msg "\001ACTION $action\001"}
    #-- Multiple lines (as e.g. from pasting) are sent one by one:
    foreach line [split $msg \n] {send "PRIVMSG $::chan :$line"}
    .cmd delete 0 end ;#-- clear the entry
    #-- the sent message is locally reported in the text, with possible markup
    set tag ""
    if [regexp {\001ACTION(.+)\001} $msg -> msg] {set tag italic}
    .t insert end $::me\t {bold blue} $msg\n [list blue $tag]
    #-- make sure the end of the text widget is visible
    .t yview end

, A very simple wrapper for sending a string, not forgetting to flush
proc send str {puts $::fd $str; flush $::fd}

, And now, the action begins with "logging in" to the IRC server
set ::fd [socket $::server 6667]
send "NICK $::me"
send "USER $::me 0 * :PicoIRC user"
send "JOIN $::chan"

, If the socket gets readable, recv will be called
fileevent $::fd readable recv

, This is just a convenient debugging helper for rapid restart after changes
bind . <Escape> {exec wish $argv0 &; exit}

How does this script deal with the ping-pong requirements of an IRC server? Is that handled by the socket itself?

RS: The script as above worked nicely from home (at work, IRC is blocked). I think I remember timeouts if I wasn't chatting for a while, but I'm not an IRC specialist anyway... Who knows better, please let us know!

MJ: The handling of the PING request has been added to the script.

Regarding that particular segment of code;
send "PONG [info hostname]"

can someone explain the info hostname command to me?

LV: Did you read the wiki page to which you referred? Basically, every computer has some sort of name - info hostname returns that name.

No I didn't read that page... posted the question without even looking at it afterwards. Many thanks to you all though! You've been very helpful!

Alternatively, some servers send ping requests with a unique key that must be returned.

a rawlog excerpt from xchat on irc.sorcery.net:
<< PING LAG2509966063 >> :nebula.sorcery.net
     PONG nebula.sorcery.net :LAG2509966063

In that case, the following replacement of the server code can suffice (if not, please edit):
if {[lindex [split $line] 0] eq {PING}} {
    send "PONG [lindex [split $line] 1]" 

MJ: The return from xchat seems to violate RFC1459, to quote:
Command:       PONG
Parameters:    <daemon> [<daemon2>]

PONG message is a reply to ping message. If parameter <daemon2> is given 
this message must be forwarded to given daemon. The <daemon> parameter 
is the name of the daemon who has responded to PING message and generated this message.

So the first parameter of pong should be the name of the daemon who has responded (e.g. the name of the client)

Therefore the way I read it, the correct reply should be:
if {[lindex [split $line] 0] eq {PING}} {
    send "PONG [info hostname] [lindex [split $line] 1]"