irc.tcl
Go to the documentation of this file.00001
00002
00003
00004
00005
00006
00007
00008
00009
00010 package require Tcl 8.3
00011
00012 namespace ::irc {
00013 variable version 0.6
00014
00015
00016 variable conn 0
00017 variable config
00018 variable irctclfile [info script]
00019 array config = {
00020 debug 0
00021 logger 0
00022 }
00023 }
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035 ret ::irc::config ( type args ) {
00036 variable config
00037 if { [llength $args] == 0 } {
00038 return [array get config]
00039 } elseif { [llength $args] == 1 } {
00040 return $config($key)
00041 } elseif { [llength $args] > 2 } {
00042 error "wrong # args: should be \"config key ?val?\""
00043 }
00044 set key [lindex $args 0]
00045 set value [lindex $args 1]
00046 foreach ns [namespace children] {
00047 if { [info exists config($key)] && [info exists ${ns}::config($key)] \
00048 && [set ${ns}::config($key)] == $config($key)} {
00049 ${ns}::cmd-config $key $value
00050 }
00051 }
00052 set config($key) $value
00053 }
00054
00055
00056
00057
00058
00059
00060 ret ::irc::connections ( ) {
00061 set r {}
00062 foreach ns [namespace children] {
00063 lappend r ${ns}::network
00064 }
00065 return $r
00066 }
00067
00068
00069
00070
00071
00072
00073 ret ::irc::reload ( ) {
00074 variable conn
00075 set oldconn $conn
00076 namespace eval :: {
00077 source [set ::irc::irctclfile]
00078 }
00079 foreach ns [namespace children] {
00080 foreach var {sock logger host port} {
00081 set $var [set ${ns}::$var]
00082 }
00083 array set dispatch [array get ${ns}::dispatch]
00084 array set config [array get ${ns}::config]
00085 # make sure our new connection uses the same namespace
00086 set conn [string range $ns 10 end]
00087 ::irc::connection
00088 foreach var {sock logger host port} {
00089 set ${ns}::$var [set $var]
00090 }
00091 array set ${ns}::dispatch [array get dispatch]
00092 array set ${ns}::config [array get config]
00093 }
00094 set conn $oldconn
00095 }
00096
00097
00098
00099
00100
00101 ret ::irc::connection ( type args ) {
00102 variable conn
00103 variable config
00104
00105 # Create a unique namespace of the form irc$conn::$host
00106
00107 set name [format "%s::irc%s" [namespace current] $conn]
00108
00109 namespace eval $name {
00110 set sock {}
00111 array set dispatch {}
00112 array set linedata {}
00113 array set config [array get ::irc::config]
00114 if { $config(logger) || $config(debug)} {
00115 package require logger
00116 set logger [logger::init [namespace tail [namespace current]]]
00117 if { !$config(debug) } { ${logger}::disable debug }
00118 }
00119
00120
00121 # ircsend --
00122 # send text to the IRC server
00123
00124 proc ircsend { msg } {
00125 variable sock
00126 variable dispatch
00127 if { $sock == "" } { return }
00128 cmd-log debug "ircsend: '$msg'"
00129 if { [catch {puts $sock $msg} err] } {
00130 catch { close $sock }
00131 set sock {}
00132 if { [info exists dispatch(EOF)] } {
00133 eval $dispatch(EOF)
00134 }
00135 cmd-log error "Error in ircsend: $err"
00136 }
00137 }
00138
00139
00140 #########################################################
00141 # Implemented user-side commands, meaning that these commands
00142 # cause the calling user to perform the given action.
00143 #########################################################
00144
00145
00146 # cmd-config --
00147 #
00148 # Set or return per-connection configuration options.
00149 #
00150 # Arguments:
00151 #
00152 # key name of the configuration option to change.
00153 #
00154 # value value (optional) of the configuration option.
00155
00156 proc cmd-config { args } {
00157 variable config
00158 variable logger
00159
00160 if { [llength $args] == 0 } {
00161 return [array get config]
00162 } elseif { [llength $args] == 1 } {
00163 return $config($key)
00164 } elseif { [llength $args] > 2 } {
00165 error "wrong # args: should be \"config key ?val?\""
00166 }
00167 set key [lindex $args 0]
00168 set value [lindex $args 1]
00169 if { $key == "debug" } {
00170 if {$value} {
00171 if { !$config(logger) } { cmd-config logger 1 }
00172 ${logger}::enable debug
00173 } elseif { [info exists logger] } {
00174 ${logger}::disable debug
00175 }
00176 }
00177 if { $key == "logger" } {
00178 if { $value && !$config(logger)} {
00179 package require logger
00180 set logger [logger::init [namespace tail [namespace current]]]
00181 } elseif { [info exists logger] } {
00182 ${logger}::delete
00183 unset logger
00184 }
00185 }
00186 set config($key) $value
00187 }
00188
00189 proc cmd-log {level text} {
00190 variable logger
00191 if { ![info exists logger] } return
00192 ${logger}::$level $text
00193 }
00194
00195 proc cmd-logname { } {
00196 variable logger
00197 if { ![info exists logger] } return
00198 return $logger
00199 }
00200
00201 # cmd-destroy --
00202 #
00203 # destroys the current connection and its namespace
00204
00205 proc cmd-destroy { } {
00206 variable logger
00207 variable sock
00208 if { [info exists logger] } { ${logger}::delete }
00209 catch {close $sock}
00210 namespace delete [namespace current]
00211 }
00212
00213 proc cmd-connected { } {
00214 variable sock
00215 if { $sock == "" } { return 0 }
00216 return 1
00217 }
00218
00219 proc cmd-user { username hostname servername {userinfo ""} } {
00220 if { $userinfo == "" } {
00221 ircsend "USER $username $hostname server :$servername"
00222 } else {
00223 ircsend "USER $username $hostname $servername :$userinfo"
00224 }
00225 }
00226
00227 proc cmd-nick { nk } {
00228 ircsend "NICK $nk"
00229 }
00230
00231 proc cmd-ping { target } {
00232 ircsend "PRIVMSG $target :\001PING [clock seconds]\001"
00233 }
00234
00235 proc cmd-serverping { } {
00236 ircsend "PING [clock seconds]"
00237 }
00238
00239 proc cmd-ctcp { target line } {
00240 ircsend "PRIVMSG $target :\001$line\001"
00241 }
00242
00243 proc cmd-join { chan {key {}} } {
00244 ircsend "JOIN $chan $key"
00245 }
00246
00247 proc cmd-part { chan {msg ""} } {
00248 if { $msg == "" } {
00249 ircsend "PART $chan"
00250 } else {
00251 ircsend "PART $chan :$msg"
00252 }
00253 }
00254
00255 proc cmd-quit { {msg {tcllib irc module - http:
00256 ircsend "QUIT :$msg"
00257 }
00258
00259 proc cmd-privmsg { target msg } {
00260 ircsend "PRIVMSG $target :$msg"
00261 }
00262
00263 proc cmd-notice { target msg } {
00264 ircsend "NOTICE $target :$msg"
00265 }
00266
00267 proc cmd-kick { chan target {msg {}} } {
00268 ircsend "KICK $chan $target :$msg"
00269 }
00270
00271 proc cmd-mode { target args } {
00272 ircsend "MODE $target [join $args]"
00273 }
00274
00275 proc cmd-topic { chan msg } {
00276 ircsend "TOPIC $chan :$msg"
00277 }
00278
00279 proc cmd-invite { chan target } {
00280 ircsend "INVITE $target $chan"
00281 }
00282
00283 proc cmd-send { line } {
00284 ircsend $line
00285 }
00286
00287 proc cmd-peername { } {
00288 variable sock
00289 if { $sock == "" } { return {} }
00290 return [fconfigure $sock -peername]
00291 }
00292
00293 proc cmd-sockname { } {
00294 variable sock
00295 if { $sock == "" } { return {} }
00296 return [fconfigure $sock -sockname]
00297 }
00298
00299 proc cmd-socket { } {
00300 variable sock
00301 return $sock
00302 }
00303
00304 proc cmd-disconnect { } {
00305 variable sock
00306 if { $sock == "" } { return -1 }
00307 catch { close $sock }
00308 set sock {}
00309 return 0
00310 }
00311
00312 # Connect --
00313 # Create the actual tcp connection.
00314
00315 proc cmd-connect { h {p 6667} } {
00316 variable sock
00317 variable host
00318 variable port
00319
00320 set host $h
00321 set port $p
00322
00323 if { $sock == "" } {
00324 set sock [socket $host $port]
00325 fconfigure $sock -translation crlf -buffering line
00326 fileevent $sock readable [namespace current]::GetEvent
00327 }
00328 return 0
00329 }
00330
00331 # Callback API:
00332
00333 # These are all available from within callbacks, so as to
00334 # provide an interface to provide some information on what is
00335 # coming out of the server.
00336
00337 # action --
00338
00339 # Action returns the action performed, such as KICK, PRIVMSG,
00340 # MODE etc, including numeric actions such as 001, 252, 353,
00341 # and so forth.
00342
00343 proc action { } {
00344 variable linedata
00345 return $linedata(action)
00346 }
00347
00348 # msg --
00349
00350 # The last argument of the line, after the last ':'.
00351
00352 proc msg { } {
00353 variable linedata
00354 return $linedata(msg)
00355 }
00356
00357 # who --
00358
00359 # Who performed the action. If the command is called as [who address],
00360 # it returns the information in the form
00361 # nick!ident@host.domain.net
00362
00363 proc who { {address 0} } {
00364 variable linedata
00365 if { $address == 0 } {
00366 return [lindex [split $linedata(who) !] 0]
00367 } else {
00368 return $linedata(who)
00369 }
00370 }
00371
00372 # target --
00373
00374 # To whom was this action done.
00375
00376 proc target { } {
00377 variable linedata
00378 return $linedata(target)
00379 }
00380
00381 # additional --
00382
00383 # Returns any additional header elements beyond the target as a list.
00384
00385 proc additional { } {
00386 variable linedata
00387 return $linedata(additional)
00388 }
00389
00390 # header --
00391
00392 # Returns the entire header in list format.
00393
00394 proc header { } {
00395 variable linedata
00396 return [concat [list $linedata(who) $linedata(action) \
00397 $linedata(target)] $linedata(additional)]
00398 }
00399
00400 # GetEvent --
00401
00402 # Get a line from the server and dispatch it.
00403
00404 proc GetEvent { } {
00405 variable linedata
00406 variable sock
00407 variable dispatch
00408 array set linedata {}
00409 set line "eof"
00410 if { [eof $sock] || [catch {gets $sock} line] } {
00411 close $sock
00412 set sock {}
00413 cmd-log error "Error receiving from network: $line"
00414 if { [info exists dispatch(EOF)] } {
00415 eval $dispatch(EOF)
00416 }
00417 return
00418 }
00419 cmd-log debug "Recieved: $line"
00420 if { [set pos [string first " :" $line]] > -1 } {
00421 set header [string range $line 0 [expr {$pos - 1}]]
00422 set linedata(msg) [string range $line [expr {$pos + 2}] end]
00423 } else {
00424 set header [string trim $line]
00425 set linedata(msg) {}
00426 }
00427
00428 if { [string match :* $header] } {
00429 set header [split [string trimleft $header :]]
00430 } else {
00431 set header [linsert [split $header] 0 {}]
00432 }
00433 set linedata(who) [lindex $header 0]
00434 set linedata(action) [lindex $header 1]
00435 set linedata(target) [lindex $header 2]
00436 set linedata(additional) [lrange $header 3 end]
00437 if { [info exists dispatch($linedata(action))] } {
00438 eval $dispatch($linedata(action))
00439 } elseif { [string match {[0-9]??} $linedata(action)] } {
00440 eval $dispatch(defaultnumeric)
00441 } elseif { $linedata(who) == "" } {
00442 eval $dispatch(defaultcmd)
00443 } else {
00444 eval $dispatch(defaultevent)
00445 }
00446 }
00447
00448 # registerevent --
00449
00450 # Register an event in the dispatch table.
00451
00452 # Arguments:
00453 # evnt: name of event as sent by IRC server.
00454 # cmd: proc to register as the event handler
00455
00456 proc cmd-registerevent { evnt cmd } {
00457 variable dispatch
00458 set dispatch($evnt) $cmd
00459 if { $cmd == "" } {
00460 unset dispatch($evnt)
00461 }
00462 }
00463
00464 # getevent --
00465
00466 # Return the currently registered handler for the event.
00467
00468 # Arguments:
00469 # evnt: name of event as sent by IRC server.
00470
00471 proc cmd-getevent { evnt } {
00472 variable dispatch
00473 if { [info exists dispatch($evnt)] } {
00474 return $dispatch($evnt)
00475 }
00476 return {}
00477 }
00478
00479 # eventexists --
00480
00481 # Return a boolean value indicating if there is a handler
00482 # registered for the event.
00483
00484 # Arguments:
00485 # evnt: name of event as sent by IRC server.
00486
00487 proc cmd-eventexists { evnt } {
00488 variable dispatch
00489 return [info exists dispatch($evnt)]
00490 }
00491
00492 # network --
00493
00494 # Accepts user commands and dispatches them.
00495
00496 # Arguments:
00497 # cmd: command to invoke
00498 # args: arguments to the command
00499
00500 proc network { cmd args } {
00501 eval [linsert $args 0 [namespace current]::cmd-$cmd]
00502 }
00503
00504 # Create default handlers.
00505
00506 set dispatch(PING) {network send "PONG :[msg]"}
00507 set dispatch(defaultevent) #
00508 set dispatch(defaultcmd) #
00509 set dispatch(defaultnumeric) #
00510 }
00511
00512 set returncommand [format "%s::irc%s::network" [namespace current] $conn]
00513 incr conn
00514 return $returncommand
00515 }
00516
00517
00518
00519 package provide irc $::irc::version
00520
00521
00522