SS The following is a new version of picoIRC 0.2, a tiny IRC client that adds support for private query, nick colorization using an hash function, the /join, /nick, /names, /msg, /quit commands, auto resolution of nick collisions, colorized user list on join. Not as short as the first versions we wrote, but considering the feature set this script continues to show that Tcl is great to get a lot with little work.
package require Tk set ::registered 0 set ::joined 0 set ::server irc.freenode.org set ::chan #tcl set ::me $tcl_platform(user) set ::meseq 0 ;# sequencial number to add to the NICK if the previous was busy set ::names {} text .t -height 30 -wrap word -font {Arial 9} -background white .t tag config bold -font [linsert [.t cget -font] end bold] .t tag config italic -font [linsert [.t cget -font] end italic] set ::colors {red blue darkgreen black darkcyan darkmagenta} foreach color $::colors { .t tag config $color -foreground $color } entry .cmd -background white pack .cmd -side bottom -fill x pack .t -fill both -expand 1 bind .cmd <Return> post focus .cmd proc nickcolor nick { binary scan $nick c* v set hash 4817 set op + foreach x $v { set hash [expr "$hash $op $x"] set op [if {$op eq {+}} {concat *} {concat +}] } set hash [expr {$hash%[llength $::colors]}] lindex $::colors $hash } proc shownames {} { .t insert end "\nNames:\n" bold set i 0 foreach n $::names { if {$i == 0} { .t insert end [string repeat " " 8] } .t insert end "$n " [nickcolor $n] if {[incr i] eq 5} { .t insert end "\n" set i 0 } } if {$i} {.t insert end "\n\n"} .t yview end } proc recv {} { gets $::fd line puts $line # handle PING messages from server (lpz 2012 06 18) if {[lindex [split $line] 0] eq "PING"} { send "PONG [info hostname] [lindex [split $line] 1]" return } if {[regexp {:([^!]*)![^ ].* +PRIVMSG ([^ :]+) +:(.*)} $line -> \ nick target msg]} { set tag "" if {$nick eq "ijchain"} {regexp {<([^>]+)>(.+)} $msg -> nick msg} if [regexp {\001ACTION(.+)\001} $msg -> msg] { set msg "$nick $msg" set nick "*" set tag {bold darkgreen} set nicktag bold } else { set nicktag [nickcolor $nick] } if {[string index $target 0] ne {#}} { set nick "=== $nick ===" lappend nicktag bold } .t insert end $nick\t $nicktag $msg\n $tag } elseif {[regexp {^:([^ ]+) +([^ ]+) +([^ ]+) +(.*)} $line -> \ server code target rest]} \ { switch -- $code { 001 { set ::registered 1 } 433 { set seqlen [string length [incr ::meseq]] set ::me [string range $::me 0 [expr 8-$seqlen]]$::meseq send "NICK $::me" } 353 { if {[regexp {[^:]*:(.*)} $rest -> nameslist]} { foreach name $nameslist { lappend ::names $name } } } 366 { shownames set ::names {} } } .t insert end $line\n italic } else { .t insert end $line\n italic } .t yview end } proc usererr msg { .t insert end "--- $msg\n" {bold red} .t yview end } proc post {} { set msg [.cmd get] .cmd delete 0 end if [regexp {^/([^ ]+) *(.*)} $msg -> cmd msg] { switch -- $cmd { me {set msg "\001ACTION $msg\001"} nick {send "NICK $msg"; set ::me $msg} quit {send "QUIT $msg"; exit} names {send "NAMES $::chan"} quote {send $msg} join { send "PART $::chan" send "JOIN $msg" set ::chan $msg } msg { if {[regexp {([^ ]+) +(.*)} $msg -> target querymsg]} { send "PRIVMSG $target :$msg" set tags [list [nickcolor $target] bold] set target ">>> $target <<<" .t insert end $target\t $tags $querymsg\n {black bold} .t yview end } } default {usererr "unknown command /$cmd"} } if {$cmd ne {me} || $cmd eq {msg}} return } if [regexp {^/me (.+)} $msg -> action] {set msg "\001ACTION $action\001"} foreach line [split $msg \n] {send "PRIVMSG $::chan :$line"} set tag "" if [regexp {\001ACTION(.+)\001} $msg -> msg] {set tag italic} .t insert end $::me\t {bold blue} $msg\n [list blue $tag] .t yview end } proc send str { puts $::fd $str flush $::fd } proc cron {} { if {!$::joined && $::registered} { send "JOIN $::chan" set ::joined 1 } after 2000 cron } set ::fd [socket $::server 6667] send "NICK $::me" send "USER $::me 0 * :PicoIRC user" send "JOIN $::chan" fileevent $::fd readable recv bind . <Escape> {exec wish $argv0 &; exit} cron