Quick tutorial: how to write a bead for use with SB6:
[Updated: 1235004551 :: 18 Feb 2009, 16:49 PST (GMT-0800)]
[Updated: 1264381449 :: 24 Jan 2010, 17:04 PST (GMT-0800): SB6-specific information]

This little quick-tutorial is for those who want to write a bead for use [or even inclusion] in StormBot.TCL V6.

The bead format has caused a little confusion, as-to the formatting of the command-registration command. Hopefully, this will straighten out the matter sufficiently. When the format was originally written, it was never intended that there be user submissions into the fray. However, since some donations have come through, I thought I should write something to help coders write their custom beads for their own bots (without having to come to me for syntax help).

This file is now included in the SB6 distribution taballs (all current versions).

=====

To register an SB6 command, use the following command:
SB:register command <command name> <command level: -1 to 1000> <flags | "-none"> [shortcuts to command word] [command PROC to use, or use "SB:proc_<commandword>" by default]

Example:

SB:register command SAY 500 "-chanspec -locklevel" SPEAK SB:proc_say

This will register the "SAY" command, assign it command level 500 (chanowners only), mark it as channel-specific (require the channel name to be implied / specified) and level-locked (can't change the access level via the CMDLEVEL command), allow a synonym "SPEAK" to also be registered, and send it for processing through SB:proc_say.

Breakdown:

     SB:register command: tell SB6 that we're activating a command

     SAY: we're registering the command word "SAY"

     500: we're assigning the command to level =500 access (channel owners (and above) only)

     -chanspec: we're making this a command that requires specific channel access to use (must have the required access level, in a given channel, to use the command in it)

     SPEAK: we're assigning a synonym to the command (so, SPEAK will be processed as SAY when used)

     SB:proc_say: we're assigning this command to the SB:proc_say PROC (command procedure). This is the default, but the option is available if you want have multiple commands share one common PROC.

=====

The "SB:register command" tells SB6 that we're activating a command. There are other options available, and we'll touch upon those a little later.

The command word itself, in the above case, "SAY," is what we want to tell the bot for it to react. "LlamaBot say something" is now available as a command, as SAY is the command word trigger.

The command level, 500, is the access level you must have in a given channel (or globally) to use the command. Otherwise, if you have less-than-that access (or are just completely unknown), the bot will complain that you don't have enough access to use the command. 0 - 500 are channel-specific levels, and 501 - 999 are global levels. Level "-1" (negative one) means "everyone," including people not known to the bot at all; some games (like a trivia game) would require this. Level 1000 is a special level which specifies those who have shell-level access to the bot. This level can not be assigned through the script itself, because it gives special shell-level priviliges to the command user.

The flags are important to the registration process because they define the attributes of the command (how it will be used internally). The command flags, which must be combined in double quotes ("") or braces ({}), are as follows:

     -invalidok :: This allows a command to be used on "invalid" channels (channels the bot has not been given a JOIN command to monitor). Without this flag, any command given to a channel the bot doesn't monitor will result in an "illegal channel" complaint.

     -loggedoutok :: This allows a command to be used by a user without having to use the LOGIN command. Games are an example of commands that shouldn't require a LOGIN. Commands that are marked level "-1" (negative one / unknown user), or "0" (no access on channel, but known elsewhere on the bot) are automatically processed with the -loggedoutok flag.

     -suspendok :: This flag is for commands that can be used by SUSPENDed users (via the SUSPEND command). Otherwise, all commands will fail (and complain) if a SUSPENDed user tries to use them. After all, if you SUSPEND someone's access, that means they shouldn't be able to access anything, right?

     -authcommandcontinue :: This flag is only useful to commands that require a password to be issued (such as with the LOGIN command). This flag internally masks the use of the password when creating the  command-usage log. Then, the command continues processing like a regular command. The companion flag, -authcommand, would normally log the command's use (after masking any potential passwords used with the command), then stop cold (and not process any further) unless you write a special PROC for is, instead of the default SB:proc_<cmd> PROC. This flag, -authcommandcontinue, will make SB6's parser continue with the command to the code you wrote like any other command.

     -authcommand :: This flag is only useful to commands that require a password to be issued (such as with the LOGIN command). This flag internally masks the use of the password when creating the  command-usage log. The -authcommand flag will make SB6 log the command use, then stop cold (and not process any further) unless you write a special PROC for is, instead of the default SB:proc_<cmd> PROC. The companion flag, -authcommandcontinue, would normally continue processing like a regular command after logging the command's use (after masking the potential password use).

     -chanspec :: This flag specifies that you must specify a target channel for the command when used in /MSG or .DCC (DCC CHAT). Otherwise, by default, the command can be used without the channel specifically listed. Examples of commands that do NOT need a specific channel are most GLOBAL commands at the =700 level (REHASH, RESTART, DIE, RELOAD, BANNER, SAVE). Examples of commands that NEED the target channel specifically stated are "regular" commands used on channels, in which users would have different access levels on each channel (SAY, ADDUSER, BAN, OP). Generally, if a channel owner can use the command, it should have the -chanspec flag (to force an evaluation of WHICH channel the user has access to use the command); if not (s, such as with global commands, then you probably don't need the -chanspec flag.

     -helpcmd :: This flag indicates a command that is only used with the HELP command, but isn't for actual interpretation. For example, the IDENT command is really an internal EGGDROP CORE command, and has nothing to do with SB6. However, there is no real "help" to explain how to use it (except for coders, who should be familiar with it). Therefore, I wrote a HELP routine for the command (IDENT). However, I didn't want it to actually "process" a new IDENT, only to provide HELP information ("LlamaBot HELP IDENT"). This flag marks that a command is NOT to be processed, except for being used with HELP.

     -locklevel :: This flag locks the level of the command to what you specify with the SB:register command. Normally, the CMDLEVEL command allows you to change the access level of ANY command within SB6. The -locklevel flag defeats that ability; the CMDLEVEL command will complain to the user that a command's access level is LOCKED and can not be changed if this flag is used.

     -corecmd :: This flag will mark your command as a "SB6 core command" as opposed to a standard module bead. This is only cosmetic, and visible through the CMDLEVEL command, but allows you to visually sort (group) commands by importance, if you wish. An example of a command that might be marked as a -corecmd would be ADDUSER (as you can't use SB6 without being added to the bot in the first place).

     -nobind :: This flag bypasses the automatic binding of your command word for /MSG or .DCC use. Normally, your command word is bound in 3 ways: in-channel ( LlamaBot SAY something ), in-message ( /msg LlamaBot SAY #Llamas something ), and in-partyline ( .SAY #Llamas something ). The -nobind command skips binding the in-message (/MSG) and in-partyline (.DCC) bindings, making the command only available in-channel. This can be useful for special commands like "!MP3 Dave Matthews Band-So Right.mp3" that you only want used in the channel and in full view of everyone.

There are six (6) other flags that have not been defined and that may be used in future SB6 releases. You may define and process them ( flags 8 - 13, SB:registercommand ) if you wish, but they may be overwritten in the future.

The synonym, SPEAK, is an optional parameter, which allows you to assign alternate command names when registering the bead. If you do not want to do this here, including nothing, or use "" (null). You can assign synonyms any time with the ABBR (abbreviation) command real-time.

The SB:proc_say optional PROC parameter allows you to direct where the command (SAY) goes for interpretation & processing. By default, "SB:proc_<command word>" is used by the registration process, and you should never need to use this option. However, some commands may have common processing needs (like op / deop / voice / devoice / et cetera), and would benefit from a common PROC to process them. By default, you should skip this parameter.

=====

The other confusing thing in SB6 bead writing is the assigning of the traditional $X variables. While this is nothing more than a hold-over from the SB3 days, most people are used to those variables (as am I). Therefore, I've simplified assigning the variables.

Normally, the following variables are assigned the following values:
     $1 :: 1st word of user text
     $2 :: 2nd word of user text
     $3 :: 3rd word of user text
     $4 :: 4th word of user text
     $5 :: 5th word of user text
     $1e :: from the 1st word of user text to the end
     $2e :: from the 2nd word of user text to the end
     $3e :: from the 3rd word of user text to the end
     $4e :: from the 4th word of user text to the end
     $5e :: from the 5th word of user text to the end

set 0 [lindex $arg 0]; set 1 [lindex $arg 1]; set 2 [lindex $arg 2]; set 3 [lindex $arg 3]; set 4 [lindex $arg 4]; set 5 [lindex $arg 5]
set 1e [lrange $arg 1 end]; set 2e [lrange $arg 2 end]; set 3e [lrange $arg 3 end]; set 4e [lrange $arg 4 end]; set 5e [lrange $arg 5 end]

However, putting this at the top of EVERY SINGLE BEAD was stupid, so I simplified the process.

SB:setvariables

This one, single command sets a whole series of variables to help you with your bead coding.

     $0 - $10 & $e ( "end," the last word of user text)
     $l0 - $l10 & $le (forced lower case versions of $X)
     $u0 - $u10 & $ue (forced upper case versions of $X)
     $t0 - $t10 & $te (forced title case versions of $X - 1st word capitalized (1st letter only), the rest forced lowercase)
     $c0 - $c10 & $ce (forced title case (of all words) versions of $X)
     $r0 - $r10 & $re (forced reversing case versions of $X (all lower case letters are raised; all upper case letters are lowered))

All of the above variations have the "end" versions available as well, for example: $u1e or $c5e.

All above variations (including the "end" variations) include a "j" option, such as: $j1 or $ju5e, which JOIN the text within the normal variable. This is done to help you with handling data with "special characters" in it.

Here are some specific examples of the data. If your command was:

LlamaBot SAY "Thi$" is some sample text; process it with your new command.

... the variables assigned by SB:setvariables would be:

     $0 :: say [by default, this will always be the command word]
     $1 :: \"Thi\$\"
     $2 :: is
     $3 :: some
     $4 :: sample
     $5 :: text\;
     $6 :: process
     $7 :: it
     $8 :: with
     $9 :: your
     $10 :: new
     $e :: command.

     $0e :: say \"Thi\$\" is some sample text\; process it with your new command.
     $1e :: \"Thi\$\" is some sample text\; process it with your new command.
     $2e :: is some sample text\; process it with your new command.
     $3e :: some sample text\; process it with your new command.
     $4e :: sample text\; process it with your new command.
     $5e :: text\; process it with your new command.
     $6e :: process it with your new command.
     $7e :: it with your new command.
     $8e :: with your new command.
     $9e :: your new command.
     $10e :: new command.

     $l1 :: \"thi\$\"
     $u1 :: \"THI\$\"
     $t1 :: \"Thi\$\"
     $c1 :: \"Thi\$\"
     $r1 :: \"tHI\$\"

     $j1 :: "Thi$"
     $jl1 :: "thi$"
     $ju1 :: "THI$"
     $jt1 :: "Thi$"
     $jc1 :: "Thi$"
     $jr1 :: "tHI$"

[The same pattern applies for the $2 - $10, $e, $1e - $10e variables.]

All of these variable can be set by the SB:setvariables (or just "SB:smv") IFF (if-and-only-if) you call the PROC with "arg" as the data variable:

proc <procname> {nick host handle chan arg} { procedure text }

If you use any other variable name except "arg," SB:setvariables will choke and complain (TCL error exception thrown: no such variable "arg"). All other beads are written as such; it would be easier to keep in the pattern with your code.

=====

Special topic: command flags

I have also included the ability to assign unique flags to your command words. These are unix-style flags (I believe "POSIX 1002.3 compliant" is the right explanation). These are single-dash flags (used like *nix --flags) used AFTER the channel designation (if specified).

Example: LlamaBot say #Llamas -caps What's that line from "I like Chinese?" Wo ai zhong hua ren?

You can write whatever code you want to process the flags. The commands SB6 adds to help you with flags are the following:

replace "SB:setvariables" with this:

The SB:setvariables command processes the $ARG variable (remember I said you need to use "arg" above?), and processes as such:

     $flags :: will hold the list of VALID flags the user used in the command; illegal flags are not included here and would be considered regular text.

     $arg :: all normal text AFTER the valid flags were removed.

Therefore:

LlamaBot say #Llamas -caps What's that line from "I like Chinese?" Wo ai zhong hua ren?

... against ...

proc SB:proc_say {nick host handle chan arg} {
     checkflags5 flags arg $arg "-caps -nocaps" 1 1
     # $flags now = "-caps"
     # $arg now = "What's that line from \"I like Chinese?\" Wo ai zhong hua ren?
     # $1 now = "What's"
     # $5 now = ""I" (double-double-quote only exists to clarify the defined text here)
     # $j7e now = "like Chinese?" Wo ai zhong hua ren?"

     set text $j1e ; # Note the "j" in there, meaning: JOIN the text (remove the escaping of special characters)
     if [isvalidflag $flags -caps] { set text $ju1e }; # Again, note the "j" in there. Also, "u" is forced upper-case
     putserv "PRIVMSG $chan :You said: $text"
     return 0

     # The printed text would be, from this example:
     # WHAT'S THAT LINE FROM "I LIKE CHINESE?" WO AI ZHONG HUA REN?
}

=====

Special topic: "special character" processing

When a user's command text is processed, it's put in a pseudo-SPLIT format for processing; this is a security measure to provent maliciously-formed text strings from causing problems for you & your bot. Sometimes, this will affect how you should process the data, sometimes not. I'll explain the difference a bit later.

Any "special" characters will be escaped when they arrive at your PROC. Special characters are defined as the following list:

{ } [ ] \ " $ ;

They will have a backslash (\) prepended to each of them when they arrive:

\{ \} \[ \] \\ \" \$ \;

When you are processing items of data like channel names, or text to be output right back to the command user / channel (such as the SAY command), use the JOIN to remove the escaping:

Example (if you used SB:setvariables, as noted above):
     print $nick "You said: $1e" (You said: I only have \$5.75 in my bank account!)
     print $nick "You said: [join $1e]" (You said: I only have $5.75 in my bank account!)

     print $nick "You said: $j1e" (You said: I only have $5.75 in my bank account!)

===== Writing new beads: outputting text =====

When writing a bead for SB6, the one thing you need to be able to control the output of data or replies to commands. Through SB6, you are given several options.

The standard way to output through EGGDROP is:

putserv "PRIVMSG TARGET :text"; # Note the syntax: PRIVMSG<space>target<space>:<text>

All output from EGGDROP is queued (in order)

You also have two other queues: the 'help' queue (which has the lowest priority), and the 'mode' queue, a.k.a. 'quick' queue (highest priority). The PUTSERV above is the 'standard' queue and has medium priority. What this all means is: the mode queue will empty first, line-by-line. Then, only when that it empty, then EGGDROP will start emptying the standard queue. When that is finally empty, the help queue will finally dump out. However, if you throw something into a higher queue while a lower queue is draining, the higher queue will 'interrupt' (because of its higher priority) and the process will start over with the mode (quick) queue.

With the above 3 commands (putserv, puthelp, & putquick), you have an additional option: you can put your next output at the head of the =that= queue with the "-next" flag.

proc test_output {message} {
    putserv "PRIVMSG #Llama :This will come out third: $message"
    puthelp "PRIVMSG #Llama :This will come out last: $message"
    putquick "PRIVMSG #Llama :This will come out first: $message"
    putserv "PRIVMSG #Llama :This will come out second: $message" -next
}

Now, when you're using SB6, this is all easier. I've made several programming adjustments to allow an easier output syntax:

print $nick "<message>"

Whenever you use any command, key data about your output preferences (cf. SET OUTPUT) are stored for later use; the PRINT command is such use. Every time you use the PRINT command, your output settings are recalled and you can get your bot to respond to your commands in a much-easier-to-remember syntax.

To send a regular NOTICE and MSG messages, I've added appropriate commands:

msg $chan "Hey, channel! I see you there!"
notice $nick "Hey, $nick, I had a question to ask you if you had some time on your hands."

To use the quick or help queues with these 3 commands (print, msg, & notice), simply add the letter to the command of the queue you want to use.

printh $nick "This is help information for a command, so you should use the HELP queue for it. That way, other users' command replies aren't stuck behind your HELP request output."

msgq $nickserv "identify password"; # Quickly auth to NickServ so I don't get killed!

noticeh $nick "This is more help information."

And, lastly, with the later 1.6.X EGGDROP versions, there is one more option: raw output. Raw output does not use a queue; it dumps straight out to the server. This is useful when writing a script that needs real-time output speed, such as an oper (IRC) bot. There is a special syntax for raw output, but SB6 handles that for you. There are two ways to utilize it.

The first way is just another implementation of the PRINT / MSG / NOTICE commands:

printr $nick "Yes, ma'am, I /killed $target as requested."
noticer $nick "That website replied: the first 25 digits of PI are:  (3.)14159 26535 89793 23846 26433"
msgr $chan "Someone just said a bad word! Do I have to kick $nick out for such language?"

The second way is to use the RAWOUTPUT command. This is more appropriate for server commands (mode changes or kicks):

rawoutput "MODE $chan -o LlamaUser16"
rawoutput "KICK $chan LlamaUser256 :You are being kicked for mass-deops"

Here's a sample of how an easy bead could look:

proc SB:proc_parrot {nick host handle chan arg} {
	SB:setvariables; # See appropriate forum entry about setting default variables to explain this
	msgq chan "$nick said: $j1e"; # Use the quick queue
	noticeh $nick "I repeated that (\"$j1e\") to $chan as requested."; # Use the help queue
	return 0; # Always remember to RETURN your commands!
}

Remember to 'escape' your special characters! ( :


