This is the new home of the egghelp.org community forum.
All data has been migrated (including user logins/passwords) to a new phpBB version.


For more information, see this announcement post. Click the X in the top right-corner of this box to dismiss this message.

utimer not working???

Help for those learning Tcl or writing their own scripts.
Post Reply
F
Fill
Halfop
Posts: 80
Joined: Sun Jan 18, 2009 6:08 pm

utimer not working???

Post by Fill »

Hi folks,

I'm trying to create a script that sends a warning message some seconds after a user joins the channel, but it is not working. The script is like this:

Code: Select all

bind join - "#cyber-world *" caps:join
bind nick - "#cyber-world *" caps:nick

proc caps:join { nick uhost hand chan } {
if {[regexp {^[A-Z]+$} $nick]} {
	set letras_nick [split $nick {}]
	utimer 15 [puthelp "PRIVMSG $chan :message"]
	}
}
However it seems to "ignore" the timer and sends the message in the moment the user joins the chan. What's wrong with it?
User avatar
arfer
Master
Posts: 436
Joined: Fri Nov 26, 2004 8:45 pm
Location: Manchester, UK

Post by arfer »

Using square brackets suggests that what they contain is a command and its arguments, if there are any. Command substitution will occur and the result fed back into the utimer statement. In your case the output command executes immediately and returns nothing. Hence the code will be interpreted as simply

utimer 15

Use the command
  • to return the appropriate arguments to the utimer command and have the output occur when the utimer triggers

    Code: Select all

    bind join - "#cyber-world *" caps:join 
    bind nick - "#cyber-world *" caps:nick 
    
    proc caps:join { nick uhost hand chan } { 
    if {[regexp {^[A-Z]+$} $nick]} { 
       set letras_nick [split $nick {}] 
       utimer 15 [list puthelp "PRIVMSG $chan :message"] 
       } 
    } 
    
I must have had nothing to do
F
Fill
Halfop
Posts: 80
Joined: Sun Jan 18, 2009 6:08 pm

Post by Fill »

ahmmm I see. But we must use square brackets with utimers isn't it?
I understood now, but how would I make to execute another proc after the 15 secs.?

Thanks for the help and great explanation
User avatar
arfer
Master
Posts: 436
Joined: Fri Nov 26, 2004 8:45 pm
Location: Manchester, UK

Post by arfer »

It rather depends on whether the proc you want to execute requires arguments to be passed to it, because the syntax for the command utimer is :-

utimer seconds command

That is why, taking the example above, you could not have simply written as follows :-

utimer 15 puthelp "PRIVMSG $chan :message"

That would result in the Tcl error :-

wrong # args: should be "utimer seconds command"

The square brackets group together the command name and its arguments into a single command. However, as we have seen, that will bring about command substitution during a preliminary pass through the interpreter. We simply used the list command to prevent immediate output (during command substitution). A Tcl list of arguments is passed to the utimer command instead of the return value of an output command (in actuality nothing).

Therefore, to answer your question. If the proc you want to execute after a delay does not take any arguments then you don't need square brackets :-

utimer 15 procName

If the proc does take arguments, then you do need square brackets to group them. In which case substitution will occur and to prevent immediate execution you would generally make use of the list command :-

utimer 15
  • I think that's just about the best explanation I can manage from my understanding of Tcl. There are more knowledgeable folk here that could perhaps give you a technically better answer.

    I will tell you this with some degree of certainty. Everybody learning Tcl has fallen foul of this or similar.
I must have had nothing to do
n
nml375
Revered One
Posts: 2860
Joined: Fri Aug 04, 2006 2:09 pm

Post by nml375 »

To get technical:
The reason we recommend using the list command when using timer/utimer is to avoid "double substitution and evaulation". This problem arises because of the way variable and command substitutions are done in tcl.

Contrary to many other programming languages, when you prefix a variable name with $ in tcl, this causes the tcl interpreter to read the variable and replace the $variable with it's content. Same goes for command substitutions ( [command] ). In essence, the command will never see the variable, let alone be aware of it's existence.

The problem with timer/utimer, is that the command line argument gets passed to the tcl interpreter as the timer expires. As such, the interpreter will first check for any variable and command substitutions before executing the command. Hence, we got a second substitution.

The effect of this is perhaps best illustrated with an example:

Code: Select all

#Set tmpnick to contain #evil[die]nick
set tmpnick "evil\[die\]nick"

#msg $tmpnick Hello1!
puthelp "PRIVMSG $tmpnick :Hello1!"

#Now lets create a timer that msg's $tmpnick Hello2! after 5 secondsutimer 5 "puthelp \"PRIVMSG $tmpnick :Hello2!\""
Here one might think that we've been careful and escaped everything properly. For a simple case, such as the Hello1! msg, that would be true. The tmpnick variable is substituted and the command will send an msg to evil[die]nick.
However, in the second case, we don't execute the puthelp command instantly, but store it for later execution:

Code: Select all

#timer command after all substitutions are done:
utimer 5 {puthelp "PRIVMSG evil[die]nick :Hello2!"}
When the timer expires, the second argument is sent to eval:

Code: Select all

eval {puthelp "PRIVMSG evil[die]nick :Hello2!"}
puthelp "PRIVMSG evil[die]nick :Hello2!"
#the interpreter sees [die], and does a command substitution, calling die.
#Your eggdrop dies...
So, why don't we just escape the $ in the first case?
Well, that would work in this simple example, but consider this one, where namespaces becomes a factor:

Code: Select all

proc myproc {nickname} {
 utimer 5 "puthelp \"PRIVMSG \$nickname :Hello3!\""
}

myproc $tmpnick
Here, we call myproc with the content of tmpnick (same as prev. example), so within myproc, nickname will contain that malicious nickname. We escaped the $ so it will not be evaluated immediately, thus we do not have an issue with double evaluation.

Code: Select all

#timer command after all substitutions are done:
utimer 5 {puthelp "PRIVMSG $nickname :Hello3!"}
#and when the timer expires..
eval {puthelp "PRIVMSG $nickname :Hello3!"}
puthelp "PRIVMSG $nickname :Hello3!"
puthelp {PRIVMSG eggdrop :Hello3!}
#This time, we got "lucky", as nickname happens to be defined, and contain your eggdrop's current nickname.
# Had it been anything else, we'd possibly disclose sensitive configuration settings,
# or end up trying to read a non-existant variable.
The idea of using tcl-lists, is that it's a data structure that is designed to preserve contents from being "mangled" by substitutions.

Code: Select all

proc mysafeproc {nickname} {
 utimer 5 [list puthelp "PRIVMSG $nickname :Hello4!"]
}

mysafeproc $tmpnick
First thing, we have a command substitution (list). Within this, the variable will also be substituted:

Code: Select all

list puthelp "PRIVMSG $nickname :Hello4!
#this returns: puthelp {PRIVMSG evil[die]nick :Hello4!}
utimer 5 {puthelp {PRIVMSG evil[die]nick :Hello4!}}
#as the timer expires:
eval {puthelp {PRIVMSG evil[die]nick :Hello4!}}
puthelp {PRIVMSG evil[die]nick :Hello4!}
#Since the "PRIVMSG ..." argument is enclosed with {} rather than "", the tcl interpreter will skip any further substitutions within this argument.
#Puthelp will thus send the message to evil[die]nick
NML_375
F
Fill
Halfop
Posts: 80
Joined: Sun Jan 18, 2009 6:08 pm

Post by Fill »

NIce, understood everything!!! Your explanation was great, thanks for the great help nml375 and arfer. We're always learning :lol:

Once again, thanks a lot. I appreciate this website.

See ya :wink:
Post Reply