1### 2# Copyright (c) 2002-2005, Jeremiah Fincher 3# Copyright (c) 2008-2009,2011, James McCoy 4# All rights reserved. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions are met: 8# 9# * Redistributions of source code must retain the above copyright notice, 10# this list of conditions, and the following disclaimer. 11# * Redistributions in binary form must reproduce the above copyright notice, 12# this list of conditions, and the following disclaimer in the 13# documentation and/or other materials provided with the distribution. 14# * Neither the name of the author of this software nor the name of 15# contributors to this software may be used to endorse or promote products 16# derived from this software without specific prior written consent. 17# 18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28# POSSIBILITY OF SUCH DAMAGE. 29### 30 31import os 32import sys 33import time 34import socket 35 36from . import ircutils, registry, utils 37from .utils import minisix 38from .utils.net import isSocketAddress 39from .version import version 40from .i18n import PluginInternationalization 41_ = PluginInternationalization() 42if minisix.PY2: 43 from urllib2 import build_opener, install_opener, ProxyHandler 44else: 45 from urllib.request import build_opener, install_opener, ProxyHandler 46 47### 48# *** The following variables are affected by command-line options. They are 49# not registry variables for a specific reason. Do *not* change these to 50# registry variables without first consulting people smarter than yourself. 51### 52 53### 54# daemonized: This determines whether or not the bot has been daemonized 55# (i.e., set to run in the background). Obviously, this defaults 56# to False. A command-line option for obvious reasons. 57### 58daemonized = False 59 60### 61# allowDefaultOwner: True if supybot.capabilities is allowed not to include 62# '-owner' -- that is, if all users should be automatically 63# recognized as owners. That would suck, hence we require a 64# command-line option to allow this stupidity. 65### 66allowDefaultOwner = False 67 68### 69# Here we replace values in other modules as appropriate. 70### 71utils.web.defaultHeaders['User-agent'] = \ 72 'Mozilla/5.0 (Compatible; Supybot %s)' % version 73 74### 75# The standard registry. 76### 77supybot = registry.Group() 78supybot.setName('supybot') 79 80def registerGroup(Group, name, group=None, **kwargs): 81 if kwargs: 82 group = registry.Group(**kwargs) 83 return Group.register(name, group) 84 85def registerGlobalValue(group, name, value): 86 value.channelValue = False 87 return group.register(name, value) 88 89def registerChannelValue(group, name, value, opSettable=True): 90 value._supplyDefault = True 91 value.channelValue = True 92 value._opSettable = opSettable 93 g = group.register(name, value) 94 gname = g._name.lower() 95 for name in registry._cache.keys(): 96 if name.lower().startswith(gname) and len(gname) < len(name): 97 name = name[len(gname)+1:] # +1 for . 98 parts = registry.split(name) 99 if len(parts) == 1 and parts[0] and ircutils.isChannel(parts[0]): 100 # This gets the channel values so they always persist. 101 g.get(parts[0])() 102 103def registerPlugin(name, currentValue=None, public=True): 104 group = registerGlobalValue(supybot.plugins, name, 105 registry.Boolean(False, _("""Determines whether this plugin is loaded 106 by default."""), showDefault=False)) 107 supybot.plugins().add(name) 108 registerGlobalValue(group, 'public', 109 registry.Boolean(public, _("""Determines whether this plugin is 110 publicly visible."""))) 111 if currentValue is not None: 112 supybot.plugins.get(name).setValue(currentValue) 113 registerGroup(users.plugins, name) 114 return group 115 116def get(group, channel=None): 117 if group.channelValue and \ 118 channel is not None and ircutils.isChannel(channel): 119 return group.get(channel)() 120 else: 121 return group() 122 123### 124# The user info registry. 125### 126users = registry.Group() 127users.setName('users') 128registerGroup(users, 'plugins', orderAlphabetically=True) 129 130def registerUserValue(group, name, value): 131 assert group._name.startswith('users') 132 value._supplyDefault = True 133 group.register(name, value) 134 135class ValidNick(registry.String): 136 """Value must be a valid IRC nick.""" 137 __slots__ = () 138 def setValue(self, v): 139 if not ircutils.isNick(v): 140 self.error() 141 else: 142 registry.String.setValue(self, v) 143 144class ValidNickOrEmpty(ValidNick): 145 """Value must be a valid IRC nick or empty.""" 146 __slots__ = () 147 def setValue(self, v): 148 if v != '' and not ircutils.isNick(v): 149 self.error() 150 else: 151 registry.String.setValue(self, v) 152 153class ValidNicks(registry.SpaceSeparatedListOf): 154 __slots__ = () 155 Value = ValidNick 156 157class ValidNickAllowingPercentS(ValidNick): 158 """Value must be a valid IRC nick, with the possible exception of a %s 159 in it.""" 160 __slots__ = () 161 def setValue(self, v): 162 # If this works, it's a valid nick, aside from the %s. 163 try: 164 ValidNick.setValue(self, v.replace('%s', '')) 165 # It's valid aside from the %s, we'll let it through. 166 registry.String.setValue(self, v) 167 except registry.InvalidRegistryValue: 168 self.error() 169 170class ValidNicksAllowingPercentS(ValidNicks): 171 __slots__ = () 172 Value = ValidNickAllowingPercentS 173 174class ValidChannel(registry.String): 175 """Value must be a valid IRC channel name.""" 176 __slots__ = ('channel',) 177 def setValue(self, v): 178 self.channel = v 179 if ',' in v: 180 # To prevent stupid users from: a) trying to add a channel key 181 # with a comma in it, b) trying to add channels separated by 182 # commas instead of spaces 183 try: 184 (channel, _) = v.split(',') 185 except ValueError: 186 self.error() 187 else: 188 channel = v 189 if not ircutils.isChannel(channel): 190 self.error() 191 else: 192 registry.String.setValue(self, v) 193 194 def error(self): 195 try: 196 super(ValidChannel, self).error() 197 except registry.InvalidRegistryValue as e: 198 e.channel = self.channel 199 raise e 200 201class ValidHostmask(registry.String): 202 """Value must be a valid user hostmask.""" 203 __slots__ = () 204 def setValue(self, v): 205 if not ircutils.isUserHostmask(v): 206 self.error() 207 super(ValidHostmask, self).setValue(v) 208 209registerGlobalValue(supybot, 'nick', 210 ValidNick('supybot', _("""Determines the bot's default nick."""))) 211 212registerGlobalValue(supybot.nick, 'alternates', 213 ValidNicksAllowingPercentS(['%s`', '%s_'], _("""Determines what alternative 214 nicks will be used if the primary nick (supybot.nick) isn't available. A 215 %s in this nick is replaced by the value of supybot.nick when used. If no 216 alternates are given, or if all are used, the supybot.nick will be perturbed 217 appropriately until an unused nick is found."""))) 218 219registerGlobalValue(supybot, 'ident', 220 ValidNick('limnoria', _("""Determines the bot's ident string, if the server 221 doesn't provide one by default."""))) 222 223# Although empty version strings are theoretically allowed by the RFC, 224# popular IRCds do not. 225# So, we keep replacing the empty string by the current version for 226# bots which are migrated from Supybot or an old version of Limnoria 227# (whose default value of supybot.user is the empty string). 228class VersionIfEmpty(registry.String): 229 __slots__ = () 230 def __call__(self): 231 ret = registry.String.__call__(self) 232 if not ret: 233 ret = 'Limnoria $version' 234 return ret 235 236registerGlobalValue(supybot, 'user', 237 VersionIfEmpty('Limnoria $version', _("""Determines the real name which the bot sends to 238 the server. A standard real name using the current version of the bot 239 will be generated if this is left empty."""))) 240 241class Networks(registry.SpaceSeparatedSetOfStrings): 242 __slots__ = () 243 List = ircutils.IrcSet 244 245registerGlobalValue(supybot, 'networks', 246 Networks([], _("""Determines what networks the bot will connect to."""), 247 orderAlphabetically=True)) 248 249class Servers(registry.SpaceSeparatedListOfStrings): 250 __slots__ = () 251 def normalize(self, s): 252 if ':' not in s: 253 s += ':6667' 254 return s 255 256 def convert(self, s): 257 s = self.normalize(s) 258 (server, port) = s.rsplit(':', 1) 259 260 # support for `[ipv6]:port` format 261 if server.startswith("[") and server.endswith("]"): 262 server = server[1:-1] 263 264 port = int(port) 265 return (server, port) 266 267 def __call__(self): 268 L = registry.SpaceSeparatedListOfStrings.__call__(self) 269 return list(map(self.convert, L)) 270 271 def __str__(self): 272 return ' '.join(registry.SpaceSeparatedListOfStrings.__call__(self)) 273 274 def append(self, s): 275 L = registry.SpaceSeparatedListOfStrings.__call__(self) 276 L.append(s) 277 278class SocksProxy(registry.String): 279 """Value must be a valid hostname:port string.""" 280 __slots__ = () 281 def setValue(self, v): 282 # TODO: improve checks 283 if ':' not in v: 284 self.error() 285 try: 286 int(v.rsplit(':', 1)[1]) 287 except ValueError: 288 self.error() 289 super(SocksProxy, self).setValue(v) 290 291class SpaceSeparatedSetOfChannels(registry.SpaceSeparatedListOf): 292 __slots__ = () 293 sorted = True 294 List = ircutils.IrcSet 295 Value = ValidChannel 296 def join(self, channel): 297 from . import ircmsgs # Don't put this globally! It's recursive. 298 key = self.key.get(channel)() 299 if key: 300 return ircmsgs.join(channel, key) 301 else: 302 return ircmsgs.join(channel) 303 def joins(self): 304 from . import ircmsgs # Don't put this globally! It's recursive. 305 channels = [] 306 channels_with_key = [] 307 keys = [] 308 old = None 309 msgs = [] 310 msg = None 311 for channel in self(): 312 key = self.key.get(channel)() 313 if key: 314 keys.append(key) 315 channels_with_key.append(channel) 316 else: 317 channels.append(channel) 318 msg = ircmsgs.joins(channels_with_key + channels, keys) 319 if len(str(msg)) > 512: 320 msgs.append(old) 321 keys = [] 322 channels_with_key = [] 323 channels = [] 324 old = msg 325 if msg: 326 msgs.append(msg) 327 return msgs 328 else: 329 # Let's be explicit about it 330 return None 331 332class ValidSaslMechanism(registry.OnlySomeStrings): 333 __slots__ = () 334 validStrings = ('ecdsa-nist256p-challenge', 'external', 'plain', 335 'scram-sha-256') 336 337class SpaceSeparatedListOfSaslMechanisms(registry.SpaceSeparatedListOf): 338 __slots__ = () 339 Value = ValidSaslMechanism 340 341def registerNetwork(name, password='', ssl=True, sasl_username='', 342 sasl_password=''): 343 network = registerGroup(supybot.networks, name) 344 registerGlobalValue(network, 'password', registry.String(password, 345 _("""Determines what password will be used on %s. Yes, we know that 346 technically passwords are server-specific and not network-specific, 347 but this is the best we can do right now.""") % name, private=True)) 348 registerGlobalValue(network, 'servers', Servers([], 349 _("""Space-separated list of servers the bot will connect to for %s. 350 Each will be tried in order, wrapping back to the first when the cycle 351 is completed.""") % name)) 352 registerGlobalValue(network, 'channels', SpaceSeparatedSetOfChannels([], 353 _("""Space-separated list of channels the bot will join only on %s.""") 354 % name, private=True)) 355 356 registerGlobalValue(network, 'ssl', registry.Boolean(ssl, 357 _("""Determines whether the bot will attempt to connect with SSL 358 sockets to %s.""") % name)) 359 registerGlobalValue(network.ssl, 'serverFingerprints', 360 registry.SpaceSeparatedSetOfStrings([], format(_("""Space-separated list 361 of fingerprints of trusted certificates for this network. 362 Supported hash algorithms are: %L. 363 If non-empty, Certification Authority signatures will not be used to 364 verify certificates."""), utils.net.FINGERPRINT_ALGORITHMS))) 365 registerGlobalValue(network.ssl, 'authorityCertificate', 366 registry.String('', _("""A certificate that is trusted to verify 367 certificates of this network (aka. Certificate Authority)."""))) 368 registerGlobalValue(network, 'requireStarttls', registry.Boolean(False, 369 _("""Deprecated config value, keep it to False."""))) 370 371 registerGlobalValue(network, 'certfile', registry.String('', 372 _("""Determines what certificate file (if any) the bot will use to 373 connect with SSL sockets to %s.""") % name)) 374 registerChannelValue(network.channels, 'key', registry.String('', 375 _("""Determines what key (if any) will be used to join the 376 channel."""), private=True)) 377 registerGlobalValue(network, 'nick', ValidNickOrEmpty('', _("""Determines 378 what nick the bot will use on this network. If empty, defaults to 379 supybot.nick."""))) 380 registerGlobalValue(network, 'ident', ValidNickOrEmpty('', _("""Determines 381 the bot's ident string, if the server doesn't provide one by default. 382 If empty, defaults to supybot.ident."""))) 383 registerGlobalValue(network, 'user', registry.String('', _("""Determines 384 the real name which the bot sends to the server. If empty, defaults to 385 supybot.user"""))) 386 registerGlobalValue(network, 'umodes', 387 registry.String('', _("""Determines what user modes the bot will request 388 from the server when it first connects. If empty, defaults to 389 supybot.protocols.irc.umodes"""))) 390 sasl = registerGroup(network, 'sasl') 391 registerGlobalValue(sasl, 'username', registry.String(sasl_username, 392 _("""Determines what SASL username will be used on %s. This should 393 be the bot's account name. Due to the way SASL works, you can't use 394 any grouped nick.""") % name, private=False)) 395 registerGlobalValue(sasl, 'password', registry.String(sasl_password, 396 _("""Determines what SASL password will be used on %s.""") \ 397 % name, private=True)) 398 registerGlobalValue(sasl, 'ecdsa_key', registry.String('', 399 _("""Determines what SASL ECDSA key (if any) will be used on %s. 400 The public key must be registered with NickServ for SASL 401 ECDSA-NIST256P-CHALLENGE to work.""") % name, private=False)) 402 registerGlobalValue(sasl, 'mechanisms', SpaceSeparatedListOfSaslMechanisms( 403 ['ecdsa-nist256p-challenge', 'external', 'plain'], _("""Determines 404 what SASL mechanisms will be tried and in which order."""))) 405 registerGlobalValue(sasl, 'required', registry.Boolean(False, 406 _("""Determines whether the bot will abort the connection if the 407 none of the enabled SASL mechanism succeeded."""))) 408 registerGlobalValue(network, 'socksproxy', registry.String('', 409 _("""If not empty, determines the hostname of the socks proxy that 410 will be used to connect to this network."""))) 411 return network 412 413# Let's fill our networks. 414for (name, s) in registry._cache.items(): 415 if name.startswith('supybot.networks.'): 416 parts = name.split('.') 417 name = parts[2] 418 if name != 'default': 419 registerNetwork(name) 420 421 422### 423# Reply/error tweaking. 424### 425registerGroup(supybot, 'reply') 426 427registerGroup(supybot.reply, 'format') 428registerChannelValue(supybot.reply.format, 'url', 429 registry.String('<%s>', _("""Determines how urls should be formatted."""))) 430def url(s): 431 if s: 432 return supybot.reply.format.url() % s 433 else: 434 return '' 435utils.str.url = url 436registerChannelValue(supybot.reply.format, 'time', 437 registry.String('%Y-%m-%dT%H:%M:%S%z', _("""Determines how timestamps 438 printed for human reading should be formatted. Refer to the Python 439 documentation for the time module to see valid formatting characters for 440 time formats."""))) 441def timestamp(t): 442 if t is None: 443 t = time.time() 444 if isinstance(t, float) or isinstance(t, int): 445 t = time.localtime(t) 446 format = get(supybot.reply.format.time, dynamic.channel) 447 return time.strftime(format, t) 448utils.str.timestamp = timestamp 449 450registerGroup(supybot.reply.format.time, 'elapsed') 451registerChannelValue(supybot.reply.format.time.elapsed, 'short', 452 registry.Boolean(False, _("""Determines whether elapsed times will be given 453 as "1 day, 2 hours, 3 minutes, and 15 seconds" or as "1d 2h 3m 15s"."""))) 454 455originalTimeElapsed = utils.timeElapsed 456def timeElapsed(*args, **kwargs): 457 kwargs['short'] = supybot.reply.format.time.elapsed.short() 458 return originalTimeElapsed(*args, **kwargs) 459utils.timeElapsed = timeElapsed 460 461registerGlobalValue(supybot.reply, 'maximumLength', 462 registry.Integer(512*256, _("""Determines the absolute maximum length of 463 the bot's reply -- no reply will be passed through the bot with a length 464 greater than this."""))) 465 466registerChannelValue(supybot.reply, 'mores', 467 registry.Boolean(True, _("""Determines whether the bot will break up long 468 messages into chunks and allow users to use the 'more' command to get the 469 remaining chunks."""))) 470 471registerChannelValue(supybot.reply.mores, 'maximum', 472 registry.PositiveInteger(50, _("""Determines what the maximum number of 473 chunks (for use with the 'more' command) will be."""))) 474 475registerChannelValue(supybot.reply.mores, 'length', 476 registry.NonNegativeInteger(0, _("""Determines how long individual chunks 477 will be. If set to 0, uses our super-tweaked, 478 get-the-most-out-of-an-individual-message default."""))) 479 480registerChannelValue(supybot.reply.mores, 'instant', 481 registry.PositiveInteger(1, _("""Determines how many mores will be sent 482 instantly (i.e., without the use of the more command, immediately when 483 they are formed). Defaults to 1, which means that a more command will be 484 required for all but the first chunk."""))) 485 486registerChannelValue(supybot.reply, 'oneToOne', 487 registry.Boolean(True, _("""Determines whether the bot will send 488 multi-message replies in a single message. This defaults to True 489 in order to prevent the bot from flooding. If this is set to False 490 the bot will send multi-message replies on multiple lines."""))) 491 492registerChannelValue(supybot.reply, 'whenNotCommand', 493 registry.Boolean(True, _("""Determines whether the bot will reply with an 494 error message when it is addressed but not given a valid command. If this 495 value is False, the bot will remain silent, as long as no other plugins 496 override the normal behavior."""))) 497 498registerGroup(supybot.reply, 'error') 499registerGlobalValue(supybot.reply.error, 'detailed', 500 registry.Boolean(False, _("""Determines whether error messages that result 501 from bugs in the bot will show a detailed error message (the uncaught 502 exception) or a generic error message."""))) 503registerChannelValue(supybot.reply.error, 'inPrivate', 504 registry.Boolean(False, _("""Determines whether the bot will send error 505 messages to users in private. You might want to do this in order to keep 506 channel traffic to minimum. This can be used in combination with 507 supybot.reply.error.withNotice."""))) 508registerChannelValue(supybot.reply.error, 'withNotice', 509 registry.Boolean(False, _("""Determines whether the bot will send error 510 messages to users via NOTICE instead of PRIVMSG. You might want to do this 511 so users can ignore NOTICEs from the bot and not have to see error 512 messages; or you might want to use it in combination with 513 supybot.reply.errorInPrivate so private errors don't open a query window 514 in most IRC clients."""))) 515registerChannelValue(supybot.reply.error, 'noCapability', 516 registry.Boolean(False, _("""Determines whether the bot will *not* provide 517 details in the error 518 message to users who attempt to call a command for which they do not have 519 the necessary capability. You may wish to make this True if you don't want 520 users to understand the underlying security system preventing them from 521 running certain commands."""))) 522 523registerChannelValue(supybot.reply, 'inPrivate', 524 registry.Boolean(False, _("""Determines whether the bot will reply 525 privately when replying in a channel, rather than replying to the whole 526 channel."""))) 527 528registerChannelValue(supybot.reply, 'withNotice', 529 registry.Boolean(False, _("""Determines whether the bot will reply with a 530 notice when replying in a channel, rather than replying with a privmsg as 531 normal."""))) 532 533# XXX: User value. 534registerGlobalValue(supybot.reply, 'withNoticeWhenPrivate', 535 registry.Boolean(True, _("""Determines whether the bot will reply with a 536 notice when it is sending a private message, in order not to open a /query 537 window in clients."""))) 538 539registerChannelValue(supybot.reply, 'withNickPrefix', 540 registry.Boolean(True, _("""Determines whether the bot will always prefix 541 the user's nick to its reply to that user's command."""))) 542 543registerChannelValue(supybot.reply, 'whenNotAddressed', 544 registry.Boolean(False, _("""Determines whether the bot should attempt to 545 reply to all messages even if they don't address it (either via its nick 546 or a prefix character). If you set this to True, you almost certainly want 547 to set supybot.reply.whenNotCommand to False."""))) 548 549registerChannelValue(supybot.reply, 'requireChannelCommandsToBeSentInChannel', 550 registry.Boolean(False, _("""Determines whether the bot will allow you to 551 send channel-related commands outside of that channel. Sometimes people 552 find it confusing if a channel-related command (like Filter.outfilter) 553 changes the behavior of the channel but was sent outside the channel 554 itself."""))) 555 556registerGlobalValue(supybot, 'followIdentificationThroughNickChanges', 557 registry.Boolean(False, _("""Determines whether the bot will unidentify 558 someone when that person changes their nick. Setting this to True 559 will cause the bot to track such changes. It defaults to False for a 560 little greater security."""))) 561 562registerChannelValue(supybot, 'alwaysJoinOnInvite', 563 registry.Boolean(False, _("""Determines whether the bot will always join a 564 channel when it's invited. If this value is False, the bot will only join 565 a channel if the user inviting it has the 'admin' capability (or if it's 566 explicitly told to join the channel using the Admin.join command)."""))) 567 568registerChannelValue(supybot.reply, 'showSimpleSyntax', 569 registry.Boolean(False, _("""Supybot normally replies with the full help 570 whenever a user misuses a command. If this value is set to True, the bot 571 will only reply with the syntax of the command (the first line of the 572 help) rather than the full help."""))) 573 574class ValidPrefixChars(registry.String): 575 """Value must contain only ~!@#$%^&*()_-+=[{}]\\|'\";:,<.>/?""" 576 __slots__ = () 577 def setValue(self, v): 578 if any([x not in '`~!@#$%^&*()_-+=[{}]\\|\'";:,<.>/?' for x in v]): 579 self.error() 580 registry.String.setValue(self, v) 581 582registerGroup(supybot.reply, 'whenAddressedBy') 583registerChannelValue(supybot.reply.whenAddressedBy, 'chars', 584 ValidPrefixChars('', _("""Determines what prefix characters the bot will 585 reply to. A prefix character is a single character that the bot will use 586 to determine what messages are addressed to it; when there are no prefix 587 characters set, it just uses its nick. Each character in this string is 588 interpreted individually; you can have multiple prefix chars 589 simultaneously, and if any one of them is used as a prefix the bot will 590 assume it is being addressed."""))) 591 592registerChannelValue(supybot.reply.whenAddressedBy, 'strings', 593 registry.SpaceSeparatedSetOfStrings([], _("""Determines what strings the 594 bot will reply to when they are at the beginning of the message. Whereas 595 prefix.chars can only be one character (although there can be many of 596 them), this variable is a space-separated list of strings, so you can 597 set something like '@@ ??' and the bot will reply when a message is 598 prefixed by either @@ or ??."""))) 599registerChannelValue(supybot.reply.whenAddressedBy, 'nick', 600 registry.Boolean(True, _("""Determines whether the bot will reply when 601 people address it by its nick, rather than with a prefix character."""))) 602registerChannelValue(supybot.reply.whenAddressedBy.nick, 'atEnd', 603 registry.Boolean(False, _("""Determines whether the bot will reply when 604 people address it by its nick at the end of the message, rather than at 605 the beginning."""))) 606registerChannelValue(supybot.reply.whenAddressedBy, 'nicks', 607 registry.SpaceSeparatedSetOfStrings([], _("""Determines what extra nicks 608 the bot will always respond to when addressed by, even if its current nick 609 is something else."""))) 610 611### 612# Replies 613### 614registerGroup(supybot, 'replies') 615 616registerChannelValue(supybot.replies, 'success', 617 registry.NormalizedString(_("""The operation succeeded."""), 618 _("""Determines what message the bot replies with when a command succeeded. 619 If this configuration variable is empty, no success message will be 620 sent."""))) 621 622registerChannelValue(supybot.replies, 'error', 623 registry.NormalizedString(_("""An error has occurred and has been logged. 624 Please contact this bot's administrator for more information."""), _(""" 625 Determines what error message the bot gives when it wants to be 626 ambiguous."""))) 627 628registerChannelValue(supybot.replies, 'errorOwner', 629 registry.NormalizedString(_("""An error has occurred and has been logged. 630 Check the logs for more information."""), _("""Determines what error 631 message the bot gives to the owner when it wants to be ambiguous."""))) 632 633registerChannelValue(supybot.replies, 'incorrectAuthentication', 634 registry.NormalizedString(_("""Your hostmask doesn't match or your password 635 is wrong."""), _("""Determines what message the bot replies with when 636 someone tries to use a command that requires being identified or having a 637 password and neither credential is correct."""))) 638 639# XXX: This should eventually check that there's one and only one %s here. 640registerChannelValue(supybot.replies, 'noUser', 641 registry.NormalizedString(_("""I can't find %s in my user 642 database. If you didn't give a user name, then I might not know what your 643 user is, and you'll need to identify before this command might work."""), 644 _("""Determines what error message the bot replies with when someone tries 645 to accessing some information on a user the bot doesn't know about."""))) 646 647registerChannelValue(supybot.replies, 'notRegistered', 648 registry.NormalizedString(_("""You must be registered to use this command. 649 If you are already registered, you must either identify (using the identify 650 command) or add a hostmask matching your current hostmask (using the 651 "hostmask add" command)."""), _("""Determines what error message the bot 652 replies with when someone tries to do something that requires them to be 653 registered but they're not currently recognized."""))) 654 655registerChannelValue(supybot.replies, 'noCapability', 656 registry.NormalizedString(_("""You don't have the %s capability. If you 657 think that you should have this capability, be sure that you are identified 658 before trying again. The 'whoami' command can tell you if you're 659 identified."""), _("""Determines what error message is given when the bot 660 is telling someone they aren't cool enough to use the command they tried to 661 use."""))) 662 663registerChannelValue(supybot.replies, 'genericNoCapability', 664 registry.NormalizedString(_("""You're missing some capability you need. 665 This could be because you actually possess the anti-capability for the 666 capability that's required of you, or because the channel provides that 667 anti-capability by default, or because the global capabilities include 668 that anti-capability. Or, it could be because the channel or 669 supybot.capabilities.default is set to False, meaning that no commands are 670 allowed unless explicitly in your capabilities. Either way, you can't do 671 what you want to do."""), 672 _("""Determines what generic error message is given when the bot is telling 673 someone that they aren't cool enough to use the command they tried to use, 674 and the author of the code calling errorNoCapability didn't provide an 675 explicit capability for whatever reason."""))) 676 677registerChannelValue(supybot.replies, 'requiresPrivacy', 678 registry.NormalizedString(_("""That operation cannot be done in a 679 channel."""), _("""Determines what error messages the bot sends to people 680 who try to do things in a channel that really should be done in 681 private."""))) 682 683registerChannelValue(supybot.replies, 'possibleBug', 684 registry.NormalizedString(_("""This may be a bug. If you think it is, 685 please file a bug report at 686 <https://github.com/ProgVal/Limnoria/issues>."""), 687 _("""Determines what message the bot sends when it thinks you've 688 encountered a bug that the developers don't know about."""))) 689### 690# End supybot.replies. 691### 692 693registerGlobalValue(supybot, 'snarfThrottle', 694 registry.Float(10.0, _("""A floating point number of seconds to throttle 695 snarfed URLs, in order to prevent loops between two bots snarfing the same 696 URLs and having the snarfed URL in the output of the snarf message."""))) 697 698registerGlobalValue(supybot, 'upkeepInterval', 699 registry.PositiveInteger(3600, _("""Determines the number of seconds 700 between running the upkeep function that flushes (commits) open databases, 701 collects garbage, and records some useful statistics at the debugging 702 level."""))) 703 704registerGlobalValue(supybot, 'flush', 705 registry.Boolean(True, _("""Determines whether the bot will periodically 706 flush data and configuration files to disk. Generally, the only time 707 you'll want to set this to False is when you want to modify those 708 configuration files by hand and don't want the bot to flush its current 709 version over your modifications. Do note that if you change this to False 710 inside the bot, your changes won't be flushed. To make this change 711 permanent, you must edit the registry yourself."""))) 712 713 714### 715# supybot.commands. For stuff relating to commands. 716### 717registerGroup(supybot, 'commands') 718 719class ValidQuotes(registry.Value): 720 """Value must consist solely of \", ', and ` characters.""" 721 __slots__ = () 722 def setValue(self, v): 723 if [c for c in v if c not in '"`\'']: 724 self.error() 725 super(ValidQuotes, self).setValue(v) 726 727 def __str__(self): 728 return str(self.value) 729 730registerChannelValue(supybot.commands, 'quotes', 731 ValidQuotes('"', _("""Determines what characters are valid for quoting 732 arguments to commands in order to prevent them from being tokenized. 733 """))) 734# This is a GlobalValue because bot owners should be able to say, "There will 735# be no nesting at all on this bot." Individual channels can just set their 736# brackets to the empty string. 737registerGlobalValue(supybot.commands, 'nested', 738 registry.Boolean(True, _("""Determines whether the bot will allow nested 739 commands, which rule. You definitely should keep this on."""))) 740registerGlobalValue(supybot.commands.nested, 'maximum', 741 registry.PositiveInteger(10, _("""Determines what the maximum number of 742 nested commands will be; users will receive an error if they attempt 743 commands more nested than this."""))) 744 745class ValidBrackets(registry.OnlySomeStrings): 746 __slots__ = () 747 validStrings = ('', '[]', '<>', '{}', '()') 748 749registerChannelValue(supybot.commands.nested, 'brackets', 750 ValidBrackets('[]', _("""Supybot allows you to specify what brackets 751 are used for your nested commands. Valid sets of brackets include 752 [], <>, {}, and (). [] has strong historical motivation, but <> or 753 () might be slightly superior because they cannot occur in a nick. 754 If this string is empty, nested commands will not be allowed in this 755 channel."""))) 756registerChannelValue(supybot.commands.nested, 'pipeSyntax', 757 registry.Boolean(False, _("""Supybot allows nested commands. Enabling this 758 option will allow nested commands with a syntax similar to UNIX pipes, for 759 example: 'bot: foo | bar'."""))) 760 761registerGroup(supybot.commands, 'defaultPlugins', 762 orderAlphabetically=True, help=_("""Determines what commands have default 763 plugins set, and which plugins are set to be the default for each of those 764 commands.""")) 765registerGlobalValue(supybot.commands.defaultPlugins, 'importantPlugins', 766 registry.SpaceSeparatedSetOfStrings( 767 ['Admin', 'Channel', 'Config', 'Misc', 'Owner', 'User'], 768 _("""Determines what plugins automatically get precedence over all 769 other plugins when selecting a default plugin for a command. By 770 default, this includes the standard loaded plugins. You probably 771 shouldn't change this if you don't know what you're doing; if you do 772 know what you're doing, then also know that this set is 773 case-sensitive."""))) 774 775# For this config variable to make sense, it must no be writable via IRC. 776# Make sure it is always blacklisted from the Config plugin. 777registerGlobalValue(supybot.commands, 'allowShell', 778 registry.Boolean(True, _("""Allows this bot's owner user to use commands 779 that grants them shell access. This config variable exists in case you want 780 to prevent MITM from the IRC network itself (vulnerable IRCd or IRCops) 781 from gaining shell access to the bot's server by impersonating the owner. 782 Setting this to False also disables plugins and commands that can be 783 used to indirectly gain shell access."""))) 784 785# supybot.commands.disabled moved to callbacks for canonicalName. 786 787### 788# supybot.abuse. For stuff relating to abuse of the bot. 789### 790registerGroup(supybot, 'abuse') 791registerGroup(supybot.abuse, 'flood') 792registerGlobalValue(supybot.abuse.flood, 'interval', 793 registry.PositiveInteger(60, _("""Determines the interval used for 794 the history storage."""))) 795registerGlobalValue(supybot.abuse.flood, 'command', 796 registry.Boolean(True, _("""Determines whether the bot will defend itself 797 against command-flooding."""))) 798registerGlobalValue(supybot.abuse.flood.command, 'maximum', 799 registry.PositiveInteger(12, _("""Determines how many commands users are 800 allowed per minute. If a user sends more than this many commands in any 801 60 second period, they will be ignored for 802 supybot.abuse.flood.command.punishment seconds."""))) 803registerGlobalValue(supybot.abuse.flood.command, 'punishment', 804 registry.PositiveInteger(300, _("""Determines how many seconds the bot 805 will ignore users who flood it with commands."""))) 806registerGlobalValue(supybot.abuse.flood.command, 'notify', 807 registry.Boolean(True, _("""Determines whether the bot will notify people 808 that they're being ignored for command flooding."""))) 809 810registerGlobalValue(supybot.abuse.flood.command, 'invalid', 811 registry.Boolean(True, _("""Determines whether the bot will defend itself 812 against invalid command-flooding."""))) 813registerGlobalValue(supybot.abuse.flood.command.invalid, 'maximum', 814 registry.PositiveInteger(5, _("""Determines how many invalid commands users 815 are allowed per minute. If a user sends more than this many invalid 816 commands in any 60 second period, they will be ignored for 817 supybot.abuse.flood.command.invalid.punishment seconds. Typically, this 818 value is lower than supybot.abuse.flood.command.maximum, since it's far 819 less likely (and far more annoying) for users to flood with invalid 820 commands than for them to flood with valid commands."""))) 821registerGlobalValue(supybot.abuse.flood.command.invalid, 'punishment', 822 registry.PositiveInteger(600, _("""Determines how many seconds the bot 823 will ignore users who flood it with invalid commands. Typically, this 824 value is higher than supybot.abuse.flood.command.punishment, since it's far 825 less likely (and far more annoying) for users to flood with invalid 826 commands than for them to flood with valid commands."""))) 827registerGlobalValue(supybot.abuse.flood.command.invalid, 'notify', 828 registry.Boolean(True, _("""Determines whether the bot will notify people 829 that they're being ignored for invalid command flooding."""))) 830 831 832### 833# supybot.drivers. For stuff relating to Supybot's drivers (duh!) 834### 835registerGroup(supybot, 'drivers') 836registerGlobalValue(supybot.drivers, 'poll', 837 registry.PositiveFloat(1.0, _("""Determines the default length of time a 838 driver should block waiting for input."""))) 839 840class ValidDriverModule(registry.OnlySomeStrings): 841 __slots__ = () 842 validStrings = ('default', 'Socket', 'Twisted') 843 844registerGlobalValue(supybot.drivers, 'module', 845 ValidDriverModule('default', _("""Determines what driver module the 846 bot will use. The default is Socket which is simple and stable 847 and supports SSL. Twisted doesn't work if the IRC server which 848 you are connecting to has IPv6 (most of them do)."""))) 849 850registerGlobalValue(supybot.drivers, 'maxReconnectWait', 851 registry.PositiveFloat(300.0, _("""Determines the maximum time the bot will 852 wait before attempting to reconnect to an IRC server. The bot may, of 853 course, reconnect earlier if possible."""))) 854 855### 856# supybot.directories, for stuff relating to directories. 857### 858 859# XXX This shouldn't make directories willy-nilly. As it is now, if it's 860# configured, it'll still make the default directories, I think. 861class Directory(registry.String): 862 __slots__ = () 863 def __call__(self): 864 # ??? Should we perhaps always return an absolute path here? 865 v = super(Directory, self).__call__() 866 if not os.path.exists(v): 867 os.mkdir(v) 868 return v 869 870 def dirize(self, filename): 871 myself = self() 872 if os.path.isabs(filename): 873 filename = os.path.abspath(filename) 874 selfAbs = os.path.abspath(myself) 875 commonPrefix = os.path.commonprefix([selfAbs, filename]) 876 filename = filename[len(commonPrefix):] 877 elif not os.path.isabs(myself): 878 if filename.startswith(myself): 879 filename = filename[len(myself):] 880 filename = filename.lstrip(os.path.sep) # Stupid os.path.join! 881 return os.path.join(myself, filename) 882 883class DataFilename(registry.String): 884 __slots__ = () 885 def __call__(self): 886 v = super(DataFilename, self).__call__() 887 dataDir = supybot.directories.data() 888 if not v.startswith(dataDir): 889 v = os.path.basename(v) 890 v = os.path.join(dataDir, v) 891 self.setValue(v) 892 return v 893 894class DataFilenameDirectory(DataFilename, Directory): 895 __slots__ = () 896 def __call__(self): 897 v = DataFilename.__call__(self) 898 v = Directory.__call__(self) 899 return v 900 901registerGroup(supybot, 'directories') 902registerGlobalValue(supybot.directories, 'conf', 903 Directory('conf', _("""Determines what directory configuration data is 904 put into."""))) 905registerGlobalValue(supybot.directories, 'data', 906 Directory('data', _("""Determines what directory data is put into."""))) 907registerGlobalValue(supybot.directories, 'backup', 908 Directory('backup', _("""Determines what directory backup data is put 909 into. Set it to /dev/null to disable backup (it is a special value, 910 so it also works on Windows and systems without /dev/null)."""))) 911registerGlobalValue(supybot.directories, 'log', 912 Directory('logs', """Determines what directory the bot will store its 913 logfiles in.""")) 914registerGlobalValue(supybot.directories.data, 'tmp', 915 DataFilenameDirectory('tmp', _("""Determines what directory temporary files 916 are put into."""))) 917registerGlobalValue(supybot.directories.data, 'web', 918 DataFilenameDirectory('web', _("""Determines what directory files of the 919 web server (templates, custom images, ...) are put into."""))) 920 921def _update_tmp(): 922 utils.file.AtomicFile.default.tmpDir = supybot.directories.data.tmp 923supybot.directories.data.tmp.addCallback(_update_tmp) 924_update_tmp() 925def _update_backup(): 926 utils.file.AtomicFile.default.backupDir = supybot.directories.backup 927supybot.directories.backup.addCallback(_update_backup) 928_update_backup() 929 930registerGlobalValue(supybot.directories, 'plugins', 931 registry.CommaSeparatedListOfStrings([], _("""Determines what directories 932 the bot will look for plugins in. Accepts a comma-separated list of 933 strings. 934 This means that to add another directory, you can nest the former value and 935 add a new one. E.g. you can say: bot: 'config supybot.directories.plugins 936 [config supybot.directories.plugins], newPluginDirectory'."""))) 937 938registerGlobalValue(supybot, 'plugins', 939 registry.SpaceSeparatedSetOfStrings([], _("""Determines what plugins will 940 be loaded."""), orderAlphabetically=True)) 941registerGlobalValue(supybot.plugins, 'alwaysLoadImportant', 942 registry.Boolean(True, _("""Determines whether the bot will always load 943 important plugins (Admin, Channel, Config, Misc, Owner, and User) 944 regardless of what their configured state is. Generally, if these plugins 945 are configured not to load, you didn't do it on purpose, and you still 946 want them to load. Users who don't want to load these plugins are smart 947 enough to change the value of this variable appropriately :)"""))) 948 949### 950# supybot.databases. For stuff relating to Supybot's databases (duh!) 951### 952class Databases(registry.SpaceSeparatedListOfStrings): 953 __slots__ = () 954 def __call__(self): 955 v = super(Databases, self).__call__() 956 if not v: 957 v = ['anydbm', 'dbm', 'cdb', 'flat', 'pickle'] 958 if 'sqlite' in sys.modules: 959 v.insert(0, 'sqlite') 960 if 'sqlite3' in sys.modules: 961 v.insert(0, 'sqlite3') 962 if 'sqlalchemy' in sys.modules: 963 v.insert(0, 'sqlalchemy') 964 return v 965 966 def serialize(self): 967 return ' '.join(self.value) 968 969registerGlobalValue(supybot, 'databases', 970 Databases([], _("""Determines what databases are available for use. If this 971 value is not configured (that is, if its value is empty) then sane defaults 972 will be provided."""))) 973 974registerGroup(supybot.databases, 'users') 975registerGlobalValue(supybot.databases.users, 'filename', 976 registry.String('users.conf', _("""Determines what filename will be used 977 for the users database. This file will go into the directory specified by 978 the supybot.directories.conf variable."""))) 979registerGlobalValue(supybot.databases.users, 'timeoutIdentification', 980 registry.Integer(0, _("""Determines how long it takes identification to 981 time out. If the value is less than or equal to zero, identification never 982 times out."""))) 983registerGlobalValue(supybot.databases.users, 'allowUnregistration', 984 registry.Boolean(False, _("""Determines whether the bot will allow users to 985 unregister their users. This can wreak havoc with already-existing 986 databases, so by default we don't allow it. Enable this at your own risk. 987 (Do also note that this does not prevent the owner of the bot from using 988 the unregister command.) 989 """))) 990 991registerGroup(supybot.databases, 'ignores') 992registerGlobalValue(supybot.databases.ignores, 'filename', 993 registry.String('ignores.conf', _("""Determines what filename will be used 994 for the ignores database. This file will go into the directory specified 995 by the supybot.directories.conf variable."""))) 996 997registerGroup(supybot.databases, 'channels') 998registerGlobalValue(supybot.databases.channels, 'filename', 999 registry.String('channels.conf', _("""Determines what filename will be used 1000 for the channels database. This file will go into the directory specified 1001 by the supybot.directories.conf variable."""))) 1002 1003# TODO This will need to do more in the future (such as making sure link.allow 1004# will let the link occur), but for now let's just leave it as this. 1005class ChannelSpecific(registry.Boolean): 1006 __slots__ = () 1007 def getChannelLink(self, channel): 1008 channelSpecific = supybot.databases.plugins.channelSpecific 1009 channels = [channel] 1010 def hasLinkChannel(channel): 1011 if not get(channelSpecific, channel): 1012 lchannel = get(channelSpecific.link, channel) 1013 if not get(channelSpecific.link.allow, lchannel): 1014 return False 1015 return channel != lchannel 1016 return False 1017 lchannel = channel 1018 while hasLinkChannel(lchannel): 1019 lchannel = get(channelSpecific.link, lchannel) 1020 if lchannel not in channels: 1021 channels.append(lchannel) 1022 else: 1023 # Found a cyclic link. We'll just use the current channel 1024 lchannel = channel 1025 break 1026 return lchannel 1027 1028registerGroup(supybot.databases, 'plugins') 1029 1030registerChannelValue(supybot.databases.plugins, 'requireRegistration', 1031 registry.Boolean(True, _("""Determines whether the bot will require user 1032 registration to use 'add' commands in database-based Supybot 1033 plugins."""))) 1034registerChannelValue(supybot.databases.plugins, 'channelSpecific', 1035 ChannelSpecific(True, _("""Determines whether database-based plugins that 1036 can be channel-specific will be so. This can be overridden by individual 1037 channels. Do note that the bot needs to be restarted immediately after 1038 changing this variable or your db plugins may not work for your channel; 1039 also note that you may wish to set 1040 supybot.databases.plugins.channelSpecific.link appropriately if you wish 1041 to share a certain channel's databases globally."""))) 1042registerChannelValue(supybot.databases.plugins.channelSpecific, 'link', 1043 ValidChannel('#', _("""Determines what channel global 1044 (non-channel-specific) databases will be considered a part of. This is 1045 helpful if you've been running channel-specific for awhile and want to turn 1046 the databases for your primary channel into global databases. If 1047 supybot.databases.plugins.channelSpecific.link.allow prevents linking, the 1048 current channel will be used. Do note that the bot needs to be restarted 1049 immediately after changing this variable or your db plugins may not work 1050 for your channel."""))) 1051registerChannelValue(supybot.databases.plugins.channelSpecific.link, 'allow', 1052 registry.Boolean(True, _("""Determines whether another channel's global 1053 (non-channel-specific) databases will be allowed to link to this channel's 1054 databases. Do note that the bot needs to be restarted immediately after 1055 changing this variable or your db plugins may not work for your channel. 1056 """))) 1057 1058 1059class CDB(registry.Boolean): 1060 __slots__ = () 1061 def connect(self, filename): 1062 from . import cdb 1063 basename = os.path.basename(filename) 1064 journalName = supybot.directories.data.tmp.dirize(basename+'.journal') 1065 return cdb.open_db(filename, 'c', 1066 journalName=journalName, 1067 maxmods=self.maximumModifications()) 1068 1069registerGroup(supybot.databases, 'types') 1070registerGlobalValue(supybot.databases.types, 'cdb', CDB(True, _("""Determines 1071 whether CDB databases will be allowed as a database implementation."""))) 1072registerGlobalValue(supybot.databases.types.cdb, 'maximumModifications', 1073 registry.Probability(0.5, _("""Determines how often CDB databases will have 1074 their modifications flushed to disk. When the number of modified records 1075 is greater than this fraction of the total number of records, the database 1076 will be entirely flushed to disk."""))) 1077 1078# XXX Configuration variables for dbi, sqlite, flat, mysql, etc. 1079 1080### 1081# Protocol information. 1082### 1083originalIsNick = ircutils.isNick 1084def isNick(s, strictRfc=None, **kw): 1085 if strictRfc is None: 1086 strictRfc = supybot.protocols.irc.strictRfc() 1087 return originalIsNick(s, strictRfc=strictRfc, **kw) 1088ircutils.isNick = isNick 1089 1090### 1091# supybot.protocols 1092### 1093registerGroup(supybot, 'protocols') 1094 1095### 1096# supybot.protocols.irc 1097### 1098registerGroup(supybot.protocols, 'irc') 1099 1100class Banmask(registry.SpaceSeparatedSetOfStrings): 1101 __slots__ = ('__parent', '__dict__') # __dict__ is needed to set __doc__ 1102 validStrings = ('exact', 'nick', 'user', 'host') 1103 def __init__(self, *args, **kwargs): 1104 assert self.validStrings, 'There must be some valid strings. ' \ 1105 'This is a bug.' 1106 self.__parent = super(Banmask, self) 1107 self.__parent.__init__(*args, **kwargs) 1108 self.__doc__ = format('Valid values include %L.', 1109 list(map(repr, self.validStrings))) 1110 1111 def help(self): 1112 strings = [s for s in self.validStrings if s] 1113 return format('%s Valid strings: %L.', self._help, strings) 1114 1115 def normalize(self, s): 1116 lowered = s.lower() 1117 L = list(map(str.lower, self.validStrings)) 1118 try: 1119 i = L.index(lowered) 1120 except ValueError: 1121 return s # This is handled in setValue. 1122 return self.validStrings[i] 1123 1124 def setValue(self, v): 1125 v = list(map(self.normalize, v)) 1126 for s in v: 1127 if s not in self.validStrings: 1128 self.error() 1129 self.__parent.setValue(self.List(v)) 1130 1131 def makeBanmask(self, hostmask, options=None, channel=None): 1132 """Create a banmask from the given hostmask. If a style of banmask 1133 isn't specified via options, the value of 1134 conf.supybot.protocols.irc.banmask is used. 1135 1136 options - A list specifying which parts of the hostmask should 1137 explicitly be matched: nick, user, host. If 'exact' is given, then 1138 only the exact hostmask will be used.""" 1139 if not channel: 1140 channel = dynamic.channel 1141 assert channel is None or ircutils.isChannel(channel) 1142 (nick, user, host) = ircutils.splitHostmask(hostmask) 1143 bnick = '*' 1144 buser = '*' 1145 bhost = '*' 1146 if not options: 1147 options = get(supybot.protocols.irc.banmask, channel) 1148 for option in options: 1149 if option == 'nick': 1150 bnick = nick 1151 elif option == 'user': 1152 buser = user 1153 elif option == 'host': 1154 bhost = host 1155 elif option == 'exact': 1156 return hostmask 1157 if (bnick, buser, bhost) == ('*', '*', '*') and \ 1158 ircutils.isUserHostmask(hostmask): 1159 return hostmask 1160 return ircutils.joinHostmask(bnick, buser, bhost) 1161 1162registerChannelValue(supybot.protocols.irc, 'banmask', 1163 Banmask(['host'], _("""Determines what will be used as the 1164 default banmask style."""))) 1165 1166registerGlobalValue(supybot.protocols.irc, 'strictRfc', 1167 registry.Boolean(False, _("""Determines whether the bot will strictly 1168 follow the RFC; currently this only affects what strings are 1169 considered to be nicks. If you're using a server or a network that 1170 requires you to message a nick such as services@this.network.server 1171 then you you should set this to False."""))) 1172 1173registerGlobalValue(supybot.protocols.irc, 'certfile', 1174 registry.String('', _("""Determines what certificate file (if any) the bot 1175 will use connect with SSL sockets by default."""))) 1176 1177registerGlobalValue(supybot.protocols.irc, 'umodes', 1178 registry.String('', _("""Determines what user modes the bot will request 1179 from the server when it first connects. Many people might choose +i; some 1180 networks allow +x, which indicates to the auth services on those networks 1181 that you should be given a fake host."""))) 1182 1183registerGlobalValue(supybot.protocols.irc, 'vhost', 1184 registry.String('', _("""Determines what vhost the bot will bind to before 1185 connecting a server (IRC, HTTP, ...) via IPv4."""))) 1186 1187registerGlobalValue(supybot.protocols.irc, 'vhostv6', 1188 registry.String('', _("""Determines what vhost the bot will bind to before 1189 connecting a server (IRC, HTTP, ...) via IPv6."""))) 1190 1191registerGlobalValue(supybot.protocols.irc, 'maxHistoryLength', 1192 registry.Integer(1000, _("""Determines how many old messages the bot will 1193 keep around in its history. Changing this variable will not take effect 1194 on a network until it is reconnected."""))) 1195 1196registerGlobalValue(supybot.protocols.irc, 'throttleTime', 1197 registry.Float(1.0, _("""A floating point number of seconds to throttle 1198 queued messages -- that is, messages will not be sent faster than once per 1199 throttleTime seconds."""))) 1200 1201registerGlobalValue(supybot.protocols.irc, 'ping', 1202 registry.Boolean(True, _("""Determines whether the bot will send PINGs to 1203 the server it's connected to in order to keep the connection alive and 1204 discover earlier when it breaks. Really, this option only exists for 1205 debugging purposes: you always should make it True unless you're testing 1206 some strange server issues."""))) 1207 1208registerGlobalValue(supybot.protocols.irc.ping, 'interval', 1209 registry.Integer(120, _("""Determines the number of seconds between sending 1210 pings to the server, if pings are being sent to the server."""))) 1211 1212registerGroup(supybot.protocols.irc, 'queuing') 1213registerGlobalValue(supybot.protocols.irc.queuing, 'duplicates', 1214 registry.Boolean(False, _("""Determines whether the bot will refuse 1215 duplicated messages to be queued for delivery to the server. This is a 1216 safety mechanism put in place to prevent plugins from sending the same 1217 message multiple times; most of the time it doesn't matter, unless you're 1218 doing certain kinds of plugin hacking."""))) 1219 1220registerGroup(supybot.protocols.irc.queuing, 'rateLimit') 1221registerGlobalValue(supybot.protocols.irc.queuing.rateLimit, 'join', 1222 registry.Float(0, _("""Determines how many seconds must elapse between 1223 JOINs sent to the server."""))) 1224 1225### 1226# supybot.protocols.http 1227### 1228registerGroup(supybot.protocols, 'http') 1229registerGlobalValue(supybot.protocols.http, 'peekSize', 1230 registry.PositiveInteger(8192, _("""Determines how many bytes the bot will 1231 'peek' at when looking through a URL for a doctype or title or something 1232 similar. It'll give up after it reads this many bytes, even if it hasn't 1233 found what it was looking for."""))) 1234 1235class HttpProxy(registry.String): 1236 """Value must be a valid hostname:port string.""" 1237 __slots__ = () 1238 def setValue(self, v): 1239 proxies = {} 1240 if v != "": 1241 if isSocketAddress(v): 1242 proxies = { 1243 'http': v, 1244 'https': v 1245 } 1246 else: 1247 self.error() 1248 proxyHandler = ProxyHandler(proxies) 1249 proxyOpenerDirector = build_opener(proxyHandler) 1250 install_opener(proxyOpenerDirector) 1251 super(HttpProxy, self).setValue(v) 1252 1253registerGlobalValue(supybot.protocols.http, 'proxy', 1254 HttpProxy('', _("""Determines what HTTP proxy all HTTP requests should go 1255 through. The value should be of the form 'host:port'."""))) 1256utils.web.proxy = supybot.protocols.http.proxy 1257 1258### 1259# supybot.protocols.ssl 1260### 1261registerGroup(supybot.protocols, 'ssl') 1262registerGlobalValue(supybot.protocols.ssl, 'verifyCertificates', 1263 registry.Boolean(False, _("""Determines whether server certificates 1264 will be verified, which checks whether the server certificate is signed 1265 by a known certificate authority, and aborts the connection if it is not."""))) 1266 1267 1268### 1269# HTTP server 1270### 1271registerGroup(supybot, 'servers') 1272registerGroup(supybot.servers, 'http') 1273 1274class IP(registry.String): 1275 """Value must be a valid IP.""" 1276 __slots__ = () 1277 def setValue(self, v): 1278 if v and not utils.net.isIP(v): 1279 self.error() 1280 else: 1281 registry.String.setValue(self, v) 1282 1283class ListOfIPs(registry.SpaceSeparatedListOfStrings): 1284 __slots__ = () 1285 Value = IP 1286 1287registerGlobalValue(supybot.servers.http, 'singleStack', 1288 registry.Boolean(True, _("""If true, uses IPV6_V6ONLY to disable 1289 forwaring of IPv4 traffic to IPv6 sockets. On *nix, has the same 1290 effect as setting kernel variable net.ipv6.bindv6only to 1."""))) 1291registerGlobalValue(supybot.servers.http, 'hosts4', 1292 ListOfIPs(['0.0.0.0'], _("""Space-separated list of IPv4 hosts the HTTP server 1293 will bind."""))) 1294registerGlobalValue(supybot.servers.http, 'hosts6', 1295 ListOfIPs(['::0'], _("""Space-separated list of IPv6 hosts the HTTP server will 1296 bind."""))) 1297registerGlobalValue(supybot.servers.http, 'port', 1298 registry.Integer(8080, _("""Determines what port the HTTP server will 1299 bind."""))) 1300registerGlobalValue(supybot.servers.http, 'keepAlive', 1301 registry.Boolean(False, _("""Determines whether the server will stay 1302 alive if no plugin is using it. This also means that the server will 1303 start even if it is not used."""))) 1304registerGlobalValue(supybot.servers.http, 'favicon', 1305 registry.String('', _("""Determines the path of the file served as 1306 favicon to browsers."""))) 1307 1308 1309### 1310# Especially boring stuff. 1311### 1312registerGlobalValue(supybot, 'defaultIgnore', 1313 registry.Boolean(False, _("""Determines whether the bot will ignore 1314 unidentified users by default. Of course, that'll make it 1315 particularly hard for those users to register or identify with the bot 1316 without adding their hostmasks, but that's your problem to solve."""))) 1317 1318 1319registerGlobalValue(supybot, 'externalIP', 1320 IP('', _("""A string that is the external IP of the bot. If this is the 1321 empty string, the bot will attempt to find out its IP dynamically (though 1322 sometimes that doesn't work, hence this variable). This variable is not used 1323 by Limnoria and its built-in plugins: see supybot.protocols.irc.vhost / 1324 supybot.protocols.irc.vhost6 to set the IRC bind host, and 1325 supybot.servers.http.hosts4 / supybot.servers.http.hosts6 to set the HTTP 1326 server bind host."""))) 1327 1328class SocketTimeout(registry.PositiveInteger): 1329 """Value must be an integer greater than supybot.drivers.poll and must be 1330 greater than or equal to 1.""" 1331 __slots__ = () 1332 def setValue(self, v): 1333 if v < supybot.drivers.poll() or v < 1: 1334 self.error() 1335 registry.PositiveInteger.setValue(self, v) 1336 socket.setdefaulttimeout(self.value) 1337 1338registerGlobalValue(supybot, 'defaultSocketTimeout', 1339 SocketTimeout(10, _("""Determines what the default timeout for socket 1340 objects will be. This means that *all* sockets will timeout when this many 1341 seconds has gone by (unless otherwise modified by the author of the code 1342 that uses the sockets)."""))) 1343 1344registerGlobalValue(supybot, 'pidFile', 1345 registry.String('', _("""Determines what file the bot should write its PID 1346 (Process ID) to, so you can kill it more easily. If it's left unset (as is 1347 the default) then no PID file will be written. A restart is required for 1348 changes to this variable to take effect."""))) 1349 1350### 1351# Debugging options. 1352### 1353registerGroup(supybot, 'debug') 1354registerGlobalValue(supybot.debug, 'threadAllCommands', 1355 registry.Boolean(False, _("""Determines whether the bot will automatically 1356 thread all commands."""))) 1357registerGlobalValue(supybot.debug, 'flushVeryOften', 1358 registry.Boolean(False, _("""Determines whether the bot will automatically 1359 flush all flushers *very* often. Useful for debugging when you don't know 1360 what's breaking or when, but think that it might be logged."""))) 1361 1362 1363# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: 1364