You are on page 1of 27

TCL Starter Guide - EGGDROP

This guide 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?
Output Commands (messages, notices etc...)
Files (Reading, Writing, Modifying)
Special Charachters
Formatting Text
Raw Events
If, Elseif, Else
Error Handling

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.

FLAG S - 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.

EVEN T - 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

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

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

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).

botn ick - Your eggdrops nickname (Lamestbot)

botn ame - Your eggdrops hostmask

serv er - The current server the eggdrop is connected to and
what port the bot is connected on
serv eraddr ess - 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

vers ion - 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')

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

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

serv er-onl ine - The unixtime value for when the bot
connected to its server

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

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

hand len - 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)

conf ig - 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.

puts erv - this uses a normal queue - If you want to stick
to just one method use this.
puth elp - 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.
putq uick - 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 time r and
utim er . 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

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

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
[tim erexis ts] and [uti merexi sts] 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

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

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
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

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

{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

[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




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.
o 0 ― white

o 1 ― black

o 2 ― darkblue

o 3 ― darkgreen

o 4 ― red

o 5 ― brown

o 6 ― magenta

o 7 ― orange

o 8 ― yellow

o 9 ― lightgreen

o 10 ― darkcyan

o 11 ― lightcyan

o 12 ― lightblue

o 13 ― pink

o 14 ― darkgrey

o 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

[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

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


Poss ible o perato rs:

$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

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!