diff options
author | William Joye <wjoye@cfa.harvard.edu> | 2016-10-27 19:39:39 (GMT) |
---|---|---|
committer | William Joye <wjoye@cfa.harvard.edu> | 2016-10-27 19:39:39 (GMT) |
commit | ea28451286d3ea4a772fa174483f9a7a66bb1ab3 (patch) | |
tree | 6ee9d8a7848333a7ceeee3b13d492e40225f8b86 /tcllib/modules/irc | |
parent | b5ca09bae0d6a1edce939eea03594dd56383f2c8 (diff) | |
parent | 7c621da28f07e449ad90c387344f07a453927569 (diff) | |
download | blt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.zip blt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.tar.gz blt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.tar.bz2 |
Merge commit '7c621da28f07e449ad90c387344f07a453927569' as 'tcllib'
Diffstat (limited to 'tcllib/modules/irc')
-rw-r--r-- | tcllib/modules/irc/ChangeLog | 259 | ||||
-rw-r--r-- | tcllib/modules/irc/irc.man | 239 | ||||
-rw-r--r-- | tcllib/modules/irc/irc.tcl | 523 | ||||
-rw-r--r-- | tcllib/modules/irc/picoirc.man | 162 | ||||
-rw-r--r-- | tcllib/modules/irc/picoirc.tcl | 271 | ||||
-rw-r--r-- | tcllib/modules/irc/pkgIndex.tcl | 8 |
6 files changed, 1462 insertions, 0 deletions
diff --git a/tcllib/modules/irc/ChangeLog b/tcllib/modules/irc/ChangeLog new file mode 100644 index 0000000..4dec8fa --- /dev/null +++ b/tcllib/modules/irc/ChangeLog @@ -0,0 +1,259 @@ +2013-02-01 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * + * Released and tagged Tcllib 1.15 ======================== + * + +2013-01-09 Andreas Kupries <andreask@activestate.com> + + * irc.man: Documented the callback for EOF as required. + +2011-12-13 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * + * Released and tagged Tcllib 1.14 ======================== + * + +2011-01-24 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * + * Released and tagged Tcllib 1.13 ======================== + * + +2009-12-07 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * + * Released and tagged Tcllib 1.12 ======================== + * + +2008-12-12 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * + * Released and tagged Tcllib 1.11.1 ======================== + * + +2008-10-16 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * + * Released and tagged Tcllib 1.11 ======================== + * + +2008-08-05 Andreas Kupries <andreask@activestate.com> + + * irc.tcl (::irc::connection): Fixed [Bug 2038217], a + * irc.man: creative-writing problem. Bumped the package + * pkgIndex.tcl: version to 0.6.1. + +2008-06-24 Pat Thoyts <patthoyts@users.sourceforge.net> + + * picoirc.tcl: Fixed ctcp responses (should use NOTICE). + +2008-03-14 Andreas Kupries <andreask@activestate.com> + + * picoirc.man: Cleaned up a bit, replaced deprecated [nl] usage + with [para]. + +2007-10-19 Pat Thoyts <patthoyts@users.sourceforge.net> + + * picoirc.man: Added an alternative that is somewhat simpler + * picoirc.tcl: to embed in an application. Based upon the picoirc + code posted to the wiki and as used in tkchat. + +2007-09-12 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * + * Released and tagged Tcllib 1.10 ======================== + * + +2007-03-21 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * irc.man: Fixed all warnings due to use of now deprecated + commands. Added a section about how to give feedback. + +2006-10-03 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * + * Released and tagged Tcllib 1.9 ======================== + * + +2006-04-23 Pat Thoyts <patthoyts@users.sourceforge.net> + + * irc.tcl: Applied patch #1349154 by Kristoffer Lawson to add + * irc.man: a command to retrieve the socket in use. + +2005-10-06 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * + * Released and tagged Tcllib 1.8 ======================== + * + +2004-10-05 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * + * Released and tagged Tcllib 1.7 ======================== + * + +2004-09-24 Aaron Faupell <afaupell@users.sourceforge.net> + + * irc.tcl: removed package require for logger and added config + option to use logger. removed backwards compatibility code from + connection and connect. added log and logname commands. + +2004-09-23 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * irc.tcl: Fixed expr'essions without braces. + +2004-05-23 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * + * Released and tagged Tcllib 1.6.1 ======================== + * + +2004-02-15 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * + * Released and tagged Tcllib 1.6 ======================== + * + +2004-01-24 Andreas Kupries <andreask@activestate.com> + + * irc.man: Small documentation cleanups. + +2004-01-20 Aaron Faupell <afaupell@users.sourceforge.net> + + * irc.tcl Fixed incorrect usage of linsert in previous change. + Removed uneeded state variable. + +2003-10-22 David N. Welton <davidw@dedasys.com> + + * irc.tcl (network): Fixed usage of eval - thanks to Jeff Hobbs. + +2003-07-27 Aaron Faupell <afaupell@users.sourceforge.net> + * irc.tcl removed catch around socket creation + +2003-07-02 Aaron Faupell <afaupell@users.sourceforge.net> + * irc.tcl fixed logger not being turned off if debug + was turned off prior to creating new connection. + +2003-07-02 Aaron Faupell + * irc.tcl moved cmd-reload to ::irc::reload. removed + unused nick variable. + * irc.man added all the recently created + commands to the man page. + + +2003-06-30 David N. Welton <davidw@dedasys.com> + + * irc.tcl (::irc::connection): connection no longer takes + host/port arguments. This is done by connect now. Added note + that compatibility code for older versions should be removed after + a release cycle or two. (Aaron Faupell) + (cmd-connect): connect command now takes hostname and port + arguments. (Aaron Faupell) + (cmd-reload): New command reloads irc.tcl file, making it possible + to make changes in a running system without losing the connection. + (::irc::connection): The unique namespace for irc connections no + longer includes the hostname. (Aaron Faupell) + (::irc::connections): New command - return list of existing + connections (Aaron Faupell). + (cmd-config): Per connection configuration (Aaron Faupell). + (cmd-peername): New command - get socket peername. + (cmd-sockname): New command - get socket name. + (cmd-disconnect): New command - disconnect the connection without + destroying it. + + * irc.man: Updated the man page to reflect the new connect and + connection commands. + +2003-05-28 David N. Welton <davidw@dedasys.com> + + * irc.tcl (cmd-quit): Add default QUIT message. + +2003-05-25 David N. Welton <davidw@dedasys.com> + + * irc.tcl: Several cleanups/improvements by Aaron Faupell. + (cmd-getevent) Fixed typo. + (cmd-user): Added backwards compatible cmd-user. + (cmd-connect): Minor improvements/cleanup. + (GetEvent): Changed parsing of $line components. + +2003-05-22 Andreas Kupries <andreask@activestate.com> + + * irc.man: Fixed some typos in the manpage which prevented + conversion. + +2003-05-17 David N. Welton <davidw@dedasys.com> + + * irc.man: Add key option to channel join command. Provided by + Aaron Faupell. + +2003-05-16 David N. Welton <davidw@dedasys.com> + + * irc.man: Added Aaron's updated documentation, including the new + commands. + + * irc.tcl: Lots of improvements by Aaron Faupell, including: new + commands, and a new method of dispatching events. Server PINGs + (as opposed to CTCP PINGS) automatically generate a reply, as a + convenience. Version number 0.4. + +2003-05-05 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * + * Released and tagged Tcllib 1.4 ======================== + * + +2003-04-13 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * irc.tcl: Accepted the patch in report [#718985] for a more + robust 'GetEvent' routine. Provided by Donal Fellows + <dkf@users.sourceforge.net>. + +2003-04-11 Andreas Kupries <andreask@activestate.com> + + * irc.man: + * irc.tcl: Fixed bug #614591. Set version of the package to + to 0.3 throughout. Was insonsistent. + +2003-01-25 David N. Welton <davidw@dedasys.com> + + * irc.tcl: Added Tcl requirement to package itself. + +2003-01-24 David N. Welton <davidw@dedasys.com> + + * pkgIndex.tcl: Added dependency on Tcl 8.3 in the pkgIndex.tcl + file. I'm not sure that this code won't work with earlier + versions of Tcl, but 8.3 is all I have to test against. Please + let me know if you successfully run it with earlier versions. + Fixes [674331]. + +2003-01-16 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * irc.man: More semantic markup, less visual one. + +2003-01-08 David N. Welton <davidw@dedasys.com> + + * irc.tcl: Make sure 'api' commands return strings, not lists. + (DispatchServerEvent): Add a missing join, to keep + +2002-12-16 David N. Welton <davidw@dedasys.com> + + * irc.tcl: Use 'logger' package for error/debug reporting. + Cleanups with regards to possible 'bgerror' situations (network + input/output). Bumped version number to 0.3. + +2002-04-10 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * irc.man: Added doctools manpage. + +2002-02-14 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * irc.tcl: Frink run. + + * irc: Version is now 0.2 to distinguish this from the code in + tcllib release 1.2 + +2001-11-19 Andreas Kupries <andreas_kupries@users.sourceforge.net> + + * irc.n: + * irc.tcl: Applied patch #481477. diff --git a/tcllib/modules/irc/irc.man b/tcllib/modules/irc/irc.man new file mode 100644 index 0000000..a0d5423 --- /dev/null +++ b/tcllib/modules/irc/irc.man @@ -0,0 +1,239 @@ +[comment {-*- tcl -*- doctools manpage}] +[manpage_begin irc n 0.6.1] +[see_also {rfc 1459}] +[keywords chat] +[keywords irc] +[moddesc {Low Level Tcl IRC Interface}] +[titledesc {Create IRC connection and interface.}] +[category Networking] +[require Tcl] +[require irc [opt 0.6.1]] +[description] + +This package provides low-level commands to deal with the IRC protocol +(Internet Relay Chat) for immediate and interactive multi-cast +communication. + +[para] + +[list_begin definitions] + +[call [cmd ::irc::config] [opt key] [opt value]] + +Sets configuration [opt key] to [opt value]. The configuration keys +currently defined are the boolean flags [const logger] and [const debug]. +[const logger] makes [package irc] use the logger package for printing +error. [const debug] requires [const logger] and prints extra debug output. + +If no [opt key] or [opt value] is given the current values are returned. + +[call [cmd ::irc::connection]] + +The command creates a new object to deal with an IRC connection. +Creating this IRC object does not automatically create the network +connection. It returns a new irc namespace command which can be used +to interact with the new IRC connection. NOTE: the old form of the +connection command, which took a hostname and port as arguments, is +deprecated. Use [cmd connect] instead to specify this information. + +[call [cmd ::irc::connections]] + +Returns a list of all the current connections that were created with +[const connection] + +[list_end] + +[section {Per-connection Commands}] +[para] + +In the following list of available connection methods [arg net] +represents a connection command as returned by +[cmd ::irc::connection]. + +[list_begin definitions] + +[call [arg net] [method registerevent] [arg event] [arg script]] + +Registers a callback handler for the specific event. Events available +are those described in RFC 1459 + +[uri http://www.rfc-editor.org/rfc/rfc1459.txt]. + +In addition, there are several other events defined. + +[const defaultcmd] adds a command that is called if no other callback +is present. [const EOF] is called if the connection signals an End of +File condition. The events [const defaultcmd], [const defaultnumeric], +[const defaultevent], and [const EOF] are required. + +[arg script] is executed in the connection namespace, which can take +advantage of several commands (see [sectref {Callback Commands}] +below) to aid in the parsing of data. + +[call [arg net] [method getevent] [arg event] [arg script]] + +Returns the current handler for the event if one exists. Otherwise an +empty string is returned. + +[call [arg net] [method eventexists] [arg event] [arg script]] + +Returns a boolean value indicating the existence of the event handler. + +[call [arg net] [method connect] [arg hostname] [opt port]] + +This causes the socket to be established. [cmd ::irc::connection] +created the namespace and the commands to be used, but did not +actually open the socket. This is done here. NOTE: the older form of +'connect' did not require the user to specify a hostname and port, +which were specified with 'connection'. That form is deprecated. + +[call [arg net] [method config] [opt key] [opt value]] + +The same as [cmd ::irc::config] but sets and gets options for the [arg net] +connection only. + +[call [arg net] [method log] [arg level] [arg message]] + +If logger is turned on by [method config] this will write a log [arg message] +at [arg level]. + +[call [arg net] [method logname]] + +Returns the name of the logger instance if logger is turned on. + +[call [arg net] [method connected]] + +Returns a boolean value indicating if this connection is connected to a server. + +[call [arg net] [method sockname]] + +Returns a 3 element list consisting of the ip address, the hostname, and the port +of the local end of the connection, if currently connected. + +[call [arg net] [method peername]] + +Returns a 3 element list consisting of the ip address, the hostname, and the port +of the remote end of the connection, if currently connected. + +[call [arg net] [method socket]] + +Return the Tcl channel for the socket used by the connection. + +[call [arg net] [method user] [arg username] [arg localhostname] [arg localdomainname] [arg userinfo]] + +Sends USER command to server. [arg username] is the username you want +to appear. [arg localhostname] is the host portion of your hostname, [arg localdomainname] +is your domain name, and [arg userinfo] is a short description of who you are. The 2nd and 3rd +arguments are normally ignored by the IRC server. + +[call [arg net] [method nick] [arg nick]] + +NICK command. [arg nick] is the nickname you wish to use for the +particular connection. + +[call [arg net] [method ping] [arg target]] + +Send a CTCP PING to [arg target]. + +[call [arg net] [method serverping]] + +PING the server. + +[call [arg net] [method join] [arg channel] [opt [arg key]]] + +[arg channel] is the IRC channel to join. IRC channels typically +begin with a hashmark ("#") or ampersand ("&"). + +[call [arg net] [method part] [arg channel] [opt [arg message]]] + +Makes the client leave [arg channel]. Some networks may support the optional +argument [arg message] + +[call [arg net] [method quit] [opt [arg message]]] + +Instructs the IRC server to close the current connection. The package +will use a generic default if no [arg message] was specified. + +[call [arg net] [method privmsg] [arg target] [arg message]] + +Sends [arg message] to [arg target], which can be either a channel, or +another user, in which case their nick is used. + +[call [arg net] [method notice] [arg target] [arg message]] + +Sends a [const notice] with message [arg message] to [arg target], +which can be either a channel, or another user, in which case their nick is used. + +[call [arg net] [method ctcp] [arg target] [arg message]] + +Sends a CTCP of type [arg message] to [arg target] + +[call [arg net] [method kick] [arg channel] [arg target] [opt [arg message]]] + +Kicks the user [arg target] from the channel [arg channel] with a [arg message]. +The latter can be left out. + +[call [arg net] [method mode] [arg target] [arg args]] + +Sets the mode [arg args] on the target [arg target]. [arg target] may be a channel, +a channel user, or yourself. + +[call [arg net] [method topic] [arg channel] [arg message]] + +Sets the topic on [arg channel] to [arg message] specifying an empty string +will remove the topic. + +[call [arg net] [method invite] [arg channel] [arg target]] + +Invites [arg target] to join the channel [arg channel] + +[call [arg net] [method send] [arg text]] + +Sends [arg text] to the IRC server. + +[call [arg net] [method destroy]] + +Deletes the connection and its associated namespace and information. + +[list_end] + +[section {Callback Commands}] +[para] + +These commands can be used within callbacks + +[list_begin definitions] + +[call [cmd who] [opt [const address]]] + +Returns the nick of the user who performed a command. The optional +keyword [const address] causes the command to return the user in the +format "username@address". + +[call [cmd action]] + +Returns the action performed, such as KICK, PRIVMSG, MODE, etc... +Normally not useful, as callbacks are bound to a particular event. + +[call [cmd target]] + +Returns the target of a particular command, such as the channel or +user to whom a PRIVMSG is sent. + +[call [cmd additional]] + +Returns a list of any additional arguments after the target. + +[call [cmd header]] + +Returns the entire event header (everything up to the :) as a proper list. + +[call [cmd msg]] + +Returns the message portion of the command (the part after the :). + +[list_end] + +[vset CATEGORY irc] +[include ../doctools2base/include/feedback.inc] +[manpage_end] diff --git a/tcllib/modules/irc/irc.tcl b/tcllib/modules/irc/irc.tcl new file mode 100644 index 0000000..c8a7db8 --- /dev/null +++ b/tcllib/modules/irc/irc.tcl @@ -0,0 +1,523 @@ +# irc.tcl -- +# +# irc implementation for Tcl. +# +# Copyright (c) 2001-2003 by David N. Welton <davidw@dedasys.com>. +# This code may be distributed under the same terms as Tcl. + +package require Tcl 8.3 + +namespace eval ::irc { + # counter used to differentiate connections + variable conn 0 + variable config + variable irctclfile [info script] + array set config { + debug 0 + logger 0 + } +} + +# ::irc::config -- +# +# Set global configuration options. +# +# Arguments: +# +# key name of the configuration option to change. +# +# value value of the configuration option. + +proc ::irc::config { args } { + variable config + if { [llength $args] == 0 } { + return [array get config] + } elseif { [llength $args] == 1 } { + return $config($key) + } elseif { [llength $args] > 2 } { + error "wrong # args: should be \"config key ?val?\"" + } + set key [lindex $args 0] + set value [lindex $args 1] + foreach ns [namespace children] { + if { [info exists config($key)] && [info exists ${ns}::config($key)] \ + && [set ${ns}::config($key)] == $config($key)} { + ${ns}::cmd-config $key $value + } + } + set config($key) $value +} + + +# ::irc::connections -- +# +# Return a list of handles to all existing connections + +proc ::irc::connections { } { + set r {} + foreach ns [namespace children] { + lappend r ${ns}::network + } + return $r +} + +# ::irc::reload -- +# +# Reload this file, and merge the current connections into +# the new one. + +proc ::irc::reload { } { + variable conn + set oldconn $conn + namespace eval :: { + source [set ::irc::irctclfile] + } + foreach ns [namespace children] { + foreach var {sock logger host port} { + set $var [set ${ns}::$var] + } + array set dispatch [array get ${ns}::dispatch] + array set config [array get ${ns}::config] + # make sure our new connection uses the same namespace + set conn [string range $ns 10 end] + ::irc::connection + foreach var {sock logger host port} { + set ${ns}::$var [set $var] + } + array set ${ns}::dispatch [array get dispatch] + array set ${ns}::config [array get config] + } + set conn $oldconn +} + +# ::irc::connection -- +# +# Create an IRC connection namespace and associated commands. + +proc ::irc::connection { args } { + variable conn + variable config + + # Create a unique namespace of the form irc$conn::$host + + set name [format "%s::irc%s" [namespace current] $conn] + + namespace eval $name { + variable sock + variable dispatch + variable linedata + variable config + + set sock {} + array set dispatch {} + array set linedata {} + array set config [array get ::irc::config] + if { $config(logger) || $config(debug)} { + package require logger + variable logger + set logger [logger::init [namespace tail [namespace current]]] + if { !$config(debug) } { ${logger}::disable debug } + } + + + # ircsend -- + # send text to the IRC server + + proc ircsend { msg } { + variable sock + variable dispatch + if { $sock == "" } { return } + cmd-log debug "ircsend: '$msg'" + if { [catch {puts $sock $msg} err] } { + catch { close $sock } + set sock {} + if { [info exists dispatch(EOF)] } { + eval $dispatch(EOF) + } + cmd-log error "Error in ircsend: $err" + } + } + + + ######################################################### + # Implemented user-side commands, meaning that these commands + # cause the calling user to perform the given action. + ######################################################### + + + # cmd-config -- + # + # Set or return per-connection configuration options. + # + # Arguments: + # + # key name of the configuration option to change. + # + # value value (optional) of the configuration option. + + proc cmd-config { args } { + variable config + variable logger + + if { [llength $args] == 0 } { + return [array get config] + } elseif { [llength $args] == 1 } { + return $config($key) + } elseif { [llength $args] > 2 } { + error "wrong # args: should be \"config key ?val?\"" + } + set key [lindex $args 0] + set value [lindex $args 1] + if { $key == "debug" } { + if {$value} { + if { !$config(logger) } { cmd-config logger 1 } + ${logger}::enable debug + } elseif { [info exists logger] } { + ${logger}::disable debug + } + } + if { $key == "logger" } { + if { $value && !$config(logger)} { + package require logger + set logger [logger::init [namespace tail [namespace current]]] + } elseif { [info exists logger] } { + ${logger}::delete + unset logger + } + } + set config($key) $value + } + + proc cmd-log {level text} { + variable logger + if { ![info exists logger] } return + ${logger}::$level $text + } + + proc cmd-logname { } { + variable logger + if { ![info exists logger] } return + return $logger + } + + # cmd-destroy -- + # + # destroys the current connection and its namespace + + proc cmd-destroy { } { + variable logger + variable sock + if { [info exists logger] } { ${logger}::delete } + catch {close $sock} + namespace delete [namespace current] + } + + proc cmd-connected { } { + variable sock + if { $sock == "" } { return 0 } + return 1 + } + + proc cmd-user { username hostname servername {userinfo ""} } { + if { $userinfo == "" } { + ircsend "USER $username $hostname server :$servername" + } else { + ircsend "USER $username $hostname $servername :$userinfo" + } + } + + proc cmd-nick { nk } { + ircsend "NICK $nk" + } + + proc cmd-ping { target } { + ircsend "PRIVMSG $target :\001PING [clock seconds]\001" + } + + proc cmd-serverping { } { + ircsend "PING [clock seconds]" + } + + proc cmd-ctcp { target line } { + ircsend "PRIVMSG $target :\001$line\001" + } + + proc cmd-join { chan {key {}} } { + ircsend "JOIN $chan $key" + } + + proc cmd-part { chan {msg ""} } { + if { $msg == "" } { + ircsend "PART $chan" + } else { + ircsend "PART $chan :$msg" + } + } + + proc cmd-quit { {msg {tcllib irc module - http://core.tcl.tk/tcllib/}} } { + ircsend "QUIT :$msg" + } + + proc cmd-privmsg { target msg } { + ircsend "PRIVMSG $target :$msg" + } + + proc cmd-notice { target msg } { + ircsend "NOTICE $target :$msg" + } + + proc cmd-kick { chan target {msg {}} } { + ircsend "KICK $chan $target :$msg" + } + + proc cmd-mode { target args } { + ircsend "MODE $target [join $args]" + } + + proc cmd-topic { chan msg } { + ircsend "TOPIC $chan :$msg" + } + + proc cmd-invite { chan target } { + ircsend "INVITE $target $chan" + } + + proc cmd-send { line } { + ircsend $line + } + + proc cmd-peername { } { + variable sock + if { $sock == "" } { return {} } + return [fconfigure $sock -peername] + } + + proc cmd-sockname { } { + variable sock + if { $sock == "" } { return {} } + return [fconfigure $sock -sockname] + } + + proc cmd-socket { } { + variable sock + return $sock + } + + proc cmd-disconnect { } { + variable sock + if { $sock == "" } { return -1 } + catch { close $sock } + set sock {} + return 0 + } + + # Connect -- + # Create the actual tcp connection. + + proc cmd-connect { h {p 6667} } { + variable sock + variable host + variable port + + set host $h + set port $p + + if { $sock == "" } { + set sock [socket $host $port] + fconfigure $sock -translation crlf -buffering line + fileevent $sock readable [namespace current]::GetEvent + } + return 0 + } + + # Callback API: + + # These are all available from within callbacks, so as to + # provide an interface to provide some information on what is + # coming out of the server. + + # action -- + + # Action returns the action performed, such as KICK, PRIVMSG, + # MODE etc, including numeric actions such as 001, 252, 353, + # and so forth. + + proc action { } { + variable linedata + return $linedata(action) + } + + # msg -- + + # The last argument of the line, after the last ':'. + + proc msg { } { + variable linedata + return $linedata(msg) + } + + # who -- + + # Who performed the action. If the command is called as [who address], + # it returns the information in the form + # nick!ident@host.domain.net + + proc who { {address 0} } { + variable linedata + if { $address == 0 } { + return [lindex [split $linedata(who) !] 0] + } else { + return $linedata(who) + } + } + + # target -- + + # To whom was this action done. + + proc target { } { + variable linedata + return $linedata(target) + } + + # additional -- + + # Returns any additional header elements beyond the target as a list. + + proc additional { } { + variable linedata + return $linedata(additional) + } + + # header -- + + # Returns the entire header in list format. + + proc header { } { + variable linedata + return [concat [list $linedata(who) $linedata(action) \ + $linedata(target)] $linedata(additional)] + } + + # GetEvent -- + + # Get a line from the server and dispatch it. + + proc GetEvent { } { + variable linedata + variable sock + variable dispatch + array set linedata {} + set line "eof" + if { [eof $sock] || [catch {gets $sock} line] } { + close $sock + set sock {} + cmd-log error "Error receiving from network: $line" + if { [info exists dispatch(EOF)] } { + eval $dispatch(EOF) + } + return + } + cmd-log debug "Recieved: $line" + if { [set pos [string first " :" $line]] > -1 } { + set header [string range $line 0 [expr {$pos - 1}]] + set linedata(msg) [string range $line [expr {$pos + 2}] end] + } else { + set header [string trim $line] + set linedata(msg) {} + } + + if { [string match :* $header] } { + set header [split [string trimleft $header :]] + } else { + set header [linsert [split $header] 0 {}] + } + set linedata(who) [lindex $header 0] + set linedata(action) [lindex $header 1] + set linedata(target) [lindex $header 2] + set linedata(additional) [lrange $header 3 end] + if { [info exists dispatch($linedata(action))] } { + eval $dispatch($linedata(action)) + } elseif { [string match {[0-9]??} $linedata(action)] } { + eval $dispatch(defaultnumeric) + } elseif { $linedata(who) == "" } { + eval $dispatch(defaultcmd) + } else { + eval $dispatch(defaultevent) + } + } + + # registerevent -- + + # Register an event in the dispatch table. + + # Arguments: + # evnt: name of event as sent by IRC server. + # cmd: proc to register as the event handler + + proc cmd-registerevent { evnt cmd } { + variable dispatch + set dispatch($evnt) $cmd + if { $cmd == "" } { + unset dispatch($evnt) + } + } + + # getevent -- + + # Return the currently registered handler for the event. + + # Arguments: + # evnt: name of event as sent by IRC server. + + proc cmd-getevent { evnt } { + variable dispatch + if { [info exists dispatch($evnt)] } { + return $dispatch($evnt) + } + return {} + } + + # eventexists -- + + # Return a boolean value indicating if there is a handler + # registered for the event. + + # Arguments: + # evnt: name of event as sent by IRC server. + + proc cmd-eventexists { evnt } { + variable dispatch + return [info exists dispatch($evnt)] + } + + # network -- + + # Accepts user commands and dispatches them. + + # Arguments: + # cmd: command to invoke + # args: arguments to the command + + proc network { cmd args } { + eval [linsert $args 0 [namespace current]::cmd-$cmd] + } + + # Create default handlers. + + set dispatch(PING) {network send "PONG :[msg]"} + set dispatch(defaultevent) # + set dispatch(defaultcmd) # + set dispatch(defaultnumeric) # + } + + set returncommand [format "%s::irc%s::network" [namespace current] $conn] + incr conn + return $returncommand +} + +# ------------------------------------------------------------------------- + +package provide irc 0.6.1 + +# ------------------------------------------------------------------------- diff --git a/tcllib/modules/irc/picoirc.man b/tcllib/modules/irc/picoirc.man new file mode 100644 index 0000000..fe7224b --- /dev/null +++ b/tcllib/modules/irc/picoirc.man @@ -0,0 +1,162 @@ +[comment {-*- tcl -*- doctools manpage}] +[vset VERSION 0.5.2] +[manpage_begin picoirc n [vset VERSION]] +[see_also {rfc 1459}] +[keywords chat] +[keywords irc] +[moddesc {Simple embeddable IRC interface}] +[titledesc {Small and simple embeddable IRC client.}] +[category Networking] +[require Tcl] +[require picoirc [opt [vset VERSION]]] +[description] + +This package provides a general purpose minimal IRC client suitable for +embedding in other applications. All communication with the parent +application is done via an application provided callback procedure. +Each connection has its own state so you can hook up multiple servers +in a single application instance. + +[para] + +To initiate an IRC connection you must call [cmd picoirc::connect] +with a callback procedure, a nick-name to use on IRC and the IRC URL +that describes the connection. This will return a variable name that +is the irc connection context. See [sectref CALLBACK] for details. + +[para] + +This package is a fairly simple IRC client. If you need something with +more capability investigate the [package irc] package. + +[para] + +[section COMMANDS] + +[list_begin definitions] + +[call [cmd ::picoirc::connect] [arg callback] [arg nick] [arg url]] + +Create a new irc connection to the server specified by [arg url] and +login using the [arg nick] as the username. The [arg callback] must be +as specified in [sectref CALLBACK]. Returns a package-specific variable +that is used when calling other commands in this package. + +[call [cmd ::picoirc::post] [arg context] [arg channel] [arg message]] + +This should be called to process user input and send it to the +server. A number of commands are recognised when prefixed with a +forward-slash (/). Such commands are converted to IRC command +sequences and then sent. + +[call [cmd ::picoirc::splituri] [arg uri]] + +Splits an IRC scheme uniform resource indicator into its component +parts. Returns a list of server, port and channel. The default port is +6667 and there is no default channel. + +[call [cmd ::picoirc::send] [arg context] [arg line]] + +This command is where all raw output to the server is handled. The +default action is to write the [arg line] to the irc socket. However, +before this happens the callback is called with "debug write". This +permits the application author to inspect the raw IRC data and if +desired to return a break error code to halt further processing. In +this way the application can override the default send via the +callback procedure. + +[list_end] + +[section CALLBACK] + +The callback must look like: + +[example { +proc Callback {context state args} { +} +}] + +where context is the irc context variable name (in case you need to pass +it back to a picoirc procedure). state is one of a number of states as +described below. + +[list_begin options] + +[opt_def init] + +called just before the socket is created + +[opt_def connect] + +called once we have connected, before we join any channels + +[opt_def close] + +called when the socket gets closed, before the context is deleted. If +an error occurs before we get connected the only argument will be the +socket error message. + +[opt_def userlist "[arg channel] [arg nicklist]"] + +called to notify the application of an updated userlist. This is +generated when the output of the NAMES irc command is seen. The +package collects the entire output which can span a number of output +lines from the server and calls this callback when they have all been +received. + +[opt_def chat "[arg target] [arg nick] [arg message] [arg type]"] + +called when a message arrives. [arg target] is the identity that the +message was targetted for. This can be the logged in nick or a channel +name. [arg nick] is the name of the sender of the message. +[arg message] is the message text. [arg type] is set to "ACTION" if the +message was sent as a CTCP ACTION + +[opt_def system "[arg channel] [arg message]"] + +called when a system message is received + +[opt_def topic "[arg channel] [arg topic]"] + +called when the channel topic string is seen. [arg topic] is the text +of the channel topic. + +[opt_def traffic "[arg action] [arg channel] [arg nick] [opt [arg newnick]]"] + +called when users join, leave or change names. +[arg action] is either entered, left or nickchange and [arg nick] +is the user doing the action. [arg newnick] is +the new name if [arg action] is nickchange. +[para] +[emph NOTE]: [arg channel] is often empty for these messages as nick +activities are global for the irc server. You will have +to manage the nick for all connected channels yourself. + +[opt_def version] + +This is called to request a version string to use to +override the internal version. If implemented, you should +return as colon delimited string as +[para] + Appname:Appversion:LibraryVersion +[para] +For example, the default is +[para] + PicoIRC:[lb]package provide picoirc[rb]:Tcl [lb]info patchlevel[rb] + +[opt_def debug "[arg type] [arg raw]"] + +called when data is either being read or written to the network +socket. [arg type] is set to [const read] when reading data and +[const write] if the data is to be written. [arg raw] is the +unprocessed IRC protocol data. +[para] +In both cases the application can return a break error code to +interrupt further processing of the raw data. If this is a +[const read] operation then the package will not handle this line. If +the operation is [const write] then the package will not send the +data. This callback is intended for debugging protocol issues but +could be used to redirect all input and output if desired. + +[list_end] +[manpage_end] diff --git a/tcllib/modules/irc/picoirc.tcl b/tcllib/modules/irc/picoirc.tcl new file mode 100644 index 0000000..1728fe2 --- /dev/null +++ b/tcllib/modules/irc/picoirc.tcl @@ -0,0 +1,271 @@ +# Based upon the picoirc code by Salvatore Sanfillipo and Richard Suchenwirth +# See http://wiki.tcl.tk/13134 for the original standalone version. +# +# This package provides a general purpose minimal IRC client suitable for +# embedding in other applications. All communication with the parent +# application is done via an application provided callback procedure. +# +# Copyright (c) 2004 Salvatore Sanfillipo +# Copyright (c) 2004 Richard Suchenwirth +# Copyright (c) 2007 Patrick Thoyts +# +# ------------------------------------------------------------------------- +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# ------------------------------------------------------------------------- + +namespace eval ::picoirc { + variable uid + if {![info exists uid]} { set uid 0 } + + variable defaults { + server "irc.freenode.net" + port 6667 + channel "" + callback "" + motd {} + users {} + } + namespace export connect send post splituri +} + +proc ::picoirc::splituri {uri} { + foreach {server port channel} {{} {} {}} break + if {![regexp {^irc://([^:/]+)(?::([^/]+))?(?:/([^,]+))?} $uri -> server port channel]} { + regexp {^(?:([^@]+)@)?([^:]+)(?::(\d+))?} $uri -> channel server port + } + if {$port eq {}} { set port 6667 } + return [list $server $port $channel] +} + +proc ::picoirc::connect {callback nick args} { + if {[llength $args] > 2} { + return -code error "wrong # args: must be \"callback nick ?passwd? url\"" + } elseif {[llength $args] == 1} { + set url [lindex $args 0] + } else { + foreach {passwd url} $args break + } + variable defaults + variable uid + set context [namespace current]::irc[incr uid] + upvar #0 $context irc + array set irc $defaults + foreach {server port channel} [splituri $url] break + if {[info exists channel] && $channel ne ""} {set irc(channel) $channel} + if {[info exists server] && $server ne ""} {set irc(server) $server} + if {[info exists port] && $port ne ""} {set irc(port) $port} + if {[info exists passwd] && $passwd ne ""} {set irc(passwd) $passwd} + set irc(callback) $callback + set irc(nick) $nick + Callback $context init + set irc(socket) [socket -async $irc(server) $irc(port)] + fileevent $irc(socket) readable [list [namespace origin Read] $context] + fileevent $irc(socket) writable [list [namespace origin Write] $context] + return $context +} + +proc ::picoirc::Callback {context state args} { + upvar #0 $context irc + if {[llength $irc(callback)] > 0 + && [llength [info commands [lindex $irc(callback) 0]]] == 1} { + if {[catch {eval $irc(callback) [list $context $state] $args} err]} { + puts stderr "callback error: $err" + } + } +} + +proc ::picoirc::Version {context} { + if {[catch {Callback $context version} ver]} { set ver {} } + if {$ver eq {}} { + set ver "PicoIRC:[package provide picoirc]:Tcl [info patchlevel]" + } + return $ver +} + +proc ::picoirc::Write {context} { + upvar #0 $context irc + fileevent $irc(socket) writable {} + if {[set err [fconfigure $irc(socket) -error]] ne ""} { + Callback $context close $err + close $irc(socket) + unset irc + return + } + fconfigure $irc(socket) -blocking 0 -buffering line -translation crlf -encoding utf-8 + Callback $context connect + if {[info exists irc(passwd)]} { + send $context "PASS $irc(passwd)" + } + set ver [join [lrange [split [Version $context] :] 0 1] " "] + send $context "NICK $irc(nick)" + send $context "USER $::tcl_platform(user) 0 * :$ver user" + if {$irc(channel) ne {}} { + after idle [list [namespace origin send] $context "JOIN $irc(channel)"] + } + return +} + +proc ::picoirc::Splitirc {s} { + foreach v {nick flags user host} {set $v {}} + regexp {^([^!]*)!([^=]*)=([^@]+)@(.*)} $s -> nick flags user host + return [list $nick $flags $user $host] +} + +proc ::picoirc::Read {context} { + upvar #0 $context irc + if {[eof $irc(socket)]} { + fileevent $irc(socket) readable {} + Callback $context close + close $irc(socket) + unset irc + return + } + if {[gets $irc(socket) line] != -1} { + if {[string match "PING*" $line]} { + send $context "PONG [info hostname] [lindex [split $line] 1]" + return + } + # the callback can return -code break to prevent processing the read + if {[catch {Callback $context debug read $line}] == 3} { + return + } + if {[regexp {:([^!]*)![^ ].* +PRIVMSG ([^ :]+) +:(.*)} $line -> \ + nick target msg]} { + set type "" + if {[regexp {\001(\S+)(.*)?\001} $msg -> ctcp data]} { + switch -- $ctcp { + ACTION { set type ACTION ; set msg $data } + VERSION { + send $context "NOTICE $nick :\001VERSION [Version $context]\001" + return + } + PING { + send $context "NOTICE $nick :\001PING [lindex $data 0]\001" + return + } + TIME { + set time [clock format [clock seconds] \ + -format {%a %b %d %H:%M:%S %Y %Z}] + send $context "NOTICE $nick :\001TIME $time\001" + return + } + default { + set err [string map [list \001 ""] $msg] + send $context "NOTICE $nick :\001ERRMSG $err : unknown query\001" + return + } + } + } + if {[lsearch -exact {ijchain wubchain} $nick] != -1} { + if {$type eq "ACTION"} { + regexp {(\S+) (.+)} $msg -> nick msg + } else { + regexp {<([^>]+)> (.+)} $msg -> nick msg + } + } + Callback $context chat $target $nick $msg $type + } elseif {[regexp {^:((?:([^ ]+) +){1,}?):(.*)$} $line -> parts junk rest]} { + foreach {server code target fourth fifth} [split $parts] break + switch -- $code { + 001 - 002 - 003 - 004 - 005 - 250 - 251 - 252 - + 254 - 255 - 265 - 266 { return } + 433 { + variable nickid ; if {![info exists nickid]} {set nickid 0} + set seqlen [string length [incr nickid]] + set irc(nick) [string range $irc(nick) 0 [expr 8-$seqlen]]$nickid + send $context "NICK $irc(nick)" + } + 353 { set irc(users) [concat $irc(users) $rest]; return } + 366 { + Callback $context userlist $fourth $irc(users) + set irc(users) {} + return + } + 332 { Callback $context topic $fourth $rest; return } + 333 { return } + 375 { set irc(motd) {} ; return } + 372 { append irc(motd) $rest ; return} + 376 { return } + 311 { + foreach {server code target nick name host x} [split $parts] break + set irc(whois,$fourth) [list name $name host $host userinfo $rest] + return + } + 301 - 312 - 317 - 320 { return } + 319 { lappend irc(whois,$fourth) channels $rest; return } + 318 { + if {[info exists irc(whois,$fourth)]} { + Callback $context userinfo $fourth $irc(whois,$fourth) + unset irc(whois,$fourth) + } + return + } + JOIN { + foreach {n f u h} [Splitirc $server] break + Callback $context traffic entered $rest $n + return + } + NICK { + foreach {n f u h} [Splitirc $server] break + Callback $context traffic nickchange {} $n $rest + return + } + QUIT - PART { + foreach {n f u h} [Splitirc $server] break + Callback $context traffic left $target $n + return + } + } + Callback $context system "" "[lrange [split $parts] 1 end] $rest" + } else { + Callback $context system "" $line + } + } +} + +proc ::picoirc::post {context channel msg} { + upvar #0 $context irc + set type "" + if [regexp {^/([^ ]+) *(.*)} $msg -> cmd msg] { + regexp {^([^ ]+)?(?: +(.*))?} $msg -> first rest + switch -- $cmd { + me {set msg "\001ACTION $msg\001";set type ACTION} + nick {send $context "NICK $msg"; set $irc(nick) $msg} + quit {send $context "QUIT" } + part {send $context "PART $channel" } + names {send $context "NAMES $channel"} + whois {send $context "WHOIS $channel $msg"} + kick {send $context "KICK $channel $first :$rest"} + mode {send $context "MODE $msg"} + topic {send $context "TOPIC $channel :$msg" } + quote {send $context $msg} + join {send $context "JOIN $msg" } + version {send $context "PRIVMSG $first :\001VERSION\001"} + msg { + if {[regexp {([^ ]+) +(.*)} $msg -> target querymsg]} { + send $context "PRIVMSG $target :$querymsg" + Callback $context chat $target $target $querymsg "" + } + } + default {Callback $context system $channel "unknown command /$cmd"} + } + if {$cmd ne {me} || $cmd eq {msg}} return + } + foreach line [split $msg \n] {send $context "PRIVMSG $channel :$line"} + Callback $context chat $channel $irc(nick) $msg $type +} + +proc ::picoirc::send {context line} { + upvar #0 $context irc + # the callback can return -code break to prevent writing to socket + if {[catch {Callback $context debug write $line}] != 3} { + puts $irc(socket) $line + } +} + +# ------------------------------------------------------------------------- + +package provide picoirc 0.5.2 + +# ------------------------------------------------------------------------- diff --git a/tcllib/modules/irc/pkgIndex.tcl b/tcllib/modules/irc/pkgIndex.tcl new file mode 100644 index 0000000..c16c58d --- /dev/null +++ b/tcllib/modules/irc/pkgIndex.tcl @@ -0,0 +1,8 @@ +# pkgIndex.tcl -*- tcl -*- +# $Id: pkgIndex.tcl,v 1.10 2008/08/05 20:40:04 andreas_kupries Exp $ +if { ![package vsatisfies [package provide Tcl] 8.3] } { + # PRAGMA: returnok + return +} +package ifneeded irc 0.6.1 [list source [file join $dir irc.tcl]] +package ifneeded picoirc 0.5.2 [list source [file join $dir picoirc.tcl]] |