[DG] is authoring an [IRC] Client. It is a work in progress. beta @ http://sourceforge.net/project/showfiles.php?group_id=1616&release_id=196759 . ---- This IRC client is a faithful [XiRCON] [http://www.xircon.com] clone regarding how the user scripting operates. Here are some screenshots: * Doing Japanese: [http://tomasoft.sf.net/in_a_japanese_channel.gif] * Doing Chinese: [http://tomasoft.sf.net/263.gif] * Doing Klingon (in utf-8 over IRC and back using the CTCP/2 method of escaping): [http://tomasoft.sf.net/klingon.gif] * Elvish (layer 1 ConScript): [http://tomasoft.sourceforge.net/elvish.gif] * being normal (boring): [http://tomasoft.sf.net/in_an_english_channel.gif] ---- The client has been ''painfully'' designed to be 2 complimentary halves: 1. The back-end : The irc_engine Tcl extension DLL (100% Tcl API only, with a little STL) for handling everything that is not seen (ie. sockets, scripts, default behavior, IAL, etc..). 2. The UI : Whatever UI the user prefers according to the command control API. It could done in Tk, an implimentation in curses, Win32 GUI, or as odd as streaming html output with form input for posting. ---- '''The IRC_Engine back-end''' -- The irc_engine extension provides a single [Incr Tcl] class. The UI half (partialy undefined) also provides an [Incr Tcl] class. The two are brought together using inheritence like so: source irc_engine.itcl # =============================================================== # By selecting which IRC::ui class is sourced, we can switch # to what UI we want to use. # =============================================================== source irc_ui.itcl itcl::class IRC::connection { # =============================================== # Bring the 2 halves together using inheritence. # =============================================== inherit engine ui constructor {args} { eval engine::constructor $args } {} public method destroy {} {itcl::delete object $this} } ### Create a connection instance with a couple user scripts. set a [IRC::connection #auto someUserScript1.tcl someUserScript2.tcl] ### Connect to IRC. $a connect irc.qeast.net davygrvy DG {yo moma!} In the above, $a is now the connection object. All behavior of the engine is located in a script file called default.tcl [http://tomasoft.cvs.sourceforge.net/tomasoft/tomahawk/irc_engine/library/default.tcl?view=markup]. The behavior of irc_engine can be scripted by the user by loading scripts which are queried for event hooks prior to doing the default behavior. I do have a generic framework to the system, but only Tcl is supported. I have code started for perl, python, and java, but I'll have to get back to it later [http://tomasoft.cvs.sourceforge.net/tomasoft/tomahawk/irc_engine/generic/IRCUserScriptProvider.hpp?view=markup]. Here's where it gets interesting for user scripts.. Each tcl user script is run in its own interpreter. It maintains some of its own commands such as [[IRC::on]], but aliases most up to the global interp (I also call it the controlling or UI interp). So from the script, a call to ''IRC::echo ...'' will alias up to the global as ''connection0 echo ...'' where connection0 is the [Incr Tcl] object. This allows us to have our scripts track with their connection object. Where this gets more interesting is that from this aliasing, I can ''assume'' commands that will be in the top-most object that are provided by the UI (thanks to inheritence). The echo method is located in the UI half, yet the user script is in a separate interp of the irc_engine extension half and always linked to its parent object. irc_engine uses Tcl's event loop. This diagram shows some of the code paths: [http://tomasoft.sf.net/incoming_trail.gif] Tcl::Socket is just a wrapper for Tcl's socket API stuff. When a line from the server is received, it is parsed into its protocol parts [http://tomasoft.cvs.sourceforge.net/tomasoft/tomahawk/irc_engine/generic/IRCParse.hpp?view=markup] [http://tomasoft.cvs.sourceforge.net/tomasoft/tomahawk/irc_engine/generic/IRCParse.cpp?view=markup] and is the container for the parts that will get passed around to the subsiquent modules. IRCSplitAndQ [http://tomasoft.cvs.sourceforge.net/tomasoft/tomahawk/irc_engine/generic/IRCSplitAndQ.hpp?view=markup] [http://tomasoft.cvs.sourceforge.net/tomasoft/tomahawk/irc_engine/generic/IRCSplitAndQ.cpp?view=markup] is responsible for any text mappings (or multiple mappings) that need to be decoded, any CTCP/2 encoding escapes, any CTCP unquoting, any mode splitting, and removing/queueing of all embedded CTCP commands prior to the edited original getting posted as a job to the event loop. Phew! [http://tomasoft.sf.net/ircevent_trail.gif] When a job we had posted from IRCSplitAndQ is ready to be serviced by Tcl, our Tcl_EventProc named ''EvalCallback'' is called which just sets an interp lock then calls ''IRCEngine::EvalOneIRCEvent'' in the correct connection object [http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/tomasoft/tomahawk/irc_engine/generic/IRCEngine.hpp?view=markup] [http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/tomasoft/tomahawk/irc_engine/generic/IRCEngine.cpp?view=markup]. Within the Tcl_Event structure is our IRCParse C++ object all prepared to be serviced. First we run ''PreEvent(line)'', so certain IRC events such as 'join' can be set into the channels list prior to scripts being run. The same is true in the inverse, as a 'part' event will be removed from the channels list from ''PostEvent()''. Space is left here for the inclusion of an ''Internal Address List'' when I get around to it. Now after ''PreEvent()'' is run, all script providers in LIFO order are handed the IRCParse object. If any of the providers return ''IRCUserScriptProvider::COMPLETED'' (an enum), then the event is considered complete and all processing stops. If no providers claim the event as completed, then the default script will process it. If the default script doesn't handle it, and the mode for the connection is 'debugging' rather than normal, it will be displayed in red in the status window with event name or numeric. Same as: echo "\006CC\006***\006C\006 [event] [join [lrange [args] 1 end]]" status Without the debugging mode, no red color or event code is displayed. Same as: echo "*** [join [lrange [args] 1 end]]" status ---- Here's an example of the user scripting and how identical it is to the ''idea''' of XiRCON. Old-time XiRCON'ers will notice, of course, the new use of namespaces and the msgcat package for handling multilingual strings. We must improve on XiRCON, too. package require Xircon 2.0 namespace eval ::IRC { on PRIVMSG { set dest [lindex [args] 0] set msg [lindex [args] 1] ### ignore an empty one. if {![string length $msg]} {complete; return} if {![icomp $dest [my_nick]]} { ### private msg echo "[color highlight]*[color nick][nick][color highlight]*[color private] $msg" query [nick] } elseif {[string index $dest 0] == "\$"} { ### server (global) message echo "[color highlight]<[color nick][nick][color highlight]>[color default] $msg" status } else { echo "[color highlight]<[color nick][nick][color highlight]>[color default] $msg" channel $dest } complete } on $RPL_TOPICWHOTIME { echo "[color change]*** [mc {Topic for}] [color channel][lindex [args] 1][color change] [mc {was set by}] [color nick][lindex [args] 2][color change] [mc {on}] [clock format [lindex [args] 3]]" complete } on $RPL_UMODEIS { echo "[color change]*** [mc {Your modes are}] [color mode]\"[join [lrange [args] 1 end]]\"" status complete } } As in XiRCON, these are the commands available to an event hook for asking about the line that fired the event: [http://tomasoft.sf.net/ircparser.gif] Not shown is '''raw_line''' and it returns the whole thing as a string prior to any processing. '''raw_args'