summaryrefslogtreecommitdiffstats
path: root/tcllib/modules/irc
diff options
context:
space:
mode:
authorWilliam Joye <wjoye@cfa.harvard.edu>2016-10-27 19:39:39 (GMT)
committerWilliam Joye <wjoye@cfa.harvard.edu>2016-10-27 19:39:39 (GMT)
commitea28451286d3ea4a772fa174483f9a7a66bb1ab3 (patch)
tree6ee9d8a7848333a7ceeee3b13d492e40225f8b86 /tcllib/modules/irc
parentb5ca09bae0d6a1edce939eea03594dd56383f2c8 (diff)
parent7c621da28f07e449ad90c387344f07a453927569 (diff)
downloadblt-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/ChangeLog259
-rw-r--r--tcllib/modules/irc/irc.man239
-rw-r--r--tcllib/modules/irc/irc.tcl523
-rw-r--r--tcllib/modules/irc/picoirc.man162
-rw-r--r--tcllib/modules/irc/picoirc.tcl271
-rw-r--r--tcllib/modules/irc/pkgIndex.tcl8
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]]