mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-12-24 19:52:54 +01:00
418 lines
17 KiB
Python
Executable File
418 lines
17 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
###
|
|
# Copyright (c) 2003, Jeremiah Fincher
|
|
# All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions are met:
|
|
#
|
|
# * Redistributions of source code must retain the above copyright notice,
|
|
# this list of conditions, and the following disclaimer.
|
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
|
# this list of conditions, and the following disclaimer in the
|
|
# documentation and/or other materials provided with the distribution.
|
|
# * Neither the name of the author of this software nor the name of
|
|
# contributors to this software may be used to endorse or promote products
|
|
# derived from this software without specific prior written consent.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
###
|
|
|
|
"""
|
|
This is the main program to run Supybot.
|
|
"""
|
|
|
|
__revision__ = "$Id$"
|
|
|
|
import re
|
|
import os
|
|
import sys
|
|
import atexit
|
|
import shutil
|
|
import signal
|
|
import cStringIO as StringIO
|
|
|
|
if sys.version_info < (2, 3, 0):
|
|
sys.stderr.write('This program requires Python >= 2.3.0\n')
|
|
sys.exit(-1)
|
|
|
|
def signalHandler(signalNumber, stackFrame):
|
|
raise SystemExit, 'Signal #%s' % signalNumber
|
|
|
|
signal.signal(signal.SIGTERM, signalHandler)
|
|
|
|
import time
|
|
import optparse
|
|
|
|
started = time.time()
|
|
|
|
import supybot
|
|
import registry
|
|
|
|
def main():
|
|
import conf
|
|
import utils
|
|
import world
|
|
import drivers
|
|
import schedule
|
|
# We schedule this event rather than have it actually run because if there
|
|
# is a failure between now and the time it takes the Owner plugin to load
|
|
# all the various plugins, our registry file might be wiped. That's bad.
|
|
when = time.time() + conf.supybot.upkeepInterval()
|
|
schedule.addEvent(world.upkeep, when, name='upkeep')
|
|
world.startedAt = started
|
|
while world.ircs:
|
|
try:
|
|
drivers.run()
|
|
except KeyboardInterrupt:
|
|
log.info('Exiting due to Ctrl-C.')
|
|
break
|
|
except SystemExit, e:
|
|
s = str(e)
|
|
if s:
|
|
log.info('Exiting due to %s', s)
|
|
break
|
|
except:
|
|
try: # Ok, now we're *REALLY* paranoid!
|
|
log.exception('Exception raised out of drivers.run:')
|
|
except Exception, e:
|
|
print 'Exception raised in log.exception. This is *really*'
|
|
print 'bad. Hopefully it won\'t happen again, but tell us'
|
|
print 'about it anyway, this is a significant problem.'
|
|
print 'Anyway, here\'s the exception: %s'% utils.exnToString(e)
|
|
except:
|
|
print 'Man, this really sucks. Not only did log.exception'
|
|
print 'raise an exception, but freaking-a, it was a string'
|
|
print 'exception. People who raise string exceptions should'
|
|
print 'die a slow, painful death.'
|
|
now = time.time()
|
|
seconds = now - world.startedAt
|
|
log.info('Total uptime: %s.', utils.timeElapsed(seconds))
|
|
(user, system, _, _, _) = os.times()
|
|
log.info('Total CPU time taken: %s seconds.', user+system)
|
|
log.info('No more Irc objects, exiting.')
|
|
|
|
if __name__ == '__main__':
|
|
###
|
|
# Options:
|
|
# -p (profiling)
|
|
# -O (optimizing)
|
|
# -n, --nick (nick)
|
|
# -s, --server (server)
|
|
# --startup (commands to run onStart)
|
|
# --connect (commands to run afterConnect)
|
|
# --config (configuration values)
|
|
parser = optparse.OptionParser(usage='Usage: %prog [options] configFile',
|
|
version='supybot 0.77.2+cvs')
|
|
parser.add_option('-P', '--profile', action='store_true', dest='profile',
|
|
help='enables profiling')
|
|
parser.add_option('-O', action='count', dest='optimize',
|
|
help='-O optimizes asserts out of the code; ' \
|
|
'-OO optimizes asserts and uses psyco.')
|
|
parser.add_option('-n', '--nick', action='store',
|
|
dest='nick', default='',
|
|
help='nick the bot should use')
|
|
parser.add_option('-s', '--server', action='store',
|
|
dest='server', default='',
|
|
help='server to connect to')
|
|
parser.add_option('-u', '--user', action='store',
|
|
dest='user', default='',
|
|
help='full username the bot should use')
|
|
parser.add_option('-i', '--ident', action='store',
|
|
dest='ident', default='',
|
|
help='ident the bot should use')
|
|
parser.add_option('-p', '--password', action='store',
|
|
dest='password', default='',
|
|
help='server password the bot should use')
|
|
parser.add_option('-d', '--daemon', action='store_true',
|
|
dest='daemon',
|
|
help='Determines whether the bot will daemonize. '
|
|
'This is a no-op on non-POSIX systems.')
|
|
parser.add_option('', '--allow-eval', action='store_true',
|
|
dest='allowEval',
|
|
help='Determines whether the bot will '
|
|
'allow the evaluation of arbitrary Python code.')
|
|
parser.add_option('', '--strict-rfc', action='store_true',
|
|
dest='strictRfc',
|
|
help='Determines whether the bot will strictly follow '
|
|
'RFC guidelines defining nicks and channels.')
|
|
parser.add_option('', '--allow-root', action='store_true',
|
|
dest='allowRoot',
|
|
help='Determines whether the bot will be allowed to run'
|
|
'as root. You don\'t want this. Don\'t do it. '
|
|
'Even if you think you want it, you don\'t. '
|
|
'You\'re probably dumb if you do this.')
|
|
parser.add_option('', '--debug', action='store_true', dest='debug',
|
|
help='Determines whether some extra debugging stuff will'
|
|
'be logged in this script.')
|
|
|
|
(options, args) = parser.parse_args()
|
|
|
|
if os.name == 'posix':
|
|
if os.getuid() == 0 or os.geteuid() == 0 and not options.allowRoot:
|
|
sys.stderr.write('Dude, don\'t even try to run this as root.\n')
|
|
sys.exit(-1)
|
|
|
|
if len(args) > 1:
|
|
parser.error()
|
|
elif not args:
|
|
try:
|
|
import socket
|
|
import ircutils
|
|
import questions
|
|
questions.output("""It seems like you're running supybot for the
|
|
first time. Or, perhaps, you just forgot to give this program an
|
|
argument for your registry file. If the latter is the case,
|
|
simply press Ctrl-C and this script will exit and you can run it
|
|
again as indicated. If the former is the case, however, we'll
|
|
have a few questions for you to write your initial registry
|
|
file.""")
|
|
###
|
|
# Nick.
|
|
###
|
|
nick = questions.something("""What nick would you like your bot to
|
|
use?""")
|
|
while not ircutils.isNick(nick):
|
|
questions.output("""That's not a valid IRC nick. Please
|
|
choose a different nick.""")
|
|
nick = questions.something("""What nick would you like your
|
|
bot to use?""")
|
|
|
|
###
|
|
# Server.
|
|
###
|
|
def checkServer(server):
|
|
try:
|
|
ip = socket.gethostbyname(server)
|
|
questions.output("""%s resolved to %s.""" % (server, ip))
|
|
return True
|
|
except socket.error:
|
|
questions.output("""That's not a valid hostname. Please
|
|
enter a hostname that resolves.""")
|
|
return False
|
|
server = questions.something("""What server would you like your
|
|
bot to connect to?""")
|
|
while not checkServer(server):
|
|
server = questions.something("""What server would you like
|
|
your bot to connect to?""")
|
|
|
|
###
|
|
# Channels.
|
|
###
|
|
def checkChannels(s):
|
|
for channel in s.split():
|
|
if ',' in channel:
|
|
(channel, _) = channel.split(',', 1)
|
|
if not ircutils.isChannel(channel):
|
|
questions.output("""%s is not a valid IRC channel.
|
|
Please choose a different channel.""" % channel)
|
|
return False
|
|
return True
|
|
channels = questions.something("""What channels would you like
|
|
your bot to join when it connects to %s? Separate your channels
|
|
by spaces; if any channels require a keyword to join, separate the
|
|
keyword from the channel by a comma. For instance, if you want to
|
|
join #supybot with no keyword and #secret with a keyword of 'foo',
|
|
you would type '#supybot #secret,foo' without the quotes.""" %
|
|
server)
|
|
while not checkChannels(channels):
|
|
channels = questions.something("""What channels would you like
|
|
your bot to join when it connects to %s? Separate your
|
|
channels by spaces; if any channels require a keyword to join,
|
|
separate the keyword from the channel by a comma. For
|
|
instance, if you want to join #supybot with no keyword and
|
|
#secret with a keyword of 'foo', you would type '#supybot
|
|
#secret,foo' without the quotes. """ % server)
|
|
|
|
###
|
|
# Filename.
|
|
###
|
|
def checkFilename(s):
|
|
if os.path.exists(s):
|
|
questions.output("""That file already exists. Please
|
|
choose a file that doesn't exist yet. You can always copy
|
|
it over later, of course, but we'd rather play it safe
|
|
ourselves and not risk overwriting an important file.""")
|
|
return False
|
|
try:
|
|
fd = file(s, 'w')
|
|
fd.write('supybot.nick: %s\n' % nick)
|
|
fd.write('supybot.server: %s\n' % server)
|
|
fd.write('supybot.channels: %s\n' % channels)
|
|
fd.close()
|
|
questions.output("""File %s written. Now, to run your
|
|
bot, run this script with just that filename as an option.
|
|
Once you do so, your configuration file will become much
|
|
fuller and more complete, with help descriptions
|
|
describing all the options and a significant number more
|
|
options than you see now. Have fun! """ % s)
|
|
return True
|
|
except EnvironmentError, e:
|
|
questions.output("""Python told me that it couldn't create
|
|
your file, giving me this specific error: %s.""" % e)
|
|
return False
|
|
filename = questions.something("""What filename would you like to
|
|
write this configuration to?""")
|
|
while not checkFilename(filename):
|
|
filename = questions.something("""What filename would you like
|
|
to write this configuration to?""")
|
|
questions.output("""Great! Seeya on the flipside!""")
|
|
sys.exit(0)
|
|
except KeyboardInterrupt:
|
|
print
|
|
print
|
|
questions.output("""Well, it looks like you cancelled out of the
|
|
bot before it was done. Unfortunately, I didn't get to write
|
|
anything to file. Please run the bot/wizard again to
|
|
completion.""")
|
|
sys.exit(0)
|
|
else:
|
|
registryFilename = args.pop()
|
|
try:
|
|
# The registry *MUST* be opened before importing log or conf.
|
|
registry.open(registryFilename)
|
|
shutil.copy(registryFilename, registryFilename + '.bak')
|
|
except registry.InvalidRegistryFile, e:
|
|
sys.stderr.write(str(e))
|
|
sys.stderr.write(os.linesep)
|
|
sys.exit(-1)
|
|
except EnvironmentError, e:
|
|
sys.stderr.write(str(e))
|
|
sys.stderr.write(os.linesep)
|
|
sys.exit(-1)
|
|
|
|
import log
|
|
import conf
|
|
import world
|
|
world.starting = True
|
|
|
|
def closeRegistry():
|
|
# We only print if world.dying so we don't see these messages during
|
|
# upkeep.
|
|
if world.dying:
|
|
log.info('Writing registry file to %s', registryFilename)
|
|
registry.close(conf.supybot, registryFilename, annotated=True)
|
|
if world.dying:
|
|
log.info('Finished writing registry file.')
|
|
world.flushers.append(closeRegistry)
|
|
world.registryFilename = registryFilename
|
|
|
|
nick = options.nick or conf.supybot.nick()
|
|
user = options.user or conf.supybot.user()
|
|
ident = options.ident or conf.supybot.ident()
|
|
password = options.password or conf.supybot.password()
|
|
|
|
server = options.server or conf.supybot.server()
|
|
if ':' in server:
|
|
serverAndPort = server.split(':', 1)
|
|
serverAndPort[1] = int(serverAndPort[1])
|
|
server = tuple(serverAndPort)
|
|
else:
|
|
server = (server, 6667)
|
|
|
|
if options.optimize:
|
|
__builtins__.__debug__ = False
|
|
if options.optimize > 1:
|
|
try:
|
|
import psyco
|
|
psyco.full()
|
|
except ImportError:
|
|
log.warning('Psyco isn\'t installed, cannot -OO.')
|
|
|
|
conf.allowEval = options.allowEval
|
|
|
|
if not os.path.exists(conf.supybot.directories.log()):
|
|
os.mkdir(conf.supybot.directories.log())
|
|
if not os.path.exists(conf.supybot.directories.conf()):
|
|
os.mkdir(conf.supybot.directories.conf())
|
|
if not os.path.exists(conf.supybot.directories.data()):
|
|
os.mkdir(conf.supybot.directories.data())
|
|
|
|
import irclib
|
|
import ircmsgs
|
|
import drivers
|
|
import callbacks
|
|
import Owner
|
|
|
|
conf.strictRfc = bool(options.strictRfc)
|
|
|
|
irc = irclib.Irc(nick, user=user, ident=ident, password=password)
|
|
callback = Owner.Class()
|
|
irc.addCallback(callback)
|
|
driver = drivers.newDriver(server, irc)
|
|
|
|
if options.debug:
|
|
for (name, module) in sys.modules.iteritems():
|
|
if hasattr(module, '__file__') and hasattr(module, '__revision__'):
|
|
if module.__file__.startswith(supybot.installDir):
|
|
print '%s: %s' % (name, module.__revision__.split()[2])
|
|
|
|
if os.name == 'posix' and options.daemon:
|
|
def fork():
|
|
child = os.fork()
|
|
if child != 0:
|
|
if options.debug:
|
|
print 'Parent exiting, child PID: %s' % child
|
|
# We must us os._exit instead of sys.exit so atexit handlers
|
|
# don't run. They shouldn't be dangerous, but they're ugly.
|
|
os._exit(0)
|
|
fork()
|
|
os.setsid()
|
|
# What the heck does this do? I wonder if it breaks anything...
|
|
os.umask(0)
|
|
# Let's not do this for now (at least until I can make sure it works):
|
|
# Actually, let's never do this -- we'll always have files open in the
|
|
# bot directories, so they won't be able to be unmounted anyway.
|
|
# os.chdir('/')
|
|
fork()
|
|
# Since this is the indicator that no writing should be done to stdout,
|
|
# we'll set it to True before closing stdout et alii.
|
|
conf.daemonized = True
|
|
# Closing stdin shouldn't cause problems. We'll let it raise an
|
|
# exception if it does.
|
|
sys.stdin.close()
|
|
# Closing these two might cause problems; we log writes to them as
|
|
# level WARNING on upkeep.
|
|
sys.stdout.close()
|
|
sys.stderr.close()
|
|
sys.stdout = StringIO.StringIO()
|
|
sys.stderr = StringIO.StringIO()
|
|
signal.signal(signal.SIGHUP, signal.SIG_IGN)
|
|
log.info('Completed daemonization. Current PID: %s', os.getpid())
|
|
|
|
# Let's write the PID file. This has to go after daemonization, obviously.
|
|
pidFile = conf.supybot.pidFile()
|
|
if pidFile:
|
|
try:
|
|
fd = file(pidFile, 'w')
|
|
pid = os.getpid()
|
|
fd.write('%s\n' % pid)
|
|
fd.close()
|
|
def removePidFile():
|
|
os.remove(pidFile)
|
|
atexit.register(removePidFile)
|
|
except EnvironmentError, e:
|
|
log.error('Error opening pid file %s: %s', pidFile, e)
|
|
|
|
if options.profile:
|
|
import hotshot
|
|
profiler = hotshot.Profile('%s-%i.prof' % (nick, time.time()))
|
|
profiler.run('main()')
|
|
else:
|
|
main()
|
|
|
|
|
|
|
|
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|