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