This will go into some of the basics of Tcl coding with eggdrops. You will learn some of the basic Tcl syntax and how to make short scripts. If you do come across any errors in this Tcl document please email me (Gh0st) using the contact page and I will try and fix it post haste.

What is Tcl? 

Tcl (originally from "Tool Command Language", although conventionally referred to Tcl. It is a scripting language that was originally designed by John Ousterhout while he was a Professor at the University of California, Berkeley.

Its a powerful scripting language that is generally considered easy to learn (well i guess anything starting out is an uphill struggle, but in time you will get the idea). The structure is very nice and Tcl has a dynamic nature where everything can be redefined on the fly, as well as having the ability to manipulate data types as strings. By the end of this guide I hope you will have enough knowledge to start you off on your Tcl experience.


The first thing you need to understand about Tcl scripting is how to trigger an event. Events in Tcl generally are triggered by binds.

Binds are normally formated as:

BIND [type] [Flags] [Event] [NameofProc]

BIND - this sets up the trigger event

TYPE - this tells us what kind of trigger to look out for (you can only have 1 "type" per bind). Common types include:

PUB - Public commands (i.e. the first word of a line written in an IRC channel)
MSG - The first word of a private message sent to an eggdrop
JOIN - When somone joins a channel.
PART - When a user leaves a channel.
PUBM - As PUB but can match a string of words, rather then triggering on the first word of a line. e.g. can be set to trigger on "hello nick" specifically.
MSGM - As MSG but can trigger on a string of words, rather then looking just for the first word in a line

There are many more but you get the idea of some of the types of binds.

FLAGS - This checks against people in the bots userfile matching the specific flag e.g. if you was to make a command like "!kick somone" you would only want certain users to be able to access that command.

EVENT - well this is where you tell the script what to look for, e.g. what the actual trigger is (!food etc).

PROC - once it has been triggered what to do next. This basically tells the script where to go next and will point it in the direction fo the procedure to follow once triggered.

some examples:

BIND JOIN - * proc_join

A join trigger - this will trigger when anyone, with any hostmask joins a channel and use a procedure called "proc_join" to formulate what to do next.

BIND PUB o|m !hello pub_hello

This will trigger when somone types "!hello" as the first word in any channel. Note the o|m - these represent the flags for a user the bot will look for - The first o represents a global flag, the second m a channel flag. This means the bot will only look for a !hello from users with those specific flags in its userlist.


Procs are where all the binds are routed and where much of the programming as to what outcome an event will have takes place.

The general layout of the proc follows the following syntax

proc NameOfProc { argumentList }{

commands/arguements etc...

blah blah blah blah...


The argument list for data being routed from a channel will often look like: {nickname Userhost Handle Channel Rest} - but this obviously depends on where the variables are being routed from, as a proc does not necessarily have to have any arguements

Proc pub_hello {nick uhost hand chan rest} {




As you see above the arguments can be shortened and they can even be renamed to anything you choose, but it always is best to keep the same convention to make things easier to follow e.g.

proc pub_hello {n u h c r} {




A list of the arguements possible for each event is included (in) Tcl-commands.doc (available on our download section and also distributed with eggdrops).


Varibles are simply set as follows

set colour "blue"

set country "Britain"

set number "8"

Varibles can be unset just as quick and at any time

unset country

if you want to retrieve a variable you would use a $ infront of the variable name you just set e.g.


Varibles can be changed, unset or set to anything else at any time during a script. One thing to note is that if a variable is set outside a proc you need to have a global declaration of the variable before you can use it within that proc. If set within a proc this is not required if recalling the variable in the same proc. An example of this is below.

set country "holland"
set colour "red"
proc some_proc { ArguementList } {
global colour country
set number "4"
putlog "$colour $country $number"

As you can see country and colour were set outside the proc and where declared as global variables within the proc. number was set within the proc and did not need any such declaration.

If a variable is a number you can increase it or decrease it by using incr and decr respectively.

incr $number
decr $number
incr $number 3

The first 2 will increase or decrease the number by one, while the 3rd example will specify how much to increase a number by.

There are also several predefined Global varibles that are available. These all need to be declared as a global varible if you are using them inside a proc (i.e. global botnick at the beginning of the proc).

botnick - Your eggdrops nickname (Lamestbot)

botname - Your eggdrops hostmask (LamestBot!

server - The current server the eggdrop is connected to and what port the bot is connected on (

serveraddress - The server and port you are connected to corresponding to the bots internal server list, this does not necessarily match what the server calls itself (

version - The version of your eggdrop - the first item will be the text format, the second the numerical format of the version and any following items will be the names of patches added ('1.6.17 1061700'  & '1.6.11+channel_tcldocs 1061103 CVS 1021687856 channel_tcldocs')

uptime - The uptime of the bot - displays unixtime value when the bot was started (1135791932)

numversion - The current numerical version of the bot as in $version MNNRRPP (1061700) ― M - major release number NN - minor release number RR - sub-release number PP - patch level for that sub-release

server-online - The unixtime value for when the bot connected to its server

lastbind - The last command binding that was triggered, allowing you to supposedly identify which proc was triggered.

isjuped - Will return the value 1 if the bot has been juped, 0 otherwise.

handlen - the value of the current handlen defined in src/eggdrop.h - i.e. the legth of the nickname the bot will support in its internal userlist. (9)

config - The name of the config file that the eggdrop is currently using. (egg.conf)

Please note that all config file variables are also global.

Output Commands

The first place you might was to output a command might be to a channel. This can be achieved in various ways, most achieving the same result. If i wanted to write "Good Morning" to a channel called #egginfo I could use any of the below:

putserv "PRIVMSG #egginfo :Good Morning"

puthelp "PRIVMSG #egginfo :Good Morning"

putquick "PRIVMSG #egginfo :Good Morning"

All three of the above would do exactly the same. The difference evolves around how quick the command is sent to the server to process.

putserv - this uses a normal queue - If you want to stick to just one method use this.
puthelp - Gives a command lowest priority.. you would use this if the bot is performing a non important function i.e. an entertainment script where the quickness of a reply isn't critical.
putquick - Sends a command to the server instantly - use for more critical maters.

If you want to notice or message a user all of the above can also be used. In a message you only need change the channel name for a nick, if a notice you would need to change both PRIVMSG and the channel to a nick. Putserv is used for the examples below.

putserv "PRIVMSG Gh0st :Good Morning"

putserv "NOTICE jondow :Good Morning"

Other places you might want to output values to include the dcc partyline. There are 2 commands that you might want to use for this. "PUTDCC" and "PUTLOG". The difference between the 2 is that PUTDCC will send information to a single user on the partyline of the bot, whereas everyone logged onto the bot would be able to see what was sent via putlog.

putlog "Blah blah blah...."

putdcc $idx "ho hum"

The $idx in putdcc refers to the socket a user is connected to the partyline with. You would get this from the argumentlist in a proc:

bind dcc o|o hello proc_hello

proc proc_hello {hand idx rest} {

putdcc $idx "Hello $hand"


For dcc binds those are the 3 arguments that are sent to a proc. hand refers to handle, the nick you use on the bot.

The final output that comes to mind is ctcp events. Again you can use putserv, puthelp or putquick to process ctcp. To get the bot to CTCP VERSION a user I would use:

putserv "PRIVMSG NickName :\001VERSION\001"

Explaining the above \001 is the ascii character that IRC servers use to process CTCP EVENTS.

To ping a user it is slightly different, as it requires a [unixtime] tag to calculate how long the ping actually was. [unixtime] is actually the time from 00:00:00 UTC, January 1, 1970, measured in seconds.

putserv "PRIVMSG NickName :\001PING [unixtime]\001"

This will of course only ping a user it will not return any results to you. To do that you would have to use ctcr (CTCP reply events) as below:

BIND ctcr - PING ProcName

PROC ProcName { nick uhost hand dest keyword rest} {

putserv "PRIVMSG #botnet :$nick's ping reply was [expr [unixtime] - $rest] Seconds.


The above would send any ctcp ping reply sent to the bot to a channel called #Botnet. expr is used to do any calculation addition, subtraction, multiplication etc. Above it would take the current unixtime and subtract it from the time that was sent back with the ping reply (which would be identical to the one you sent out when you first pinged the user).


There are 2 timers that are available for use timer and utimer. Timers are useful if you want the bot to have a certain amount of delay before it continues to process a command. Situations that timers are commonly used are delaying voicing and/or opping users, or displaying certain a text block in a channel after every x minutes.

timer minutes ProcName|Command

This command will execute the specified ProcName in x minutes, as specified.
Returns: TimerID

utimer seconds ProcName|Command

This command will execute the specified ProcName in x seconds, as specified.
Returns: TimerID


Will return a list of minutely set timers that have not been executed yet.
Returns: List of timers in the format of "{minutes ProcName TimerID}"


Will return a list of secondly set timers that have not been executed yet.
Returns: List of timers in the format of "{seconds ProcName TimerID}"

killtimer TimerID

killutimer TimerID

These will both remove a timer and/or utimer from the queue to be executed.

The following 2 commands check if a timer exists. Both [timerexists] and [utimerexists] are part of alltool.tcl, so remeber to load this if you plan on using either of these. Useful for restarting a timer if it doesn't exist or checking it exists before killing it.

alltool.tcl snippet

proc timerexists {command} {

foreach i [timers] {

if {![string compare $command [lindex $i 1]]} then {

return [lindex $i 2]





proc utimerexists {command} {

foreach i [utimers] {

if {![string compare $command [lindex $i 1]]} then {

return [lindex $i 2]





timerexists ProcName

utimerexists ProcName

The ProcName is the one that the timer was suppost to execute. Both commands return the TimerID

Now some examples of how a tcl script would timerid of a proc and kill it:

if {[set var [timerexists ProcName]]!=""} {

# If the timer exists, i.e. it did not ="", the script would continue, setting var to the TimerID

killtimer $var


This former example is case sensitive, unlike the following one.

foreach var [utimers] {

   if {[string equal -nocase ProcName [lindex $var 1]]} {

# This checks to see if the utimer in this case exists

   killutimer [lindex $var 2]

# This line kills the utimer, as it is the 3rd element of the varible $var.


# break stops the search, as the timer is found and there is no need to continue.




Files are useful if you have a lot of variables that you are collecting and need to access those at a later point.

A good thing to start off with is to see if a file already exists

if {[file exists FileName]} { commands }

This will follow through with the commands if the file exists, or do nothing if it doesn't. It is always useful to know whether you need to create a new file or start off by amending an existing one.

Before you do anything to a file you must open the file.

open FileName [access]

This is done as follows (where x can be any Varible).

set x [open FileName r]

This will open the file for reading only, the file must already exist. If you do not specify the permissions it will be "r" as default.

set x [open FileName w]

Open the file for writing only. Truncate it if it exists. If it doesn't exist, create a new file.

set x [open FileName a]

Open the file for writing only. If the file doesn't exist, create a new empty file. Set the initial access position to the end of the file.

set x [open FileName r+]

Open the file for both reading and writing; the file must already exist.

set x [open FileName w+]

Open the file for reading and writing. Truncate it if it exists. If it doesn't exist, create a new file.

set x [open FileName a+]

Open the file for reading and writing. If the file doesn't exist, create a new empty file. Set the initial access position to the end of the file.

Well you should be able to both create and open files at this point.

Once you are done with a file you should remember to close it again, rather then the bot accessing it continuously.

close $x

The next step with a file is how to actually write to it, while it is open. Well the command for that is "puts"

puts [-nonewline] [FileName] [String]


puts abc.txt "Hello World!"
puts $x "The fox jumped over the fence"
puts $x -nonewline "Simply amazing"

The -nonewline supresses a newline charachter being added after each string.

You need to remeber to open the filename before you can put any data into it. If the file has not been opened you will get an error similar to "can not find channel named "FileName.txt"".

Now lets put everything together:

set x [open mylogs.txt
puts $x "I love eggdrops"
close $x

Following on from writing to a file is reading from a file. The command used for this in Tcl is gets. gets will read each line of the file until it reaches the end of a file, at which point it will return "-1".

gets $x [variable]

The variable is where gets will temporarily store the line it just retrieved. $x comes from the variable set when opening the file.

We see an example of this below, where a text file can be read to a channel and/or user (depending on the speed you have it reading you might need to use timers to slow it down a bit).

set x [open story.txt r]
while {[gets $x line] != "-1"} {
putserv "PRIVMSG jondow :$line"

close $x

The eof command returns the integer 1 when the end of a file is reached. This is another useful way to get the bot to stop reading once the file has ended. This differs slightly from above as gets may also return -1 if it has not got sufficient data to proceed, wheras the eof is specifcally designed for the end of a file.

set x [open story.txt r]
while {![eof $x]} {
set line [gets $x](or: 'gets $x line')
putserv "PRIVMSG #botnet :$line"
close $x


Strings are variables that consist of more then a single element

set where {

The above is an example of a string where several countries have been set in a single variable.

If somone writes a line in a channel, the line can be considered as a string of words and can be searched for words etc.

The first way to pick up somthing from a string is lindex. Lindex is used to pick out a single variable from a string depending on its position in the string.

If we use the variables above we would have a string as follows:

{Netherlands England Italy Romania Russia}

Now say we want the 2nd country in the list we would use:

[lindex $where 1]

Note that lindex starts from 0 so the first variable in a list would be equal to position zero. The above would return "England". Likewise if you wanted to return Russia you would use:

[lindex $where 4]

Another command lrange works in a similar way. Lrange can be used to pick out several variables from a string rather then just 1. Its general syntax is:

lrange string FirstIndex LastIndex

Lets say you wanted to return the 3rd and 4th variables in the sring above you would use:

[lrange $where 3 4]

With lrange all the variables have to be together, although you can use "end" to refer to the last variable. You can also use "end-2", "end-4" etc in the place of the first or last index place to specify what part of the string you require. All of the below are aceptable.

[lrange $where 2 end]

[lrange $where end-2 end]

[lrange $where 1 end-3]

[lrange $where 0 end-1]

If you just want to get a specified number of charachters from a list rather then words like lrange you would use "string range"

string range "abc! def! efg!" 0 5

This would return the 1st five charachters i.e. "abc d". The space is also counted in this instance.

Trimming strings of leading or ending charachters can be done using "trimleft", "trimright" or "trim"

string trimleft "!!abc def!!" !

This would return "abc def!!"

string trimright "!!abc def!!" !

This would return "abc def!!"

string trim "!!abc def!!" !

This would return "abc def"

The next important thing with strings is to search through them for somthing. This can be done using "lsearch" or "string match". Both of these have their own roles to play depending on what you want to do. lsearch generally has a lot more options then string. here.


string match ?-nocase? pattern string

lsearch ?-options? string pattern

With string you can use wildcards in the patter (* and ?), you can also use the nocase option so when checking it pays no attention to the case difference between the string and pattern.

[lsearch -exact $where Netherlands]

The above would return 0 if the pattern (Netherlands) matched any element in string. If there was not match returned -1 would be returned instead. The -exact option means that it has to contain exactly the same string as the pattern. Regardless of the -exact option, the pattern and string are case sensitive at all times.

The difference wit string match is that it will not look for a single occurance of an element within a list, but instead will see if the pattern you specify exists as you have specified it. If a pattern match is found 1 is returned else the value of 0 is returned.

[string match {a} {a b c d e}]

Returns 0

[string match {a*} {a b c d e}]

Returns 1

[string match {*b*} {a b c d e}]

Returns 1

[string match {c} {a b c d e}]

Returns 0

The ? can be used as a wild card in the pattern, to represent 1 charachter in string e.g.

[string match {a????????} {a b c d e}]

Returns 1

Two useful string attributes are toupper and tolower. These can be used in conjuction with lsearch to make sure that all the variables are transformed into a single case before trying to match them. Toupper will transform all charachters in a variable or string into uppercase, whereas tolower will transform them into lowercase.


string toupper string ?first? ?last?

string tolower string ?first? ?last?

The ?first? and ?last? refer to the index of the text within a string to change the case of. Neither of these need to be specified if you wish to convert an entire string and/or variable to a single case.

trim, trimright and trimleft can aslo be very important, depending on what you are scripting. Thesr vasically remove leading and trailing charachters from a string ot variable.


string trim string ?chars?

string trimleft string ?chars?

string trimright string ?chars?

trim will strip both leading a trailing charachters matching the ?chars? you specified. trimleft will only strip the leading charachters and trimright, as you can imagine will only trim traling charachters. Note if you do not specify any ?chars? then it will assume that you only want to trim any white spaces.

Lets see some examples of this:

string trim "!!!abc!!!" !

=====> abc

string trimleft "!!!abc!!!" !

=====> abc!!!

string trimright "!!!abc!!!" !

=====> !!!abc

string trim "!!!abc!!!  "

=====> !!!abc!!!

Special Charachters


- Used to read a variable that has been set elsewhere


- Evaluates everything between the brackets - i.e. trys to output the results of the commands between the brackets


- question marks - can be used to encase variables etc..


- commands can be set between these brackets without being evaluated at the end of the process.


- Line continuation


- A line beginning with a hash, in Tcl is considered a comment and will not be processed by the script.

Formatting Text

Text can generally be formatting on irc in the following styles:





and in various....

Any format that you can achieve with your own irc client can be achieved by an eggdrop.

To achieve any of these you use the special charachter above "\". This followed by an ascii code will output the text in the format you want. With each different format you require, the ascii code changes.

\003 - is used for colour

\002 - is used for bold

\037 - is used for underline

\026 - is used for reverse test

If i wanted to type "hello world" in red i would use:

\0034 Hello World \003

The number 4 represents red and the \003 at the end tells it to stop formatting the text in red.

The colours available are as follows and are identical to the 15 mirc colours.

  • 0   ― white
  • 1   ― black
  • 2   ― darkblue
  • 3   ― darkgreen
  • 4   ― red
  • 5   ― brown
  • 6   ― magenta
  • 7   ― orange
  • 8   ― yellow
  • 9   ― lightgreen
  • 10 ― darkcyan
  • 11 ― lightcyan
  • 12 ― lightblue
  • 13 ― pink
  • 14 ― darkgrey
  • 15 ― lightgrey

You can also use \003 to have background colours.

\003(fg,bg)Your text here\003

Both the foreground (fg) and background (bg) colours can be any of the 15 numbers listed above.

The other formatting is similar except that you do not need to specify a colour or anything like that e.g. for reverse text you would use:

\026 Hello World \026

Of course you can combine as many as these formatting elements as you like to achieve the desired effect.

One last bit of formatting that you may need is the action (the /me command on irc). This is achieved much the same way as above, using \001ACTION.

\001ACTION Hands everyone a can of cola \001

You only need to use \001 (required) to end the event and not \001ACTION in this case. The above would give a result on irc like so:

* BotNick Hands everyone a can of cola

Associative Arrays

Along with variables array can be used to set and return lists of items sorted and unsorted.

We would set colours in a single array as follows

set colour(Red) Red

set colour(Teal) Teal

set colour(Blue) Blue

set colour(Pibk) Pink

set colour(Green) Green

set colour(Grey) Grey

To see how many elements you have in an array or to see if an array even exists use the following syntax

array exists arrayName

array count arrayName

Now to get a list of colours you just set you could do the following

[array names colour]

This would simply return a list of all the colours you set in the order you set them and you would get:

Red Teal Blue Pink Green Grey

Outputing the list in an ordered (alphabetic) fashion can be done by using lsort

lsort [array names colour]

and this would give...

Blue Green Grey Pink Red Teal

Raw Events

Raw events are regular scripting events when a server sends you a message as a result of somthing you do. Items like whois, who and names are all part of raw events. Raw events are caught through binds in Tcl scripting as follows:

bind raw flags numeric ProcName

The 311 represents the first line of a whois respoce you see (Nickname ident * RealName).

An Example of the bind and proc setting a nickname and realname field to variables.:

bind raw - 311 check_whois

proc check_whois {nick ident uhost} {

set nick [lindex $uhost 1]

set name [string trimleft "[lindex $uhost 5]" :]



There are liternally hundreds of server responces and their relative numerics are listed in RFC1459 (the original IRC protocol). Updated versions of the protocol can be found in RFC in RFC2810, RFC2811, RFC2812 and RFC2813 (you will find the RAW numeric section in RFC2812).

I find that they are also more conveniently listed in Jeepster´s IRC Numeric Reference - this is a windows help file.

If, Elseif, Else

The if statement can be used to determine wether a statement is true or false and check if a set of varibles match. You can perform specific commands depending on the outcome of the If statement

if {varible1 operator varible2} {

perform these set of commands

} else {

perform this set instead


Possible operators:

$a == $b

$a and $b are equal.

$a > $b

$a is greater than $b

$a < $b

$a is lesser than $b

$a >= $b

$a is greater or equal to $b

$a <= $b

$a is lesser or equal to $b

$a != $b

$a is not equal to $b


logical "and" - if you want to match a string of varibles.


logical "or" - If only 1 of a string of queries needs to be matched.

if {var1 == var2} && {var1 == var3} {commands}

You can also use some predefined procedures to see if somthing is true. The following are pretty self explanatory and will return 1 if true, or 0 if false. These are all useful for checking a variety of varibles on IRC.

isbotnick <nick>
botisop [channel]
botisvoice [channel]
botonchan [channel]
isop <nickname> [channel]
wasop <nickname> <channel>
isvoice <nickname> [channel]
onchan <nickname> [channel]

The way these would be used in an if function is similar to that above. Remember that the varibles within the <> are required, wheras the varibles within [] i.e. the channel is optional. If this is not specified the bot will check all the channels it is on for a match to your if function.

if {[isop $nick $chan] == "1"} {commands}

shortened and equivalent to:

if {[isop $nick $chan]} {commands}

This will perform the commands if nickname $nick is an op on channel $chan, i.e. if the statement is true (equal to 1).

if {[isop $nick $chan] == "0"} {commands}

shortened and equivalent to:

if {![isop $nick $chan]} {commands}

These 2 will perform the commands if the statement is false (equal to 0). the ! means it will proceed if the statement is opposite to what is in the square brackets [ ].

The following are examples of how some of these functions would work.

if {[botisop $chan]} {

putserv "privmsg $chan :I am indeed oped on $chan"

} else {

putserv "privmsg $chan :I have no @ in $chan"


if {[botisop $chan]} {

putserv "privmsg $chan :I am indeed oped on $chan"

} elseif {[botisvoice $chan]} {

putserv "privmsg $chan :I have a voice on $chan"

} else {

putserv "privmsg $chan :I have no @ in $chan"


Error Handling

When you create scripts you will undoubtedly run into some teething problems when running them for the first time. Often this can be because of missing braces { and }. Too few closing braces and the eggdrop will crash. This can usually be the most common reason for the bot to crash after a new Tcl is loaded.

Other problems can be due to typing errors in commands, or using an unallowed syntax. Once the script is loaded, it may not appear to work. If the bot isn't lagged this can only mean an error in the script. Usually in thie scenario it is helpful to have the Tcl echo the important sections of a proc to "putlog" (or another output if you prefer. This helps you follow the script step by step and better assess where the problem lies.

For now good luck in your scripting creativity!