3
0
mirror of https://github.com/jlu5/PyLink.git synced 2024-12-25 04:02:45 +01:00

core: migrate the main client to ServiceBot (#216)

- irc.spawnMain is dropped. Clients are now spawned in the endburst loop, after protocol negotiation completes. This allows PyLink to spawn clients with hideoper, etc., closing #194.
- utils.add_cmd and irc.callCommand are now just wrappers around world.services['pylink'].add_cmd and call_cmd respectively.
- coreplugin registers the main client while it is loaded up, before any commands are added.
This commit is contained in:
James Lu 2016-05-14 13:26:13 -07:00
parent 118d76fd5a
commit 32bc5f120b
4 changed files with 21 additions and 104 deletions

View File

@ -256,7 +256,6 @@ class Irc():
# All our checks passed, get the protocol module to connect
# and run the listen loop.
self.proto.connect()
self.spawnMain()
log.info('(%s) Starting ping schedulers....', self.name)
self.schedulePing()
log.info('(%s) Server ready; listening for data.', self.name)
@ -433,23 +432,6 @@ class Irc():
log.debug('(%s) Ping scheduled at %s', self.name, time.time())
def spawnMain(self):
"""Spawns the main PyLink client."""
nick = self.botdata.get('nick') or 'PyLink'
ident = self.botdata.get('ident') or 'pylink'
host = self.serverdata["hostname"]
log.info('(%s) Connected! Spawning main client %s.', self.name, nick)
olduserobj = self.pseudoclient
self.pseudoclient = self.proto.spawnClient(nick, ident, host,
modes={("+o", None)},
manipulatable=True,
opertype="PyLink Service")
for chan in self.serverdata['channels']:
self.proto.join(self.pseudoclient.uid, chan)
# PyLink internal hook called when spawnMain is called and the
# contents of Irc().pseudoclient change.
self.callHooks([self.sid, 'PYLINK_SPAWNMAIN', {'olduser': olduserobj}])
def __repr__(self):
return "<classes.Irc object for %r>" % self.name
@ -459,22 +441,7 @@ class Irc():
Calls a PyLink bot command. source is the caller's UID, and text is the
full, unparsed text of the message.
"""
cmd_args = text.strip().split(' ')
cmd = cmd_args[0].lower()
cmd_args = cmd_args[1:]
if cmd not in world.commands:
self.msg(self.called_by or source, 'Error: Unknown command %r.' % cmd)
log.info('(%s) Received unknown command %r from %s', self.name, cmd, self.getHostmask(source))
return
log.info('(%s) Calling command %r for %s', self.name, cmd, self.getHostmask(source))
for func in world.commands[cmd]:
try:
func(self, source, cmd_args)
except utils.NotAuthenticatedError:
self.msg(self.called_by or source, 'Error: You are not authorized to perform this operation.')
except Exception as e:
log.exception('Unhandled exception caught in command %r', cmd)
self.msg(self.called_by or source, 'Uncaught exception in command %r: %s: %s' % (cmd, type(e).__name__, str(e)))
world.services['pylink'].call_cmd(self, source, text)
def msg(self, target, text, notice=False, source=None):
"""Handy function to send messages/notices to clients. Source
@ -1129,7 +1096,6 @@ class FakeIRC(Irc):
self.hookmsgs = []
self.socket = None
self.initVars()
self.spawnMain()
self.connected = threading.Event()
self.connected.set()

View File

@ -40,11 +40,6 @@ signal.signal(signal.SIGTERM, sigterm_handler)
def handle_kill(irc, source, command, args):
"""Handle KILLs to PyLink service bots, respawning them as needed."""
target = args['target']
if target == irc.pseudoclient.uid:
irc.spawnMain()
return
for name, sbot in world.services.items():
if target == sbot.uids.get(irc.name):
spawn_service(irc, source, command, {'name': name})
@ -55,8 +50,7 @@ def handle_kick(irc, source, command, args):
"""Handle KICKs to the PyLink service bots, rejoining channels as needed."""
kicked = args['target']
channel = args['channel']
if kicked == irc.pseudoclient.uid or kicked in \
[sbot.uids.get(irc.name) for sbot in world.services.values()]:
if kicked in [sbot.uids.get(irc.name) for sbot in world.services.values()]:
irc.proto.join(kicked, channel)
utils.add_hook(handle_kick, 'KICK')
@ -65,14 +59,10 @@ def handle_commands(irc, source, command, args):
target = args['target']
text = args['text']
if target == irc.pseudoclient.uid and not irc.isInternalClient(source):
irc.called_by = source
irc.callCommand(source, text)
else:
for sbot in world.services.values():
if target == sbot.uids.get(irc.name):
sbot.call_cmd(irc, source, text)
return
for sbot in world.services.values():
if target == sbot.uids.get(irc.name):
sbot.call_cmd(irc, source, text)
return
utils.add_hook(handle_commands, 'PRIVMSG')
@ -223,9 +213,16 @@ def spawn_service(irc, source, command, args):
# Track the service's UIDs on each network.
sbot = world.services[name]
sbot.uids[irc.name] = u = irc.proto.spawnClient(sbot.nick, sbot.ident,
userobj = irc.proto.spawnClient(sbot.nick, sbot.ident,
host, modes=modes, opertype="PyLink Service",
manipulatable=sbot.manipulatable).uid
manipulatable=sbot.manipulatable)
sbot.uids[irc.name] = u = userobj.uid
# Special case: if this is the main PyLink client being spawned,
# assign this as irc.pseudoclient.
if name == 'pylink':
irc.pseudoclient = userobj
# TODO: channels should be tracked in a central database, not hardcoded
# in conf.
@ -256,6 +253,11 @@ def handle_endburst(irc, source, command, args):
utils.add_hook(handle_endburst, 'ENDBURST')
# Register the main PyLink service. All command definitions MUST go after this!
mynick = conf.conf['bot'].get("nick", "PyLink")
myident = conf.conf['bot'].get("ident", "pylink")
utils.registerService('pylink', nick=mynick, ident=myident)
# Essential, core commands go here so that the "commands" plugin with less-important,
# but still generic functions can be reloaded.
@ -465,4 +467,3 @@ def rehash(irc, source, args):
return
else:
irc.reply("Done.")

View File

@ -20,53 +20,6 @@ def status(irc, source, args):
irc.reply('You are not identified as anyone.')
irc.reply('Operator access: \x02%s\x02' % bool(irc.isOper(source)))
def listcommands(irc, source, args):
"""takes no arguments.
Returns a list of available commands PyLink has to offer."""
cmds = list(world.commands.keys())
cmds.sort()
for idx, cmd in enumerate(cmds):
nfuncs = len(world.commands[cmd])
if nfuncs > 1:
cmds[idx] = '%s(x%s)' % (cmd, nfuncs)
irc.reply('Available commands include: %s' % ', '.join(cmds))
irc.reply('To see help on a specific command, type \x02help <command>\x02.')
utils.add_cmd(listcommands, 'list')
@utils.add_cmd
def help(irc, source, args):
"""<command>
Gives help for <command>, if it is available."""
try:
command = args[0].lower()
except IndexError: # No argument given, just return 'list' output
listcommands(irc, source, args)
return
if command not in world.commands:
irc.msg(source, 'Error: Unknown command %r.' % command)
return
else:
funcs = world.commands[command]
if len(funcs) > 1:
irc.reply('The following \x02%s\x02 plugins bind to the \x02%s\x02 command: %s'
% (len(funcs), command, ', '.join([func.__module__ for func in funcs])))
for func in funcs:
doc = func.__doc__
mod = func.__module__
if doc:
lines = doc.split('\n')
# Bold the first line, which usually just tells you what
# arguments the command takes.
lines[0] = '\x02%s %s\x02 (plugin: %r)' % (command, lines[0], mod)
for line in lines:
irc.reply(line.strip())
else:
irc.msg(source, "Error: Command %r (from plugin %r) "
"doesn't offer any help." % (command, mod))
return
_none = '\x1D(none)\x1D'
@utils.add_cmd
def showuser(irc, source, args):

View File

@ -65,10 +65,7 @@ class IncrementalUIDGenerator():
def add_cmd(func, name=None):
"""Binds an IRC command function to the given command name."""
if name is None:
name = func.__name__
name = name.lower()
world.commands[name].append(func)
world.services['pylink'].add_cmd(func, name=name)
return func
def add_hook(func, command):