1#!/usr/bin/env python 2 3### 4# Copyright (c) 2003-2004, Jeremiah Fincher 5# Copyright (c) 2009, James McCoy 6# All rights reserved. 7# 8# Redistribution and use in source and binary forms, with or without 9# modification, are permitted provided that the following conditions are met: 10# 11# * Redistributions of source code must retain the above copyright notice, 12# this list of conditions, and the following disclaimer. 13# * Redistributions in binary form must reproduce the above copyright notice, 14# this list of conditions, and the following disclaimer in the 15# documentation and/or other materials provided with the distribution. 16# * Neither the name of the author of this software nor the name of 17# contributors to this software may be used to endorse or promote products 18# derived from this software without specific prior written consent. 19# 20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30# POSSIBILITY OF SUCH DAMAGE. 31### 32 33from __future__ import print_function 34 35import os 36import sys 37 38def error(s): 39 sys.stderr.write(s) 40 if not s.endswith(os.linesep): 41 sys.stderr.write(os.linesep) 42 sys.exit(-1) 43 44if sys.version_info < (2, 6, 0): 45 error('This program requires Python >= 2.6.0') 46 47import supybot 48 49import re 50import time 51import pydoc 52import pprint 53import socket 54import logging 55import optparse 56 57try: 58 import supybot.i18n as i18n 59except ImportError: 60 sys.stderr.write("""Error: 61 You are running a mix of Limnoria and stock Supybot code. Although you run 62 one of Limnoria\'s executables, Python tries to load stock 63 Supybot\'s library. To fix this issue, uninstall Supybot 64 ("%s -m pip uninstall supybot" should do the job) 65 and install Limnoria again. 66 For your information, Supybot's libraries are installed here: 67 %s\n""" % 68 (sys.executable, '\n '.join(supybot.__path__))) 69 exit(-1) 70 71import supybot.ansi as ansi 72import supybot.utils as utils 73import supybot.ircutils as ircutils 74import supybot.registry as registry 75# supybot.plugin, supybot.log, and supybot.conf will be imported later, 76# because we need to set a language before loading the conf 77 78import supybot.questions as questions 79from supybot.questions import output, yn, anything, something, expect, getpass 80 81def getPlugins(pluginDirs): 82 plugins = set([]) 83 join = os.path.join 84 for pluginDir in pluginDirs: 85 try: 86 for filename in os.listdir(pluginDir): 87 fname = join(pluginDir, filename) 88 if (filename.endswith('.py') or os.path.isdir(fname)) \ 89 and filename[0].isupper(): 90 plugins.add(os.path.splitext(filename)[0]) 91 except OSError: 92 continue 93 plugins.discard('Owner') 94 plugins = list(plugins) 95 plugins.sort() 96 return plugins 97 98def loadPlugin(name): 99 import supybot.plugin as plugin 100 try: 101 module = plugin.loadPluginModule(name) 102 if hasattr(module, 'Class'): 103 return module 104 else: 105 output("""That plugin loaded fine, but didn't seem to be a real 106 Supybot plugin; there was no Class variable to tell us what class 107 to load when we load the plugin. We'll skip over it for now, but 108 you can always add it later.""") 109 return None 110 except Exception as e: 111 output("""We encountered a bit of trouble trying to load plugin %r. 112 Python told us %r. We'll skip over it for now, you can always add it 113 later.""" % (name, utils.gen.exnToString(e))) 114 return None 115 116def describePlugin(module, showUsage): 117 if module.__doc__: 118 output(module.__doc__, unformatted=False) 119 elif hasattr(module.Class, '__doc__'): 120 output(module.Class.__doc__, unformatted=False) 121 else: 122 output("""Unfortunately, this plugin doesn't seem to have any 123 documentation. Sorry about that.""") 124 if showUsage: 125 if hasattr(module, 'example'): 126 if yn('This plugin has a usage example. ' 127 'Would you like to see it?', default=False): 128 pydoc.pager(module.example) 129 else: 130 output("""This plugin has no usage example.""") 131 132def clearLoadedPlugins(plugins, pluginRegistry): 133 for plugin in plugins: 134 try: 135 pluginKey = pluginRegistry.get(plugin) 136 if pluginKey(): 137 plugins.remove(plugin) 138 except registry.NonExistentRegistryEntry: 139 continue 140 141_windowsVarRe = re.compile(r'%(\w+)%') 142def getDirectoryName(default, basedir=os.curdir, prompt=True): 143 done = False 144 while not done: 145 if prompt: 146 dir = something('What directory do you want to use?', 147 default=os.path.join(basedir, default)) 148 else: 149 dir = os.path.join(basedir, default) 150 orig_dir = dir 151 dir = os.path.expanduser(dir) 152 dir = _windowsVarRe.sub(r'$\1', dir) 153 dir = os.path.expandvars(dir) 154 dir = os.path.abspath(dir) 155 try: 156 os.makedirs(dir) 157 done = True 158 except OSError as e: 159 # 17 is File exists for Linux (and likely other POSIX systems) 160 # 183 is the same for Windows 161 if e.args[0] == 17 or (os.name == 'nt' and e.args[0] == 183): 162 done = True 163 else: 164 output("""Sorry, I couldn't make that directory for some 165 reason. The Operating System told me %s. You're going to 166 have to pick someplace else.""" % e) 167 prompt = True 168 return (dir, os.path.dirname(orig_dir)) 169 170 171def main(): 172 import supybot.version as version 173 parser = optparse.OptionParser(usage='Usage: %prog [options]', 174 version='Supybot %s' % version.version) 175 parser.add_option('', '--allow-root', action='store_true', 176 dest='allowRoot', 177 help='Determines whether the wizard will be allowed to ' 178 'run as root. You don\'t want this. Don\'t do it.' 179 ' Even if you think you want it, you don\'t. ' 180 'You\'re probably dumb if you do this.') 181 parser.add_option('', '--allow-home', action='store_true', 182 dest='allowHome', 183 help='Determines whether the wizard will be allowed to ' 184 'run directly in the HOME directory. ' 185 'You should not do this unless you want it to ' 186 'create multiple files in your HOME directory.') 187 parser.add_option('', '--allow-bin', action='store_true', 188 dest='allowBin', 189 help='Determines whether the wizard will be allowed to ' 190 'run directly in a directory for binaries (eg. ' 191 '/usr/bin, /usr/local/bin, or ~/.local/bin). ' 192 'You should not do this unless you want it to ' 193 'create multiple non-binary files in directory ' 194 'that should only contain binaries.') 195 parser.add_option('', '--no-network', action='store_false', 196 dest='network', 197 help='Determines whether the wizard will be allowed to ' 198 'run without a network connection.') 199 (options, args) = parser.parse_args() 200 if os.name == 'posix': 201 if (os.getuid() == 0 or os.geteuid() == 0) and not options.allowRoot: 202 error('Please, don\'t run this as root.') 203 if os.name == 'posix': 204 if (os.getcwd() == os.path.expanduser('~')) and not options.allowHome: 205 error('Please, don\'t run this in your HOME directory.') 206 if (os.path.split(os.getcwd())[-1] == 'bin') and not options.allowBin: 207 error('Please, don\'t run this in a "bin" directory.') 208 if os.path.isfile(os.path.join('scripts', 'supybot-wizard')) or \ 209 os.path.isfile(os.path.join('..', 'scripts', 'supybot-wizard')): 210 print('') 211 print('+------------------------------------------------------------+') 212 print('| +--------------------------------------------------------+ |') 213 print('| | Warning: It looks like you are running the wizard from | |') 214 print('| | the Supybot source directory. This is not recommended. | |') 215 print('| | Please press Ctrl-C and change to another directory. | |') 216 print('| +--------------------------------------------------------+ |') 217 print('+------------------------------------------------------------+') 218 print('') 219 220 if args: 221 parser.error('This program takes no non-option arguments.') 222 output("""This is a wizard to help you start running supybot. What it 223 will do is create the necessary config files based on the options you 224 select here. So hold on tight and be ready to be interrogated :)""") 225 226 227 output("""First of all, we can bold the questions you're asked so you can 228 easily distinguish the mostly useless blather (like this) from the 229 questions that you actually have to answer.""") 230 if yn('Would you like to try this bolding?', default=True): 231 questions.useBold = True 232 if not yn('Do you see this in bold?'): 233 output("""Sorry, it looks like your terminal isn't ANSI compliant. 234 Try again some other day, on some other terminal :)""") 235 questions.useBold = False 236 else: 237 output("""Great!""") 238 239 ### 240 # Preliminary questions. 241 ### 242 output("""We've got some preliminary things to get out of the way before 243 we can really start asking you questions that directly relate to what your 244 bot is going to be like.""") 245 246 # Advanced? 247 output("""We want to know if you consider yourself an advanced Supybot 248 user because some questions are just utterly boring and useless for new 249 users. Others might not make sense unless you've used Supybot for some 250 time.""") 251 advanced = yn('Are you an advanced Supybot user?', default=False) 252 253 # Language? 254 output("""This version of Supybot (known as Limnoria) includes another 255 language. This can be changed at any time. You need to answer with a short 256 id for the language, such as 'en', 'fr', 'it' (without the quotes). If 257 you want to use English, just press enter.""") 258 language = something('What language do you want to use?', default='en') 259 260 class Empty: 261 """This is a hack to allow the i18n to get the current language, before 262 loading the conf module, before the conf module needs i18n to set the 263 default strings.""" 264 def __call__(self): 265 return self.value 266 fakeConf = Empty() 267 fakeConf.supybot = Empty() 268 fakeConf.supybot.language = Empty() 269 fakeConf.supybot.language.value = language 270 i18n.conf = fakeConf 271 i18n.currentLocale = language 272 i18n.reloadLocales() 273 import supybot.conf as conf 274 i18n.import_conf() # It imports the real conf module 275 276 ### Directories. 277 # We set these variables in cache because otherwise conf and log will 278 # create directories for the default values, which might not be what the 279 # user wants. 280 if advanced: 281 output("""Now we've got to ask you some questions about where some of 282 your directories are (or, perhaps, will be :)). If you're running this 283 wizard from the directory you'll actually be starting your bot from and 284 don't mind creating some directories in the current directory, then 285 just don't give answers to these questions and we'll create the 286 directories we need right here in this directory.""") 287 288 # conf.supybot.directories.log 289 output("""Your bot will need to put its logs somewhere. Do you have 290 any specific place you'd like them? If not, just press enter and we'll 291 make a directory named "logs" right here.""") 292 (logDir, basedir) = getDirectoryName('logs') 293 conf.supybot.directories.log.setValue(logDir) 294 import supybot.log as log 295 log._stdoutHandler.setLevel(100) # *Nothing* gets through this! 296 297 # conf.supybot.directories.data 298 output("""Your bot will need to put various data somewhere. Things 299 like databases, downloaded files, etc. Do you have any specific place 300 you'd like the bot to put these things? If not, just press enter and 301 we'll make a directory named "data" right here.""") 302 (dataDir, basedir) = getDirectoryName('data', basedir=basedir) 303 conf.supybot.directories.data.setValue(dataDir) 304 305 # conf.supybot.directories.conf 306 output("""Your bot must know where to find its configuration files. 307 It'll probably only make one or two, but it's gotta have some place to 308 put them. Where should that place be? If you don't care, just press 309 enter and we'll make a directory right here named "conf" where it'll 310 store its stuff. """) 311 (confDir, basedir) = getDirectoryName('conf', basedir=basedir) 312 conf.supybot.directories.conf.setValue(confDir) 313 314 # conf.supybot.directories.backup 315 output("""Your bot must know where to place backups of its conf and 316 data files. Where should that place be? If you don't care, just press 317 enter and we'll make a directory right here named "backup" where it'll 318 store its stuff.""") 319 (backupDir, basedir) = getDirectoryName('backup', basedir=basedir) 320 conf.supybot.directories.backup.setValue(backupDir) 321 322 # conf.supybot.directories.data.tmp 323 output("""Your bot needs a directory to put temporary files (used 324 mainly to atomically update its configuration files).""") 325 (tmpDir, basedir) = getDirectoryName('tmp', basedir=basedir) 326 conf.supybot.directories.data.tmp.setValue(tmpDir) 327 328 # conf.supybot.directories.data.web 329 output("""Your bot needs a directory to put files related to the web 330 server (templates, CSS).""") 331 (webDir, basedir) = getDirectoryName('web', basedir=basedir) 332 conf.supybot.directories.data.web.setValue(webDir) 333 334 335 # imports callbacks, which imports ircdb, which requires 336 # directories.conf 337 import supybot.plugin as plugin 338 339 # pluginDirs 340 output("""Your bot will also need to know where to find its plugins at. 341 Of course, it already knows where the plugins that it came with are, 342 but your own personal plugins that you write for will probably be 343 somewhere else.""") 344 pluginDirs = conf.supybot.directories.plugins() 345 output("""Currently, the bot knows about the following directories:""") 346 output(format('%L', pluginDirs + [plugin._pluginsDir])) 347 while yn('Would you like to add another plugin directory? ' 348 'Adding a local plugin directory is good style.', 349 default=True): 350 (pluginDir, _) = getDirectoryName('plugins', basedir=basedir) 351 if pluginDir not in pluginDirs: 352 pluginDirs.append(pluginDir) 353 conf.supybot.directories.plugins.setValue(pluginDirs) 354 else: 355 output("""Your bot needs to create some directories in order to store 356 the various log, config, and data files.""") 357 basedir = something("""Where would you like to create these 358 directories?""", default=os.curdir) 359 360 # conf.supybot.directories.log 361 (logDir, basedir) = getDirectoryName('logs', 362 basedir=basedir, prompt=False) 363 conf.supybot.directories.log.setValue(logDir) 364 365 # conf.supybot.directories.data 366 (dataDir, basedir) = getDirectoryName('data', 367 basedir=basedir, prompt=False) 368 conf.supybot.directories.data.setValue(dataDir) 369 (tmpDir, basedir) = getDirectoryName('tmp', 370 basedir=basedir, prompt=False) 371 conf.supybot.directories.data.tmp.setValue(tmpDir) 372 (webDir, basedir) = getDirectoryName('web', 373 basedir=basedir, prompt=False) 374 conf.supybot.directories.data.web.setValue(webDir) 375 376 # conf.supybot.directories.conf 377 (confDir, basedir) = getDirectoryName('conf', 378 basedir=basedir, prompt=False) 379 conf.supybot.directories.conf.setValue(confDir) 380 381 # conf.supybot.directories.backup 382 (backupDir, basedir) = getDirectoryName('backup', 383 basedir=basedir, prompt=False) 384 conf.supybot.directories.backup.setValue(backupDir) 385 386 # pluginDirs 387 pluginDirs = conf.supybot.directories.plugins() 388 (pluginDir, _) = getDirectoryName('plugins', 389 basedir=basedir, prompt=False) 390 if pluginDir not in pluginDirs: 391 pluginDirs.append(pluginDir) 392 conf.supybot.directories.plugins.setValue(pluginDirs) 393 394 import supybot.log as log 395 log._stdoutHandler.setLevel(100) # *Nothing* gets through this! 396 import supybot.plugin as plugin 397 398 output("Good! We're done with the directory stuff.") 399 400 ### 401 # Bot stuff 402 ### 403 output("""Now we're going to ask you things that actually relate to the 404 bot you'll be running.""") 405 406 network = None 407 while not network: 408 output("""First, we need to know the name of the network you'd like to 409 connect to. Not the server host, mind you, but the name of the 410 network. If you plan to connect to chat.freenode.net, for instance, 411 you should answer this question with 'freenode' (without the quotes). 412 """) 413 network = something('What IRC network will you be connecting to?') 414 if '.' in network: 415 output("""There shouldn't be a '.' in the network name. Remember, 416 this is the network name, not the actual server you plan to connect 417 to.""") 418 network = None 419 elif not registry.isValidRegistryName(network): 420 output("""That's not a valid name for one reason or another. Please 421 pick a simpler name, one more likely to be valid.""") 422 network = None 423 424 conf.supybot.networks.setValue([network]) 425 network = conf.registerNetwork(network) 426 427 defaultServer = None 428 server = None 429 ip = None 430 while not ip: 431 serverString = something('What server would you like to connect to?', 432 default=defaultServer) 433 if options.network: 434 try: 435 output("""Looking up %s...""" % serverString) 436 ip = socket.gethostbyname(serverString) 437 except: 438 output("""Sorry, I couldn't find that server. Perhaps you 439 misspelled it? Also, be sure not to put the port in the 440 server's name -- we'll ask you about that later.""") 441 else: 442 ip = 'no network available' 443 444 output("""Found %s (%s).""" % (serverString, ip)) 445 446 # conf.supybot.networks.<network>.ssl 447 output("""Most networks allow you to use a secure connection via SSL. 448 If you are not sure whether this network supports SSL or not, check 449 its website.""") 450 use_ssl = yn('Do you want to use an SSL connection?', default=True) 451 network.ssl.setValue(use_ssl) 452 453 output("""IRC servers almost always accept connections on port 454 6697 (or 6667 when not using SSL). They can, however, accept connections 455 anywhere their admins feel like they want to accept connections from.""") 456 if yn('Does this server require connection on a non-standard port?', 457 default=False): 458 port = 0 459 while not port: 460 port = something('What port is that?') 461 try: 462 i = int(port) 463 if not (0 < i < 65536): 464 raise ValueError() 465 except ValueError: 466 output("""That's not a valid port.""") 467 port = 0 468 else: 469 if network.ssl.value: 470 port = 6697 471 else: 472 port = 6667 473 server = ':'.join([serverString, str(port)]) 474 network.servers.setValue([server]) 475 476 # conf.supybot.nick 477 # Force the user into specifying a nick if it didn't have one already 478 while True: 479 nick = something('What nick would you like your bot to use?', 480 default=None) 481 try: 482 conf.supybot.nick.set(nick) 483 break 484 except registry.InvalidRegistryValue: 485 output("""That's not a valid nick. Go ahead and pick another.""") 486 487 # conf.supybot.user 488 if advanced: 489 output("""If you've ever done a /whois on a person, you know that IRC 490 provides a way for users to show the world their full name. What would 491 you like your bot's full name to be? If you don't care, just press 492 enter and it'll be the same as your bot's nick.""") 493 user = '' 494 user = something('What would you like your bot\'s full name to be?', 495 default=nick) 496 conf.supybot.user.set(user) 497 # conf.supybot.ident (if advanced) 498 defaultIdent = 'limnoria' 499 if advanced: 500 output("""IRC servers also allow you to set your ident, which they 501 might need if they can't find your identd server. What would you like 502 your ident to be? If you don't care, press enter and we'll use 503 'limnoria'. In fact, we prefer that you do this, because it provides 504 free advertising for Supybot when users /whois your bot. But, of 505 course, it's your call.""") 506 while True: 507 ident = something('What would you like your bot\'s ident to be?', 508 default=defaultIdent) 509 try: 510 conf.supybot.ident.set(ident) 511 break 512 except registry.InvalidRegistryValue: 513 output("""That was not a valid ident. Go ahead and pick 514 another.""") 515 else: 516 conf.supybot.ident.set(defaultIdent) 517 518 # conf.supybot.networks.<network>.password 519 output("""Some servers require a password to connect to them. Most 520 public servers don't. If you try to connect to a server and for some 521 reason it just won't work, it might be that you need to set a 522 password.""") 523 if yn('Do you want to set such a password?', default=False): 524 network.password.set(getpass()) 525 526 # conf.supybot.networks.<network>.channels 527 output("""Of course, having an IRC bot isn't the most useful thing in the 528 world unless you can make that bot join some channels.""") 529 if yn('Do you want your bot to join some channels when it connects?', 530 default=True): 531 defaultChannels = ' '.join(network.channels()) 532 output("""Separate channels with spaces. If the channel is locked 533 with a key, follow the channel name with the key separated 534 by a comma. For example: 535 #supybot-bots #mychannel,mykey #otherchannel"""); 536 while True: 537 channels = something('What channels?', default=defaultChannels) 538 try: 539 network.channels.set(channels) 540 break 541 except registry.InvalidRegistryValue as e: 542 output(""""%s" is an invalid IRC channel. Be sure to prefix 543 the channel with # (or +, or !, or &, but no one uses those 544 channels, really). Be sure the channel key (if you are 545 supplying one) does not contain a comma.""" % e.channel) 546 else: 547 network.channels.setValue([]) 548 549 ### 550 # Plugins 551 ### 552 def configurePlugin(module, advanced): 553 if hasattr(module, 'configure'): 554 output("""Beginning configuration for %s...""" % 555 module.Class.__name__) 556 module.configure(advanced) 557 print() # Blank line :) 558 output("""Done!""") 559 else: 560 conf.registerPlugin(module.__name__, currentValue=True) 561 562 plugins = getPlugins(pluginDirs + [plugin._pluginsDir]) 563 for s in ('Admin', 'User', 'Channel', 'Misc', 'Config', 'Utilities'): 564 m = loadPlugin(s) 565 if m is not None: 566 configurePlugin(m, advanced) 567 else: 568 error('There was an error loading one of the core plugins that ' 569 'under almost all circumstances are loaded. Go ahead and ' 570 'fix that error and run this script again.') 571 clearLoadedPlugins(plugins, conf.supybot.plugins) 572 573 output("""Now we're going to run you through plugin configuration. There's 574 a variety of plugins in supybot by default, but you can create and 575 add your own, of course. We'll allow you to take a look at the known 576 plugins' descriptions and configure them 577 if you like what you see.""") 578 579 # bulk 580 addedBulk = False 581 if advanced and yn('Would you like to add plugins en masse first?'): 582 addedBulk = True 583 output(format("""The available plugins are: %L.""", plugins)) 584 output("""What plugins would you like to add? If you've changed your 585 mind and would rather not add plugins in bulk like this, just press 586 enter and we'll move on to the individual plugin configuration. 587 We suggest you to add Aka, Ctcp, Later, Network, Plugin, String, 588 and Utilities""") 589 massPlugins = anything('Separate plugin names by spaces or commas:') 590 for name in re.split(r',?\s+', massPlugins): 591 module = loadPlugin(name) 592 if module is not None: 593 configurePlugin(module, advanced) 594 clearLoadedPlugins(plugins, conf.supybot.plugins) 595 596 # individual 597 if yn('Would you like to look at plugins individually?'): 598 output("""Next comes your opportunity to learn more about the plugins 599 that are available and select some (or all!) of them to run in your 600 bot. Before you have to make a decision, of course, you'll be able to 601 see a short description of the plugin and, if you choose, an example 602 session with the plugin. Let's begin.""") 603 # until we get example strings again, this will default to false 604 #showUsage =yn('Would you like the option of seeing usage examples?') 605 showUsage = False 606 name = expect('What plugin would you like to look at?', 607 plugins, acceptEmpty=True) 608 while name: 609 module = loadPlugin(name) 610 if module is not None: 611 describePlugin(module, showUsage) 612 if yn('Would you like to load this plugin?', default=True): 613 configurePlugin(module, advanced) 614 clearLoadedPlugins(plugins, conf.supybot.plugins) 615 if not yn('Would you like add another plugin?'): 616 break 617 name = expect('What plugin would you like to look at?', plugins) 618 619 ### 620 # Sundry 621 ### 622 output("""Although supybot offers a supybot-adduser script, with which 623 you can add users to your bot's user database, it's *very* important that 624 you have an owner user for you bot.""") 625 if yn('Would you like to add an owner user for your bot?', default=True): 626 import supybot.ircdb as ircdb 627 name = something('What should the owner\'s username be?') 628 try: 629 id = ircdb.users.getUserId(name) 630 u = ircdb.users.getUser(id) 631 if u._checkCapability('owner'): 632 output("""That user already exists, and has owner capabilities 633 already. Perhaps you added it before? """) 634 if yn('Do you want to remove its owner capability?', 635 default=False): 636 u.removeCapability('owner') 637 ircdb.users.setUser(id, u) 638 else: 639 output("""That user already exists, but doesn't have owner 640 capabilities.""") 641 if yn('Do you want to add to it owner capabilities?', 642 default=False): 643 u.addCapability('owner') 644 ircdb.users.setUser(id, u) 645 except KeyError: 646 password = getpass('What should the owner\'s password be?') 647 u = ircdb.users.newUser() 648 u.name = name 649 u.setPassword(password) 650 u.addCapability('owner') 651 ircdb.users.setUser(u) 652 653 output("""Of course, when you're in an IRC channel you can address the bot 654 by its nick and it will respond, if you give it a valid command (it may or 655 may not respond, depending on what your config variable replyWhenNotCommand 656 is set to). But your bot can also respond to a short "prefix character," 657 so instead of saying "bot: do this," you can say, "@do this" and achieve 658 the same effect. Of course, you don't *have* to have a prefix char, but 659 if the bot ends up participating significantly in your channel, it'll ease 660 things.""") 661 if yn('Would you like to set the prefix char(s) for your bot? ', 662 default=True): 663 output("""Enter any characters you want here, but be careful: they 664 should be rare enough that people don't accidentally address the bot 665 (simply because they'll probably be annoyed if they do address the bot 666 on accident). You can even have more than one. I (jemfinch) am quite 667 partial to @, but that's because I've been using it since my ocamlbot 668 days.""") 669 import supybot.callbacks as callbacks 670 c = '' 671 while not c: 672 try: 673 c = anything('What would you like your bot\'s prefix ' 674 'character(s) to be?') 675 conf.supybot.reply.whenAddressedBy.chars.set(c) 676 except registry.InvalidRegistryValue as e: 677 output(str(e)) 678 c = '' 679 else: 680 conf.supybot.reply.whenAddressedBy.chars.set('') 681 682 ### 683 # logging variables. 684 ### 685 686 if advanced: 687 # conf.supybot.log.stdout 688 output("""By default, your bot will log not only to files in the logs 689 directory you gave it, but also to stdout. We find this useful for 690 debugging, and also just for the pretty output (it's colored!)""") 691 stdout = not yn('Would you like to turn off this logging to stdout?', 692 default=False) 693 conf.supybot.log.stdout.setValue(stdout) 694 if conf.supybot.log.stdout(): 695 # conf.something 696 output("""Some terminals may not be able to display the pretty 697 colors logged to stderr. By default, though, we turn the colors 698 off for Windows machines and leave it on for *nix machines.""") 699 if os.name is not 'nt': 700 conf.supybot.log.stdout.colorized.setValue( 701 not yn('Would you like to turn this colorization off?', 702 default=False)) 703 704 # conf.supybot.log.level 705 output("""Your bot can handle debug messages at several priorities, 706 CRITICAL, ERROR, WARNING, INFO, and DEBUG, in decreasing order of 707 priority. By default, your bot will log all of these priorities except 708 DEBUG. You can, however, specify that it only log messages above a 709 certain priority level.""") 710 priority = str(conf.supybot.log.level) 711 logLevel = something('What would you like the minimum priority to be?' 712 ' Just press enter to accept the default.', 713 default=priority).lower() 714 while logLevel not in ['debug','info','warning','error','critical']: 715 output("""That's not a valid priority. Valid priorities include 716 'DEBUG', 'INFO', 'WARNING', 'ERROR', and 'CRITICAL'""") 717 logLevel = something('What would you like the minimum priority to ' 718 'be? Just press enter to accept the default.', 719 default=priority).lower() 720 conf.supybot.log.level.set(logLevel) 721 722 # conf.supybot.databases.plugins.channelSpecific 723 724 output("""Many plugins in Supybot are channel-specific. Their 725 databases, likewise, are specific to each channel the bot is in. Many 726 people don't want this, so we have one central location in which to 727 say that you would prefer all databases for all channels to be shared. 728 This variable, supybot.databases.plugins.channelSpecific, is that 729 place.""") 730 731 conf.supybot.databases.plugins.channelSpecific.setValue( 732 not yn('Would you like plugin databases to be shared by all ' 733 'channels, rather than specific to each channel the ' 734 'bot is in?')) 735 736 output("""There are a lot of options we didn't ask you about simply 737 because we'd rather you get up and running and have time 738 left to play around with your bot. But come back and see 739 us! When you've played around with your bot enough to 740 know what you like, what you don't like, what you'd like 741 to change, then take a look at your configuration file 742 when your bot isn't running and read the comments, 743 tweaking values to your heart's desire.""") 744 745 # Let's make sure that src/ plugins are loaded. 746 conf.registerPlugin('Admin', True) 747 conf.registerPlugin('AutoMode', True) 748 conf.registerPlugin('Channel', True) 749 conf.registerPlugin('Config', True) 750 conf.registerPlugin('Misc', True) 751 conf.registerPlugin('Network', True) 752 conf.registerPlugin('NickAuth', True) 753 conf.registerPlugin('User', True) 754 conf.registerPlugin('Utilities', True) 755 756 ### 757 # Write the registry 758 ### 759 760 # We're going to need to do a darcs predist thing here. 761 #conf.supybot.debug.generated.setValue('...') 762 763 if advanced: 764 basedir = '.' 765 filename = os.path.join(basedir, '%s.conf') 766 767 filename = something("""In which file would you like to save 768 this config?""", default=filename % nick) 769 if not filename.endswith('.conf'): 770 filename += '.conf' 771 registry.close(conf.supybot, os.path.expanduser(filename)) 772 773 # Done! 774 output("""All done! Your new bot configuration is %s. If you're running 775 a *nix based OS, you can probably start your bot with the command line 776 "supybot %s". If you're not running a *nix or similar machine, you'll 777 just have to start it like you start all your other Python scripts.""" % \ 778 (filename, filename)) 779 780if __name__ == '__main__': 781 try: 782 main() 783 except KeyboardInterrupt: 784 # We may still be using bold text when exiting during a prompt 785 if questions.useBold: 786 import supybot.ansi as ansi 787 print(ansi.RESET) 788 print() 789 print() 790 output("""Well, it looks like you canceled out of the wizard before 791 it was done. Unfortunately, I didn't get to write anything to file. 792 Please run the wizard again to completion.""") 793 794# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: 795