diff --git a/docs/DocBook/capabilities.sgml b/docs/DocBook/capabilities.sgml index e161cfb63..a8c6ef6e7 100644 --- a/docs/DocBook/capabilities.sgml +++ b/docs/DocBook/capabilities.sgml @@ -1,4 +1,4 @@ - +
@@ -18,7 +18,7 @@ 0.1 18 Feb 2004 - Initial revision + Initial Docbook translation @@ -48,7 +48,7 @@ the bot a command. Commands, rather than checking for a user level of 100, or checking if the user has an o flag, are instead able to check if a user has the - owner capability. At this point such a + owner capability. At this point such a difference might not seem revolutionary, but at least we can already tell that this method is self-documenting, and easier for users and developers to understand what's truly going on. @@ -68,49 +68,49 @@ “anticapability” for that command. An anticapability is a capability that, instead of saying “what a user can do”, says what a user cannot do. It's - formed rather simply by adding a dash (“-”) to the - beginning of a capability; rot13 is a - capability, and -rot13 is an anticapability. - Anyway, when a user issues the bot a command, perhaps - calc or help the bot - first checks to make sure the user doesn't have the - -calc or the -help - capabilities before even considering responding to the user. So - commands can be turned on or off on a per - user basis, offering finegrained control not often (if - at all!) seen in other bots. + formed rather simply by adding a dash (“-”) to the + beginning of a capability; rot13 is a + capability, and -rot13 is an + anticapability. Anyway, when a user issues the bot a command, + perhaps calc or + help, the bot first checks to make sure + the user doesn't have the -calc or the + -help capabilities before even + considering responding to the user. So commands can be turned on + or off on a per user basis, offering + finegrained control not often (if at all!) seen in other bots. Channel capabilities But that's not all! The capabilities system also supports Channel capabilities, which are - capabilities that only apply to a specific channel; they're of - the form #channel.capability. Whenever a - user issues a command to the bot in a channel, the command - dispatcher also checks to make sure the user doesn't have the - anticapability for that command in that - channel and if the user does, the bot won't respond - to the user in the channel. Thus now, in addition to having - the ability to turn individual commands on or off for an - individual user, we can now turn commands on or off for an - individual user on an individual channel! + capabilities that only apply to a specific channel; they're of + the form #channel.capability. + Whenever a user issues a command to the bot in a channel, the + command dispatcher also checks to make sure the user doesn't + have the anticapability for that command in that + channel and if the user does, the bot won't respond + to the user in the channel. Thus now, in addition to having + the ability to turn individual commands on or off for an + individual user, we can now turn commands on or off for an + individual user on an individual channel! - So when a user foo sends a command - bar to the bot on channel - #baz, first the bot checks to see if the - user has the anticapability for the command by itself, - -bar. If so, it returns right then and - there, compltely ignoring the fact that the user issued that - command to it. If the user doesn't have that anticapability, - then the bot checks to see if the user issued the command over - a channel, and if so, checks to see if the user has the - antichannelcapability for that command, - #baz.-bar. If so, again, he returns right - then and there and doesn't even think about responding to the - bot. If neither of these anticapabilities are present, then - the bot just responds to the user like normal. + So when a user foo sends a command + bar to the bot on channel + #baz, first the bot checks to see if the + user has the anticapability for the command by itself, + -bar. If so, it returns right then + and there, compltely ignoring the fact that the user issued + that command to it. If the user doesn't have that + anticapability, then the bot checks to see if the user issued + the command over a channel, and if so, checks to see if the + user has the antichannelcapability for that command, + #baz.-bar. If so, again, he returns + right then and there and doesn't even think about responding + to the bot. If neither of these anticapabilities are present, + then the bot just responds to the user like normal. @@ -148,14 +148,14 @@ Hard-coded supybot capabilities There are several default capabilities the bot uses. The most - important of these is the owner capability. - This capability allows the person having it to use - any command. It's best to keep this - capability reserved to people who actually have access to the - shell the bot is running on. + important of these is the owner + capability. This capability allows the person having it to use + any command. It's best to keep this + capability reserved to people who actually have access to the + shell the bot is running on. - There is also the admin capability for + There is also the admin capability for non-owners that are highly trusted to administer the bot appropriately. They can do things such as change the bot's nick, globally enable/disable commands, cause the bot to ignore a given @@ -164,40 +164,41 @@ people with the next capability. - People who are to administer channels with the bot should have the - #channel.op capability -- whatever channel they - are to administrate, they should have that channel capability for - op. For example, since I want - inkedmn to be an administrator in - #supybot, I'll give him the - #supybot.op capability. This is in addition to - his admin capability, since the - admin capability doesn't give the person having - it control over channels. #channel.op is used - for such things as giving/receiving ops, kickbanning people, - lobotomizing the bot, ignoring users in the channel, and managing - the channel capabilities. The #channel.op - capability is also basically the equivalent of the owner - capability for capabilities involving #channel - – basically anyone with the #channel.op - capability is considered to have all positive capabilities and no - negative capabilities for #channel. + People who are to administer channels with the bot should have the + #channel.op capability -- whatever + channel they are to administrate, they should have that channel + capability for op. For example, since I + want inkedmn to be an administrator in + #supybot, I'll give him the + #supybot.op capability. This is in + addition to his admin capability, since + the admin capability doesn't give the + person having it control over channels. + #channel.op is used for such things as + giving/receiving ops, kickbanning people, lobotomizing the bot, + ignoring users in the channel, and managing the channel + capabilities. The #channel.op capability + is also basically the equivalent of the owner capability for + capabilities involving #channel – + basically anyone with the #channel.op + capability is considered to have all positive capabilities and no + negative capabilities for #channel. - One other globally important capability exists: - trusted. This is a command that basically says - “This user can be trusted not to try and crash the - bot.” It allows users to call commands like - Math.icalc, which potentially could cause the - bot to begin a calculation that could potentially never return (a - calculation like 10**10**10**10). Another command that requires - the trusted capability is Utilties.re, which - (due to the regular expression implementation in Python (and any - other language that uses NFA regular expressions, like Perl or - Ruby or Lua or …) which can allow a regular expression to - take exponential time to process). Consider what would happen if - the someone gave the bot the command re [strjoin "" s/./ - [dict go] /] [dict go]. + One other globally important capability exists: + trusted. This is a command that + basically says “This user can be trusted not to try and + crash the bot.” It allows users to call commands like + Math.icalc, which potentially could cause the + bot to begin a calculation that could potentially never return (a + calculation like 10**10**10**10). Another command that requires + the trusted capability is Utilties.re, which + (due to the regular expression implementation in Python (and any + other language that uses NFA regular expressions, like Perl or + Ruby or Lua or …) which can allow a regular expression to + take exponential time to process). Consider what would happen if + the someone gave the bot the command re [strjoin "" s/./ + [dict go] /] [dict go].
diff --git a/docs/DocBook/configuration.sgml b/docs/DocBook/configuration.sgml new file mode 100644 index 000000000..e719dac76 --- /dev/null +++ b/docs/DocBook/configuration.sgml @@ -0,0 +1,311 @@ + + +
+ + + + Jeremiah + Fincher + + + Daniel + DiPaolo + + + Daniel + DiPaolo + DocBook translator + + + Supybot configuration system explanation + + + 0.1 + 18 Feb 2004 + Initial Docbook translation + + + 0.2 + 26 Feb 2004 + Conversion to Supybot DTD + + + + + Introduction + + So you've got your Supybot up and running and there are some + things you don't like about it. Fortunately for you, chances are + that these things are configurable, and this document is here to + tell you how to configure them. + + + Configuration of Supybot is handled via the + Config plugin, which controls runtime access to + Supybot's registry (the configuration file generated by the + program you ran). The + Config plugin provides a way to get or set + variables, to list the available variables, and even to get help + for certain variables. Take a moment now to read the help for + each of those commands: get, + set, list, and + help. If you don't know how to get help on + those commands, go ahead and read our + GETTING_STARTED document before this one. + + + + Supybot's registry + + Now, if you're used to the Windows registry, don't worry, + Supybot's registry is completely different. For one, it's + completely plain text. There's no binary database sensitive to + corruption, it's not necessary to use another program to edit it + – all you need is a simple text editor. But there is at + least one good idea in Windows' registry: hierarchical + configuration. Supybot's configuration variables are organized in + a hierarchy: variables having to do with the way Supybot makes + replies all start with + supybot.reply; variables having to + do with the way a plugin works all start with + supybot.plugins.Plugin (where + Plugin is the name of the plugin in question). + This hierarchy is nice because it means the user isn't inundated + with hundreds of unrelated and unsorted configuration variables. + + + Some of the more important configuration values are located + directly under the base group, + supybot. Things like the bot's + nick, its ident, etc. Along with these config values are a few + subgroups that contain other values. Some of the more prominent + subgroups are: plugins (where all + the plugin-specific configuration is held), + reply (where variables affecting + the way a Supybot makes its replies resides), + replies (where all the specific + standard replies are kept), and + directories (where all the + directories a Supybot uses are defined). There are other + subgroups as well, but these are the ones we'll use in our + example. + + + Config plugin commands + + Listing registry contents + + Using the Config plugin, you can list + the values in a subgroup and get or set any of the values + anywhere in the configuration hierarchy. For example, + let's say you wanted to see what configuration values were + under the supybot (the base + group) hierarchy. You would simply issue this command: + + +<jemfinch|lambda> @config list supybot +<supybot> jemfinch|lambda: nick, ident, user, server, password, + channels, prefixChars, defaultCapabilities, defaultAllow, defaultIgnore, + humanTimestampFormat, externalIP, pipeSyntax, + followIdentificationThroughNickChanges, alwaysJoinOnInvite, + showSimpleSyntax, maxHistoryLength, nickmods, throttleTime, + snarfThrottle, threadAllCommands, pingServer, pingInterval, + upkeepInterval, flush, httpPeekSize, and defaultSocketTimeout + + + These are all the configuration values you can set which + are under the base supybot + group. Actually, their full names would each have a + “supybot.” appended on to the front of them, + but it is omitted in the listing in order to shorten the + output. + + + Now, to see all of the available configuration groups + under the base supybot + group, we simply use the --groups flag to + config list: + + +<jemfinch|lambda> @config list --groups supybot +<supybot> jemfinch|lambda: commands, databases, directories, drivers, + log, plugins, replies, and reply + + + These are all the subgroups of + supybot. Again, the full + name of these would have “supybot.” prepended + to them. So really, we have + supybot.commands, + supybot.databases, etc. + + + + An item can show up in both lists if it is a group + that itself has a value. For example, all plugins + fall under this category, as their value is a boolean + value determining whether or not that plugin is to be + loaded when the bot is started. + + + + + Dealing with registry values + + Okay, now that you've used the Config + plugin to list configuration variables, it's time that we + start looking at individual variables and their values. + + + Built-in help for registry values + + The first (and perhaps most important) thing you + should know about each configuration variable is that + they all have an associated help string to tell you + what they represent. So the first command we'll cover + is config help. To see the + help string for any value or group, simply use the + config help command. For + example, to see what this + supybot.prefixChars + configuration variable is all about, we'd do this: + + +<jemfinch|lambda> @config help supybot.prefixChars +<supybot> jemfinch|lambda: Determines what prefix characters the bot + will reply to. A prefix character is a single character that the bot will + use to determine what messages are addressed to it; when there are no + prefix characters set, it just uses its nick. + + + Pretty simple, eh? + + + + Getting/setting registry values + + Now, if you're curious what the current value of a + configuration variable is, you'll use the + config command with one + argument, the name of the variable you want to see the + value of: + + +<jemfinch|lambda> @config supybot.prefixChars +<supybot> jemfinch|lambda: '@' + + + To set this value, just stick an extra argument after + the name: + + +<jemfinch|lambda> @config supybot.prefixChars @$ +<supybot> jemfinch|lambda: The operation succeeded. + + + Now, check this out: + + +<jemfinch|lambda> $config supybot.prefixChars +<supybot> jemfinch|lambda: '@$' + + + Note that we used $ as our prefix + character, and that the value of the configuration + variable changed. If I were to use the + flush command now, this + change would be flushed to the registry file on disk + (this would also happen if I made the bot quit, or + pressed + + Ctrl + C + + in the terminal the bot was running in). Instead, + I'll revert the change: + + +<jemfinch|lambda> $config supybot.prefixChars @ +<supybot> jemfinch|lambda: The operation succeeded. +<jemfinch|lambda> $note that this makes no response. + + + If you're ever curious what the default for a given + configuration variable is, use the config + default command: + + +<jemfinch|lambda> @config default supybot.prefixChars +<supybot> jemfinch|lambda: '' + + + Thus, to reset a configuration variable to its default + value, you can simply say: + + +<jemfinch|lambda> @config supybot.prefixChars [config default + supybot.prefixChars] +<supybot> jemfinch|lambda: The operation succeeded. +<jemfinch|lambda> @note that this does nothing + + + Simple, eh? + + + + + Searching the registry + + Now, let's say you want to find all configuration + variables that might be even remotely related to opping. + For that, you'll want the config + search command. Check this out: + + +<jemfinch|lambda> @config search op +<supybot> jemfinch|lambda: supybot.plugins.Enforcer.autoOp, + supybot.plugins.Enforcer.autoOp.#supybot, + supybot.plugins.Enforcer.autoHalfop, + supybot.plugins.Enforcer.cycleToGetOps, supybot.plugins.Topic, + supybot.plugins.Topic.separator, and supybot.plugins.Relay.topicSync + + + Sure, it showed up all the topic-related stuff in there, + but it also showed you all the op-related stuff, too. Do + note, however, that you can only see configuration + variables for plugins that you have loaded or that you + loaded in the past; if you've never loaded a plugin, + there's no way for the bot to know what configuration + variables it registers. + + + Some people might like editing their registry file + directly rather than manipulating all these things through + the bot. For those people, we offer the + config reload command, which + reloads both registry configuration and + user/channel/ignore database configuration. Just edit the + interesting files and then give the bot the + config reload command and it'll + work as expected. Do note, however, that Supybot flushes + his configuration files and databases to disk every hour + or so, and if this happens after you've edited your + configuration files but before you reload your changes, + you could lose the changes you made. To prevent this, set + the supybot.flush value to + Off, and no automatic flushing will + occur. + + + + + + All done! + + Anyway, that's about it for configuration. Have fun, and enjoy + your configurable bot! + + +
+ + diff --git a/docs/DocBook/example.sgml b/docs/DocBook/example.sgml index 95789a757..8f0987c84 100644 --- a/docs/DocBook/example.sgml +++ b/docs/DocBook/example.sgml @@ -1,4 +1,4 @@ - +
@@ -32,6 +32,11 @@ Updated to match EXAMPLE included with 0.75.0 + + 0.4 + 26 Feb 2004 + Converted to use Supybot DTD + @@ -56,13 +61,12 @@ Creating your own plugin - Using <application>scripts/newplugin.py</application> + Using <script>scripts/newplugin.py</script> - First, the easiest way to start writing a module is to use the - wizard provided, - scripts/newplugin.py. Here's an - example session: + First, the easiest way to start writing a module is to use the + wizard provided, . + Here's an example session: functor% scripts/newplugin.py @@ -169,7 +173,7 @@ Class = Random Describe what you want the plugin to do in the docstring. - This is used in scripts/setup.py in + This is used in in order to explain to the user the purpose of the module. It's also returned when someone asks the bot for help for a given module (instead of help for a certain command). We'll change @@ -177,17 +181,18 @@ Class = Random numbers." - Then there are the imports. The callbacks + Then there are the imports. The + callbacks module is used (the class you're given subclasses - callbacks.Privmsg) but the - privmsgs module isn't used. That's + callbacks.Privmsg) but the + privmsgs module isn't used. That's alright; we can almost guarantee you'll use it, so we go ahead and add the import to the template. Then you see a configure function. This the function that's called when users decide to add your - module in scripts/setup.py. You'll + module in . You'll note that by default it simply adds "load Example" (where 'Example' is the name you provided as the name of your plugin, so in our case it is @@ -204,8 +209,8 @@ Class = Random What you're given is a skeleton: a simple subclass of - callbacks.Privmsg for you to start with. - Now let's add a command. + callbacks.Privmsg for you to start + with. Now let's add a command. I don't know what you know about random number generators, but @@ -213,7 +218,7 @@ Class = Random seed) and they continue (via some somewhat complicated/unpredictable algorithm) from there. This seed (and the rest of the sequence, really) is all nice and - packaged up in Python's random module, the + packaged up in Python's random module, the Random object. So the first thing we're going to have to do is give our plugin a Random object. @@ -225,8 +230,8 @@ Class = Random be careful of is that you call the superclass __init__ method at the end of your own __init__. So to add this - random.Random object to our plugin, we can - replace the pass statement with + random.Random object to our plugin, we + can replace the pass statement with this: @@ -285,40 +290,40 @@ def __init__(self): And that's it! Pretty simple, huh? Anyway, you're probably wondering what all that means. We'll - start with the def statement: + start with the def statement: def random(self, irc, msg, args): - What that does is define a command - random. You can call it by saying - "@random" (or whatever prefix character your specific bot - uses). The arguments are a bit less obvious. - self is self-evident (hah!). - irc is the Irc object - passed to the command; msg is the original - IrcMsg object. But you're really not going - to have to deal with either of these too much (with the - exception of calling irc.reply or - irc.error). What you're - really interested in is the - args arg. That if a list of all the - arguments passed to your command, pre-parsed and already - evaluated (i.e., you never have to worry about nested - commands, or handling double quoted strings, or splitting on - whitespace -- the work has already been done for you). You - can read about the Irc object in - irclib.py (you won't find - .reply or .error - there, though, because you're actually getting an - IrcObjectProxy, but that's beyond the level - we want to describe here :)). You can read about the - msg object in - ircmsgs.py. But again, aside from - calling irc.reply or - irc.error, you'll very rarely be using - these objects. + What that does is define a command + random. You can call it by saying + "@random" (or whatever prefix character your specific bot + uses). The arguments are a bit less obvious. + self is self-evident (hah!). + irc is the Irc + object passed to the command; msg is the + original IrcMsg object. But you're + really not going to have to deal with either of these too much + (with the exception of calling irc.reply + or irc.error). What you're + really interested in is the + args arg. That if a list of all the + arguments passed to your command, pre-parsed and already + evaluated (i.e., you never have to worry about nested + commands, or handling double quoted strings, or splitting on + whitespace -- the work has already been done for you). You + can read about the Irc object in + irclib.py (you won't find + .reply or .error + there, though, because you're actually getting an + IrcObjectProxy, but that's beyond the + level we want to describe here :)). You can read about the + msg object in + ircmsgs.py. But again, aside from + calling irc.reply or + irc.error, you'll very rarely be using + these objects. (In case you're curious, the answer is yes, you @@ -342,13 +347,13 @@ def __init__(self): is the help! Given the above docstring, this is what a supybot does: - + <angryman> jemfinch: random takes no arguments (for more help use the morehelp command) <jemfinch> $morehelp random <angryman> jemfinch: Returns the next random number from the current random number generator. - + 'help <command>' replies with the command name followed by the first line of the command's docstring; there should be @@ -361,21 +366,22 @@ def __init__(self): irc.reply(msg, str(self.rng.random())) - irc.reply takes two arguments, an - IrcMsg (like the one passed into your - function) and a string. The IrcMsg is used - to determine who the reply should go to and whether or not it - should be sent in private message (commands sent in private - are replied to in private). The string is the reply to be - sent. Don't worry about length restrictions or anything -- if - the string you want to send is too big for an IRC message (and - oftentimes that turns out to be the case :)) the supybot - framework handles that entirely transparently to you. Do make - sure, however, that you give irc.reply a - string. It doesn't take anything else (sometimes even unicode - fails!). That's why we have "str(self.rng.random())" instead - of simply "self.rng.random()" -- we had to give - irc.reply a string. + irc.reply takes two arguments, an + IrcMsg (like the one passed into your + function) and a string. The IrcMsg is + used to determine who the reply should go to and whether or + not it should be sent in private message (commands sent in + private are replied to in private). The string is the reply + to be sent. Don't worry about length restrictions or anything + -- if the string you want to send is too big for an IRC + message (and oftentimes that turns out to be the case :)) the + supybot framework handles that entirely transparently to you. + Do make sure, however, that you give + irc.reply a string. It doesn't take + anything else (sometimes even unicode fails!). That's why we + have "str(self.rng.random())" instead of simply + "self.rng.random()" -- we had to give + irc.reply a string. Anyway, now that we have an RNG, we have a need for seed! Of @@ -422,7 +428,7 @@ def __init__(self): arguments are returned in a tuple/list). Yes, we could've just said "seed = args[0]" and gotten the first argument, but what if the user didn't pass us an argument at all? Then - we've got to catch the IndexError from + we've got to catch the IndexError from args[0] and complain to the user about it. privmsgs.getArgs, on the other hand, handles all that for us. If the user didn't give us enough @@ -440,7 +446,7 @@ def __init__(self): sure to remind the user that an error has been encountered (currently, that means it puts "Error: " at the beginning of the message). After erroring, we return. It's important to - remember this return here; otherwise, + remember this return here; otherwise, we'll just keep going down through the function and try to use this "seed" variable that never got assigned. A good general rule of thumb is that any time you use @@ -500,9 +506,9 @@ def __init__(self): to. - The Random object we're using offers us a - "sample" method that takes a sequence and a number (we'll call - it N) and returns a list of + The Random object we're using offers us + a "sample" method that takes a sequence and a number (we'll + call it N) and returns a list of N items taken randomly from the sequence. So I'll show you an example that takes advantage of multiple arguments but doesn't use @@ -542,13 +548,13 @@ def __init__(self): into a big long string that we'll just have to re-split. But we still want the nice error handling of privmsgs.getArgs. So what do we do? We - raise callbacks.ArgumentError! That's the - secret juju that privmsgs.getArgs is - doing; now we're just doing it ourself. Someone up our - callchain knows how to handle it so a neat error message is - returned. So in this function, if - .pop(0) fails, we weren't given enough - arguments and thus need to tell the user how to call us. + raise callbacks.ArgumentError! That's + the secret juju that privmsgs.getArgs is + doing; now we're just doing it ourself. Someone up our + callchain knows how to handle it so a neat error message is + returned. So in this function, if + .pop(0) fails, we weren't given enough + arguments and thus need to tell the user how to call us. So we have the args, we have the number, we do a simple call @@ -607,22 +613,22 @@ def __init__(self): Later, though, you'll see something other than irc.reply. This is - irc.queueMsg, the general interface for - sending messages to the server. It's what - irc.reply is using under the covers. It - takes an IrcMsg object. Fortunately, - that's exactly what's returned by - ircmsgs.action. An action message, just - in case you don't know, is a /me kind of message. - ircmsgs.action is a helper function that - takes a target (a place to send the message, either a channel - or a person) and a payload (the thing to /me) and returns the - appropriate IrcMsg object. - ircutils.replyTo simply takes an - IrcMsg and returns where we should reply - to; if the message was originally sent to a channel, we'll - reply to there, if it was originally sent to us privately, - we'll reply in private. + irc.queueMsg, the general interface for + sending messages to the server. It's what + irc.reply is using under the covers. It + takes an IrcMsg object. Fortunately, + that's exactly what's returned by + ircmsgs.action. An action message, just + in case you don't know, is a /me kind of message. + ircmsgs.action is a helper function that + takes a target (a place to send the message, either a channel + or a person) and a payload (the thing to /me) and returns the + appropriate IrcMsg object. + ircutils.replyTo simply takes an + IrcMsg and returns where we should + reply to; if the message was originally sent to a channel, + we'll reply to there, if it was originally sent to us + privately, we'll reply in private. At the end, you might be surprised by the "raise @@ -646,7 +652,7 @@ def __init__(self): Finishing touches Let's take a look at that configure - function scripts/newplugin.py made + function made for us. Here it is, in case you've forgotten: @@ -660,10 +666,10 @@ def configure(onStart, afterConnect, advanced): You remember when you first started running supybot and ran - scripts/setup.py and it asked you + and it asked you all those questions? Well, now's your chance to ask other users some questions of your own. In our case, with our - Random plugin, it might be nice to offer + Random plugin, it might be nice to offer the user the ability to specify a seed to use whenever the plugin is loaded. So let's ask him if he wants to do that, and if so, let's ask him what the seed should be. @@ -684,7 +690,7 @@ def configure(onStart, afterConnect, advanced): onStart.append('seed %s' % seed) - As you can see, what the questions module + As you can see, what the questions module does is fairly self-evident: yn returns either 'y' or 'n'; something returns something (but not nothing; for nothing, @@ -694,12 +700,12 @@ def configure(onStart, afterConnect, advanced): onStart is a list of the commands to run when the bot starts; we're just throwing our little piece into it. These commands will then be written into the template - scripts/setup.py creates for the bot. + creates for the bot. We've written our own plugin from scratch (well, from the boilerplate that we got from - scripts/newplugin.py :)) and + :)) and survived! Now go write more plugins for supybot, and send them to me so I can use them too :) diff --git a/docs/DocBook/faq.sgml b/docs/DocBook/faq.sgml new file mode 100644 index 000000000..caa6247c5 --- /dev/null +++ b/docs/DocBook/faq.sgml @@ -0,0 +1,284 @@ + +
+ + + + Jeremiah + Fincher + + + Daniel + DiPaolo + DocBook translator + + + Supybot Frequently Asked Questions + + + 0.1 + 18 Feb 2004 + Initial Docbook translation + + + 0.2 + 26 Feb 2004 + Changed to Supybot DTD + + + + + + + + Why does my bot not recognize me or tell me that I don't + have the owner capability? + + + + + Because you're not given it anything to recognize you + from! You'll need to identify with the bot + (help identify to see how that + works) or add your hostmask to your user record + (help addhostmask to see how that + works) for it to know that you're you. You may wish to + note that addhostmask can accept + a password; rather than identify, you can send the command + addhostmask myOwnerUser [hostmask] + myOwnerUserPassword and the bot will add your + current hostmask to your owner user (of course, you should + change myOwnerUser and + myOwnerUserPassword appropriately for + your bot). + + + + + + + How do I make Supybot op my users? + + + + + First, you'll have to make sure that your users register + with the bot. They can do this with the + register command. After they do + so, you'll want to add the + #channel,op capability to their + user. Use the channel + addcapability command to do this. After + that, your users should be able to use the + op command to get ops. + + + If you want your users to be auto-opped when they join the + channel, you'll need to load the Enforcer + plugin and turn its autoOp + configuration variable on. Use the + config command to do so. Here's + an example of how to do these steps: + + + <jemfinch|lambda> I'm going to make an example session for giving + you auto-ops, for our FAQ. + <dunk1> ah ok ;] + <jemfinch|lambda> First, I need you to register with supybot, using + the "register" command (remember to send it in private). + <dunk1> done + <jemfinch|lambda> what name are you registered under? + <dunk1> dunk1 + <jemfinch|lambda> ok, cool. + <jemfinch|lambda> @channel addcapability dunk1 op + <supybot> jemfinch|lambda: The operation succeeded. + <jemfinch|lambda> now use the "op" command to get ops. + <dunk1> @op + — supybot gives channel operator status to dunk1 + <dunk1> works! + <dunk1> ;] + <jemfinch|lambda> @load Enforcer + <supybot> jemfinch|lambda: The operation succeeded. + <jemfinch|lambda> @config supybot.plugins.Enforcer.autoOp.#supybot On + <supybot> jemfinch|lambda: The operation succeeded. + <jemfinch|lambda> ok, now cycle the channel (part and then rejoin) + <– dunk1 (dunker@freebsd.nl) has left #supybot + –> dunk1 (dunker@freebsd.nl) has joined #supybot + — supybot gives channel operator status to dunk1 + <jemfinch|lambda> cool, thanks :) + + + + + + + Can users with the admin + capability change configuration variables? + + + + + Currently, no. Since this is the first release of Supybot + that uses the registry, we wanted to stay on the + conservative side and require the + owner capability for changing all + non-channel-related configuration variables. Feel free to + make your case to us as to why a certain configuration + variable should only require the + admin capability instead of the + owner capability, and if we agree + with you, we'll change it for the next release. + + + + + + + How do I make my Supybot connect to multiple servers? + + + + + You'll need to use the Relay plugin. As + long as you don't call the relay + join command, it won't actually do any + relaying between channels (even if the bot is on the same + channel on different networks). In order to use the Relay + plugin, you'll want to first call the relay + start command, followed by the + relay connect command. These + commands are (unfortunately) not persistent at this time, + so you'll need to give them to the bot anytime you start + it up. We'll probably have this lack of persistence + rectified before the next release. + + + + + + + Can Supybot do factoids? + + + + + Supybot most certainly can! In fact, we offer two + full-fledged factoids-related plugins! + + + Factoids (written by + jemfinch) is Supybot's original + factoids-related plugin. It offers full integration with + Supybot's nested commands as well as a complete 1:n key to + factoid ratio, with lookup by individual number. + Factoids also uses a channel-specific + database instead of a global database, although in the + future it will likely be a configuration option whether to + use channel-specific or global databases for such plugins. + + + MoobotFactoids (written by + Strike) is much more full-featured, offering + users the ability to define factoids in a slightly more + user-friendly way, as well as parsing factoids to handle + <reply>, <action>, "see", and alternations + (defining a factoid "test" as "<reply>(foo|bar|baz)" + will make the bot send "foo" or "bar" or "baz" to the + channel (without the normal "test is " at the beginning)). + If you're accustomed to Moobot's factoids or Blootbot's + factoids, then this is the Factoids plugin for you. + Unfortunately, due to the more natural definition syntax + (required to be compatible with Moobot) you can't define + Factoids with nested commands; you'll have to evaluate the + command first and then copy the result into your factoid + definition. MoobotFactoids uses a global + database, so the factoids are the same for all channels. + + + In the future, we plan to have a compatibility plugin for + Infobot, but as of present we've not yet written one. + + + + + + + Can I import my Infobot/Blootbot/Moobot factoids into + Supybot? + + + + + As of present, we have no automated way to do so. + Strike has written a few scripts for + importing a Moobot database into + MoobotFactoids, however, so you'll want + to talk to him about helping you with that. We're + certainly happy to help you convert such databases; if you + can provide us with such a database exported to a flat + file, we can probably do the rest of the work to write a + script that imports it into a database for one of our + factoids-related plugins. + + + + + + + I found a bug, what do I do? + + + + + Submit it on Sourceforge through our Sourceforge project + page: + + http://sourceforge.net/tracker/?group_id=58965&atid=489447 + . If Sourceforge happens to be down when you try + to submit your bug, then post it in the "Supybot Developer + Discussion" forum at our forums at + + http://forums.supybot.org/ + . If that doesn't work, email + supybot-bugs@lists.sourceforge.net. If + that doesn't work, email + jemfinch@supybot.org. If that doesn't + work, find yourself some carrier pigeons and … hah! + You thought I was serious! + + + Anyway, when you submit your bug, we'll need several + things. If the bug involved an uncaught exception, we + need the traceback (basically the stuff from + “Uncaught exception in …” to the next + log entry). We'd also like to see the commands that + caused the bug, or happened around the time you saw the + bug. If the bug involved a database, we'd love to see the + database. Remember, it's always worse to send us too much + information in a bug report than too little. + + + + + + + Karma doesn't seem to work for me. + + + + + Karma by default doesn't acknowledge + karma updates. If you check the karma of whatever you + increased/decreased, you'll note that your increment or + decrement still took place. If you'd rather + Karma acknowledge karma updates, change + the + supybot.plugins.Karma.response + configuration variable to On. + + + + +
+ + diff --git a/docs/DocBook/getting_started.sgml b/docs/DocBook/getting_started.sgml new file mode 100644 index 000000000..27d029730 --- /dev/null +++ b/docs/DocBook/getting_started.sgml @@ -0,0 +1,297 @@ + + +
+ + + + Jeremiah + Fincher + + + Daniel + DiPaolo + DocBook translator + + + Getting started with Supybot + + + 0.1 + 18 Feb 2004 + Initial Docbook translation + + + + + Introduction + + Ok, so you've decided to try out Supybot. That's great! The more + people who use Supybot, the more people can submit bugs and help + us to make it the best IRC bot in the world :) + + + First things first: Supybot requires Python + 2.3. There ain't no getting around it. If you're a Python + developer, you probably know how superior 2.3 is to previous + incarnations. If you're not, just think about the difference + between a bowl of plain vanilla ice cream and a banana split. Or + something like that. Either way, we're + Python developers and we like banana splits. + + + + Installing the bot and its utilities + + So what do you do? First thing you'll want to do is run (with + root/admin privileges) python setup.py + install. This will install Supybot globally. If + you need to install locally for whatever reason, see this forum post on how to do so. + You'll then have several new programs installed where Python + scripts are normally installed on your system + (/usr/bin or + /usr/local/bin are common on UNIX systems; + C:\Python23\Scripts is a common place on + Windows; and (watch out, this is a long one :)) + /System/Library/Frameworks/Python.framework/Versions/2.3/bin + is a common place on MacOS X.). The two that might be of + particular interest to you, the new user, are + and + The former + ( is the script to run an actual + bot; the latter ( is an + in-depth wizard that provides a nice user interface for creating + configuration files for your bot. We'd prefer you to the use + , but if you're in a + hurry or don't feel like being asked many questions, just run + supybot with no arguments and it'll ask you only the questions + necessary ")to run a bot. + + + + Firing up the bot for the first time + + So after running either of those two programs, you've got a nice + registry file handy. If you're not satisfied with your answers + to any of the questions you were asked, feel free to run the + program again until you're satisfied with all your answers. Once + you're satisfied, though, run the + program with the + registry file you created as an argument. This will start the + bot; unless you turned off logging to stdout, you'll see some nice + log messages describing what the bot is doing at any particular + moment; it may pause for a significant amount of time after saying + "Connecting to ..." while the server tries to check its ident. + + + + Your first interactions with the bot + + Ok, so let's assume your bot connected to the server fine and + joined the channels you told it to join. For now we'll assume you + named your bot supybot (you probably didn't, + but it'll make it much clearer in the examples that follow to + assume that you did). We'll also assume that you told it to join + #channel (a nice generic name for a channel, + isn't it? :)) So what do you do with this bot that you just made + to join your channel? Try this in the channel: + + +supybot: list + + + Replacing supybot with the actual name you + picked for your bot, of course. Your bot should reply with a list + of the plugins he currently has loaded. At least + Admin, Channel, + Config, Misc, + Owner, and User should be + there; if you used to + create your configuration file you may have many more plugins + loaded. The list command can also be used to + list the commands in a given plugin: + + +supybot: list Misc + + + Will list all the commands in the Misc plugin. + + + Accessing the bot's online help + + If you want to see the help for any command, just use + the help command: + + +supybot: help help +supybot: help list +supybot: help load + + + + Dealing with ambiguous commands + + Sometimes more than one plugin will have a given command; for + instance, the list command exists in both + the Misc and Config + plugins (both loaded by default). List, in + this case, defaults to the Misc plugin, but + you may want to get the help for the + list + command in the Config plugin. In that + case, you'll want to give your command like this: + + +supybot: help config list + + + Anytime your bot tells you that a given command is defined in + several plugins, you'll want to use this syntax + (plugin command) to disambiguate which + plugin's command you wish to call. For instance, if you + wanted to call the Config plugin's + list command, then you'd need to say: + + +supybot: config list + + + Rather than just list. + + + + Loading plugins + + Now that you know how to deal with plugins having commands + with the same name, let's take a look at loading other + plugins. If you didn't use + , though, you might + do well to try it before playing around with loading plugins + yourself: each plugin has its own + configure function that the wizard uses + to setup the appropriate registry entries if the plugin + requires any. + + + Identifying yourself as the bot owner + + Now, if you do want to play around with loading plugins, + you're going to need to have the + owner + capability. If you ran the wizard, then chances are you + already added an owner user for yourself. If not, + however, you can add one via the handy-dandy + script. You'll + want to run it while the bot is not running (otherwise it + could overwrite + 's changes to + your user database before you get a chance to reload + them). Just follow the prompts, and when it asks if you + want to give the user any capabilities, say yes and then + give yourself the owner capability + (without the quotes), restart the bot and you'll be ready + to load some plugins! + + + Now, in order for the bot to recognize you as your owner + user, you'll have to identify with the bot. Open up a + query window in your irc client (/query should do it; if + not, just know that you can't identify in a channel + because it requires sending your password to the bot). + Then type this: + + +help identify + + + And follow the instructions; the command you send will + probably look like this, with your owner user and password + replaced: + + +identify myowneruser myuserpassword + + + The bot will tell you that “The operation + succeeded” if you got the right name and password. + Now that you're identified, you can do anything that + requires any privilege: that includes all the commands in + the Owner and Admin + plugins, which you may want to take a look at (using the + list and + help + commands, of course). One command in particular that you + might want to use (it's from the User + plugin) is the addhostmask command: it + lets you add a hostmask to your user record so the bot + recognizes you by your hostmask instead of requiring you + to always identify with it before it recognizes you. Use + the help command to see how this + command works. Here's how I often use it: + + +addhostmask myuser [hostmask] mypassword + + + You may not have seen that "[hostmask]" syntax before. + Supybot allows nested commands, which means that any + command's output can be nested as an argument to another + command. The hostmask command from the + Misc plugin returns the hostmask of a + given nick, but if given no arguments, it returns the + hostmask of the person giving the command. So the command + above adds the hostmask I'm currently using to my user's + list of recognized hostmasks. I'm only required to give + mypassword if I'm not already + identified with the bot. + + + + + The <botcommand>more</botcommand> command + + Another command you might find yourself needing somewhat often + is the more command. The IRC protocol + limits messages to 512 bytes, 60 or so of which must be + devoted to some bookkeeping. Sometimes, however, Supybot + wants to send a message that's longer than that. What it + does, then, is break it into "chunks" and send the first one, + following it with "(X more messages)" where X is how many more + chunks there are. To get to these chunks, use the more + command. One way to try is to look at the listing of + configuration groups for the bot (more on this in the + CONFIGURATION document) by giving the command "config list + supybot". Last I checked, it'll overflow into a second chunk. + When you invoke this command, you should see output like: + + +<supybot> nick, ident, user, server, password, channels, prefixChars, + defaultCapabilities, defaultAllow, defaultIgnore, + humanTimestampFormat, externalIP, bracketSyntax, pipeSyntax, + followIdentificationThroughNickChanges, alwaysJoinOnInvite, + showSimpleSyntax, maxHistoryLength, nickmods, throttleTime, + snarfThrottle, threadAllCommands, pingServer, pingInterval, + upkeepInterval, flush, (1 more message) + + + Now, to see the rest of the output, simply give the command + more, and it will show you the rest: + + +<jemfinch> more +<supybot> httpPeekSize, and defaultSocketTimeout + + + + + You're ready! + + You should now have a solid foundation for using Supybot. Be sure + to check the help that is built-in to the bot itself if you have + any questions, and enjoy using Supybot! + + +
+ + diff --git a/docs/DocBook/interfaces.sgml b/docs/DocBook/interfaces.sgml new file mode 100644 index 000000000..9251ea214 --- /dev/null +++ b/docs/DocBook/interfaces.sgml @@ -0,0 +1,585 @@ + + +
+ + + + Jeremiah + Fincher + + + Daniel + DiPaolo + DocBook translator + + + Supybot developer interfaces + + + 0.1 + 19 Feb 2004 + Initial Docbook translation + + + 0.2 + 26 Feb 2004 + Converted to Supybot DTD + + + + + Available interfaces + + These are the interfaces for some of the objects you'll deal with + if you code for Supybot. + + + <classname>ircmsgs.IrcMsg</classname> + <para> + This is the object that represents an IRC message. It has + several methods and attributes. The most important thing + about this class, however, is that it <emphasis>is</emphasis> + hashable, and thus <emphasis>cannot</emphasis> be modified. + Do not change any attributes; any code that modifies an IRC + message is <emphasis>broken</emphasis> and should not exist. + </para> + <variablelist> + <title>Interesting methods + + __init__ + + + One of the more complex initializers in a class. + It can be used in three different ways: + + + + + It can be given a string, as one received + from the server, which it will then parse + into its separate components and + instantiate the class with those + components as attributes. + + + + + It can be given a command, some (optional) + arguments, and a (optional) prefix, and + will instantiate the class with those + components as attributes. + + + + + It can be given, in addition to any of the + above arguments, a msg + keyword argument that will use the + attributes of msg as defaults. This + exists to make it easier to copy messages, + since the class is immutable. + + + + + + + __str__ + + + This returns the message in a string form suitable + for sending to a server. + + + + + __repr__ + + + This returns the message in a form suitable for + eval(), assuming the name + IrcMsg is in your namespace and + is bound to this class. + + + + + + The following attributes are the meat of this class. These + are generally what you'll be looking at with + IrcMsgs. + + + Interesting attributes + + command + + + This is the command of the + IrcMsg – + PRIVMSG, + NOTICE, + WHOIS, + etc. + + + + + args + + + This is a tuple of the arguments to the + IrcMsg. Some messages have + arguments, some don't, depending on what command + they are. You are, of course, always assured that + args exists and is a tuple, + though it might be empty. + + + + + prefix + + + This is the hostmask of the person/server the + message is from. In general, you won't be setting + this on your outgoing messages, but incoming + messages will always have one. This is the whole + hostmask; if the message was received from a + server, it'll be the server's hostmask; if the + message was received from a user, it'll be the + whole user hostmask. In that case, however, it's + also parsed out into the + nick/user/host + attributes, which are probably more useful to + check for many purposes. + + + + + nick + + + If the message was sent by a user, this will be + the nick of the user. If it was sent by a server, + this will be the server's name (something like + calvino.freenode.net or + similar). + + + + + user + + + If the message was sent by a user, this will be + the user string of the user – what they put + into their IRC client for their "full name." If + it was sent by a server, it'll be the server's + name, again. + + + + + host + + + If the message was sent by a user, this will be + the host portion of their hostmask. If it was + sent by a server, it'll be the server's name (yet + again :)) + + + + + + + <classname>irclib.Irc</classname> + <para> + This is the object to handle everything about IRC except the + actual connection to the server itself. + (<emphasis>NOTE</emphasis> that the object actually received + by commands in subclasses of + <classname>callbacks.Privmsg</classname> is an + <classname>IrcObjectProxy</classname>, which is described + later. It augments the following interface with several + methods of its own to help plugin authors.) + </para> + <variablelist> + <title>Interesting methods + + queueMsg + + + Queues a message for sending to the server. The + queue is generally FIFO, but it does prioritize + messages based on their command. + + + + + sendMsg + + + Queues a message for sending to the server prior + to any messages in the normal queue. This is + exactly a FIFO queue, no reordering is done at + all. + + + + + + feedMsg + + + Feeds the Irc object a message + for it handle appropriately, as well as passing it + on to callbacks. + + + + + takeMsg + + + If the Irc object has a message + it's ready to send to the server, this will return + it. Otherwise, it will return + None. + + + + + + addCallback + + + Takes a callback to add to the list of callbacks + in the Irc object. See the + interface for IrcCallback for + more information. + + + + + getCallback + + + Gets a callback by name, if it is in the + Irc object's list of callbacks. + If it it isn't, returns None. + + + + + removeCallback + + + Removes a callback by name. Returns a list of the + callbacks removed (since it is technically + possible to have multiple callbacks with the same + name. This list may be empty. + + + + + __init__ + + + Requires a nick. Optional + arguments include user and + ident, which default to the + nick given, password, which + defaults to the empty password, and + callbacks, a list of callbacks + (which defaults to nothing, an empty list). + + + + + reset + + + Resets the Irc object to its + original state, as well as sends a + reset() to every callbacks. + + + + + die + + + Kills the IRC object and all its callbacks. + + + + + + Interesting attributes + + nick + + + The current nick of the bot. + + + + + prefix + + + The current prefix of the bot. + + + + + server + + + The current server the bot is connected to. + + + + + network + + + The current network name the bot is connected to. + + + + + afterConnect + + + False until the bot has + received a command sent after the connection is + finished – 376, 377, or 422. + + + + + state + + + An IrcState object for this + particular connection. See the interface for the + IrcState object for more + information. + + + + + + + <classname>irclib.IrcCallback</classname> + + Interesting Methods + + name + + + Returns the name of the callback. The default + implementation simply returns the name of the + class. + + + + + __call__ + + + Called by the Irc object with + itself and the message whenever a message is fed + to the Irc object. Nothing is + done with the return value. + + + + + inFilter + + + Called by the Irc object with + itself and the message whenever a message is fed + to the Irc object. The return + value should be an IrcMsg + object to be passed to the next callback in the + Irc's list of callbacks. If + None is returned, all + processing stops. This gives callbacks an + oppurtunity to "filter" incoming messages before + general callbacks are given them. + + + + + outFilter + + + Basically equivalent to + inFilter, except instead of + being called on messages as they enter the + Irc object, it's called on + messages as they leave the Irc + object. + + + + + die + + + Called when the parent Irc is + told to die. This gives callbacks an oppurtunity + to close open files, network connections, or + databases before they're deleted. + + + + + reset + + + Called when the parent Irc is + told to reset (which is generally when + reconnecting to the server). Most callbacks don't + need to define this. + + + + + + Interesting attributes + + priority + + + Determines the priority of the callback in the + Irc object's list of callbacks. + Defaults to 99; the valid range + includes 0 through + sys.maxint-1 (don't use + sys.maxint itself, that's + reserved for the Misc plugin). + The lower the number, the higher the priority. + High priority callbacks are called earlier in the + inFilter cycle, earlier in the + __call__ cycle, and later in + the outFilter cycle – + basically, they're given the first chances on the + way in and the last chances on the way out. + + + + + + + <classname>callbacks.IrcObjectProxy</classname> + + IrcObjectProxy is a proxy for an + irclib.Irc instance that serves to + provide a much fuller interface for handling replies and + errors as well as to handle the nesting of commands. This is + what you'll be dealing with almost all the time when writing + commands; when writing doCommand methods + (the kind you read about in the interface description of + irclib.IrcCallback) you'll be dealing + with plain old irclib.Irc objects. + + + Interesting methods + + reply + + + Called to reply to the current message with a + string that is to be the reply. Uses the + queueMsg command discussed in + the irclib.Irc section. + + + + + replySuccess + replyError + + + These reply with the configured responses for + success and generic error, respectively. If an + additional argument is given, it's (intelligently) + appended to the generic message to be more + specific. + + + + + error + + + Called to send an error reply to the current + message; not only does the response indicate an + error, but commands that error out break the + nested-command chain, which is generally useful + for not confusing the user :) + + + + + errorNoCapability + + + Like error, except it accepts + the capability that's missing and integrates it + into the configured error message for such things. + Also accepts an additional string for a more + descriptive message, if that's what you want. + + + + + errorPossibleBug + errorNotRegistered + errorNoUser + errorRequiresPrivacy + + + These methods reply with the appropriate + configured error message for the conditions in + their names; they all take an additional arguments + to be more specific about the conditions they + indicate, but this argument is very rarely + necessary. + + + + + getRealIrc + + + Returns the actual Irc + object being proxied for. + + + + + + +
+ + diff --git a/docs/DocBook/supybot-html.dsl b/docs/DocBook/supybot-html.dsl new file mode 100644 index 000000000..821a61be8 --- /dev/null +++ b/docs/DocBook/supybot-html.dsl @@ -0,0 +1,46 @@ +(define %stylesheet% "../stylesheets/supybot.css") + +(element botcommand + (make element gi: "span" + attributes: '(("class" "botcommand")) + (process-children))) + +(element plugin + (make element gi: "span" + attributes: '(("class" "plugin")) + (process-children))) + +(element flag + (make element gi: "span" + attributes: '(("class" "flag")) + (process-children))) + +(element nick + (make element gi: "span" + attributes: '(("class" "nick")) + (process-children))) + +(element capability + (make element gi: "span" + attributes: '(("class" "capability")) + (process-children))) + +(element registrygroup + (make element gi: "span" + attributes: '(("class" "registrygroup")) + (process-children))) + +(element ircsession + (make element gi: "pre" + attributes: '(("class" "ircsession")) + (process-children))) + +(element script + (make element gi: "span" + attributes: '(("class" "script")) + (process-children))) + +(element channel + (make element gi: "span" + attributes: '(("class" "channel")) + (process-children))) diff --git a/docs/DocBook/supybot-print.dsl b/docs/DocBook/supybot-print.dsl new file mode 100644 index 000000000..56280546b --- /dev/null +++ b/docs/DocBook/supybot-print.dsl @@ -0,0 +1,43 @@ +(define %mono-font-family% "Courier New") + +(element botcommand + (make sequence + font-family-name: %mono-font-family%)) + +(element plugin + (make sequence + font-weight: 'bold)) + +(element flag + (make sequence + font-posture: 'italic)) + +(element nick + (make sequence + font-family-name: %mono-font-family%)) + +(element capability + (make sequence + font-weight: 'bold)) + +(element registrygroup + (make sequence + font-weight: 'bold)) + +(element ircsession + (make paragraph + font-family-name: %mono-font-family% + space-before: 12pt + space-after: 12pt + start-indent: 6pt + lines: 'asis + input-whitespace-treatment: 'preserve)) + +(element script + (make sequence + font-family-name: %mono-font-family%)) + +(element channel + (make sequence + font-weight: 'bold)) + diff --git a/docs/DocBook/supybot.dsl b/docs/DocBook/supybot.dsl new file mode 100644 index 000000000..08cbed3e3 --- /dev/null +++ b/docs/DocBook/supybot.dsl @@ -0,0 +1,23 @@ + + + + +]> + + + + + &supybot-print; + + + + + &supybot-html; + + + + + diff --git a/docs/DocBook/supybot.dtd b/docs/DocBook/supybot.dtd new file mode 100644 index 000000000..ba04c8016 --- /dev/null +++ b/docs/DocBook/supybot.dtd @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + +%DocBookDTD; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +