3
0
mirror of https://github.com/jlu5/PyLink.git synced 2024-11-24 11:39:25 +01:00

Merge branch 'master' into wip/unrealircd

This commit is contained in:
James Lu 2015-11-01 20:40:56 -08:00
commit cedcb9b11a
11 changed files with 293 additions and 250 deletions

View File

@ -178,11 +178,13 @@ class Irc():
return
def callCommand(self, source, text):
"""Calls a PyLink bot command."""
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, utils.getHostmask(self, source))
return
log.info('(%s) Calling command %r for %s', self.name, cmd, utils.getHostmask(self, source))
for func in world.commands[cmd]:
@ -206,6 +208,10 @@ class Irc():
cmd = 'PYLINK_SELF_PRIVMSG'
self.callHooks([source, cmd, {'target': target, 'text': text}])
def reply(self, text, notice=False, source=None):
"""Replies to the last caller in context."""
self.msg(self.called_by, text, notice=notice, source=source)
def _disconnect(self):
log.debug('(%s) Canceling pingTimer at %s due to _disconnect() call', self.name, time.time())
self.connected.clear()

View File

@ -1,4 +1,7 @@
## coreplugin.py - Core PyLink plugin
# coreplugin.py - Implements core PyLink functions as a plugin
import gc
import sys
import utils
from log import log
@ -107,3 +110,158 @@ def handle_mode(irc, source, command, args):
if ('-o', None) in modes and (target == irc.pseudoclient.uid or not utils.isManipulatableClient(irc, target)):
irc.proto.modeServer(irc.sid, target, {('+o', None)})
utils.add_hook(handle_mode, 'MODE')
def handle_operup(irc, source, command, args):
"""Logs successful oper-ups on networks."""
log.info("(%s) Successful oper-up (opertype %r) from %s", irc.name, args.get('text'), utils.getHostmask(irc, source))
utils.add_hook(handle_operup, 'PYLINK_CLIENT_OPERED')
# Essential, core commands go here so that the "commands" plugin with less-important,
# but still generic functions can be reloaded.
@utils.add_cmd
def identify(irc, source, args):
"""<username> <password>
Logs in to PyLink using the configured administrator account."""
if utils.isChannel(irc.called_by):
irc.reply('Error: This command must be sent in private. '
'(Would you really type a password inside a channel?)')
return
try:
username, password = args[0], args[1]
except IndexError:
irc.msg(source, 'Error: Not enough arguments.')
return
# Usernames are case-insensitive, passwords are NOT.
if username.lower() == irc.conf['login']['user'].lower() and password == irc.conf['login']['password']:
realuser = irc.conf['login']['user']
irc.users[source].identified = realuser
irc.msg(source, 'Successfully logged in as %s.' % realuser)
log.info("(%s) Successful login to %r by %s",
irc.name, username, utils.getHostmask(irc, source))
else:
irc.msg(source, 'Error: Incorrect credentials.')
u = irc.users[source]
log.warning("(%s) Failed login to %r from %s",
irc.name, username, utils.getHostmask(irc, source))
@utils.add_cmd
def shutdown(irc, source, args):
"""takes no arguments.
Exits PyLink by disconnecting all networks."""
utils.checkAuthenticated(irc, source, allowOper=False)
u = irc.users[source]
log.info('(%s) SHUTDOWN requested by "%s!%s@%s", exiting...', irc.name, u.nick,
u.ident, u.host)
for ircobj in world.networkobjects.values():
# Disable auto-connect first by setting the time to negative.
ircobj.serverdata['autoconnect'] = -1
ircobj.aborted.set()
def load(irc, source, args):
"""<plugin name>.
Loads a plugin from the plugin folder."""
utils.checkAuthenticated(irc, source, allowOper=False)
try:
name = args[0]
except IndexError:
irc.reply("Error: Not enough arguments. Needs 1: plugin name.")
return
if name in world.plugins:
irc.reply("Error: %r is already loaded." % name)
return
log.info('(%s) Loading plugin %r for %s', irc.name, name, utils.getHostmask(irc, source))
try:
world.plugins[name] = pl = utils.loadModuleFromFolder(name, world.plugins_folder)
except ImportError as e:
if str(e) == ('No module named %r' % name):
log.exception('Failed to load plugin %r: The plugin could not be found.', name)
else:
log.exception('Failed to load plugin %r: ImportError.', name)
raise
else:
if hasattr(pl, 'main'):
log.debug('Calling main() function of plugin %r', pl)
pl.main(irc)
irc.reply("Loaded plugin %r." % name)
utils.add_cmd(load)
def unload(irc, source, args):
"""<plugin name>.
Unloads a currently loaded plugin."""
utils.checkAuthenticated(irc, source, allowOper=False)
try:
name = args[0]
except IndexError:
irc.reply("Error: Not enough arguments. Needs 1: plugin name.")
return
if name in world.plugins:
log.info('(%s) Unloading plugin %r for %s', irc.name, name, utils.getHostmask(irc, source))
pl = world.plugins[name]
log.debug('sys.getrefcount of plugin %s is %s', pl, sys.getrefcount(pl))
# Remove any command functions set by the plugin.
for cmdname, cmdfuncs in world.commands.copy().items():
log.debug('cmdname=%s, cmdfuncs=%s', cmdname, cmdfuncs)
for cmdfunc in cmdfuncs:
log.debug('__module__ of cmdfunc %s is %s', cmdfunc, cmdfunc.__module__)
if cmdfunc.__module__ == name:
log.debug('Removing %s from world.commands[%s]', cmdfunc, cmdname)
world.commands[cmdname].remove(cmdfunc)
# If the cmdfunc list is empty, remove it.
if not cmdfuncs:
log.debug("Removing world.commands[%s] (it's empty now)", cmdname)
del world.commands[cmdname]
# Remove any command hooks set by the plugin.
for hookname, hookfuncs in world.hooks.copy().items():
for hookfunc in hookfuncs:
if hookfunc.__module__ == name:
world.hooks[hookname].remove(hookfunc)
# If the hookfuncs list is empty, remove it.
if not hookfuncs:
del world.hooks[hookname]
# Remove whois handlers too.
for f in world.whois_handlers:
if f.__module__ == name:
world.whois_handlers.remove(f)
# Call the die() function in the plugin, if present.
if hasattr(pl, 'die'):
try:
pl.die(irc)
except: # But don't allow it to crash the server.
log.exception('(%s) Error occurred in die() of plugin %s, skipping...', irc.name, pl)
# Delete it from memory (hopefully).
del world.plugins[name]
if name in sys.modules:
del sys.modules[name]
if name in globals():
del globals()[name]
# Garbage collect.
gc.collect()
irc.reply("Unloaded plugin %r." % name)
return True # We succeeded, make it clear (this status is used by reload() below)
else:
irc.reply("Unknown plugin %r." % name)
utils.add_cmd(unload)
@utils.add_cmd
def reload(irc, source, args):
"""<plugin name>.
Loads a plugin from the plugin folder."""
try:
name = args[0]
except IndexError:
irc.reply("Error: Not enough arguments. Needs 1: plugin name.")
return
if unload(irc, source, args):
load(irc, source, args)

View File

@ -20,7 +20,7 @@ def spawnclient(irc, source, args):
try:
nick, ident, host = args[:3]
except ValueError:
irc.msg(irc.called_by, "Error: Not enough arguments. Needs 3: nick, user, host.")
irc.reply("Error: Not enough arguments. Needs 3: nick, user, host.")
return
irc.proto.spawnClient(nick, ident, host, manipulatable=True)
@ -33,15 +33,15 @@ def quit(irc, source, args):
try:
nick = args[0]
except IndexError:
irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1-2: nick, reason (optional).")
irc.reply("Error: Not enough arguments. Needs 1-2: nick, reason (optional).")
return
if irc.pseudoclient.uid == utils.nickToUid(irc, nick):
irc.msg(irc.called_by, "Error: Cannot quit the main PyLink PseudoClient!")
irc.reply("Error: Cannot quit the main PyLink PseudoClient!")
return
u = utils.nickToUid(irc, nick)
quitmsg = ' '.join(args[1:]) or 'Client Quit'
if not utils.isManipulatableClient(irc, u):
irc.msg(irc.called_by, "Error: Cannot force quit a protected PyLink services client.")
irc.reply("Error: Cannot force quit a protected PyLink services client.")
return
irc.proto.quitClient(u, quitmsg)
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_QUIT', {'text': quitmsg, 'parse_as': 'QUIT'}])
@ -57,15 +57,15 @@ def joinclient(irc, source, args):
if not clist:
raise IndexError
except IndexError:
irc.msg(irc.called_by, "Error: Not enough arguments. Needs 2: nick, comma separated list of channels.")
irc.reply("Error: Not enough arguments. Needs 2: nick, comma separated list of channels.")
return
u = utils.nickToUid(irc, nick)
if not utils.isManipulatableClient(irc, u):
irc.msg(irc.called_by, "Error: Cannot force join a protected PyLink services client.")
irc.reply("Error: Cannot force join a protected PyLink services client.")
return
for channel in clist:
if not utils.isChannel(channel):
irc.msg(irc.called_by, "Error: Invalid channel name %r." % channel)
irc.reply("Error: Invalid channel name %r." % channel)
return
irc.proto.joinClient(u, channel)
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_JOIN', {'channel': channel, 'users': [u],
@ -83,16 +83,16 @@ def nick(irc, source, args):
nick = args[0]
newnick = args[1]
except IndexError:
irc.msg(irc.called_by, "Error: Not enough arguments. Needs 2: nick, newnick.")
irc.reply("Error: Not enough arguments. Needs 2: nick, newnick.")
return
u = utils.nickToUid(irc, nick)
if newnick in ('0', u):
newnick = u
elif not utils.isNick(newnick):
irc.msg(irc.called_by, 'Error: Invalid nickname %r.' % newnick)
irc.reply('Error: Invalid nickname %r.' % newnick)
return
elif not utils.isManipulatableClient(irc, u):
irc.msg(irc.called_by, "Error: Cannot force nick changes for a protected PyLink services client.")
irc.reply("Error: Cannot force nick changes for a protected PyLink services client.")
return
irc.proto.nickClient(u, newnick)
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_NICK', {'newnick': newnick, 'oldnick': nick, 'parse_as': 'NICK'}])
@ -108,15 +108,15 @@ def part(irc, source, args):
clist = args[1].split(',')
reason = ' '.join(args[2:])
except IndexError:
irc.msg(irc.called_by, "Error: Not enough arguments. Needs 2: nick, comma separated list of channels.")
irc.reply("Error: Not enough arguments. Needs 2: nick, comma separated list of channels.")
return
u = utils.nickToUid(irc, nick)
if not utils.isManipulatableClient(irc, u):
irc.msg(irc.called_by, "Error: Cannot force part a protected PyLink services client.")
irc.reply("Error: Cannot force part a protected PyLink services client.")
return
for channel in clist:
if not utils.isChannel(channel):
irc.msg(irc.called_by, "Error: Invalid channel name %r." % channel)
irc.reply("Error: Invalid channel name %r." % channel)
return
irc.proto.partClient(u, channel, reason)
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_PART', {'channels': clist, 'text': reason, 'parse_as': 'PART'}])
@ -133,12 +133,12 @@ def kick(irc, source, args):
target = args[2]
reason = ' '.join(args[3:])
except IndexError:
irc.msg(irc.called_by, "Error: Not enough arguments. Needs 3-4: source nick, channel, target, reason (optional).")
irc.reply("Error: Not enough arguments. Needs 3-4: source nick, channel, target, reason (optional).")
return
u = utils.nickToUid(irc, nick) or nick
targetu = utils.nickToUid(irc, target)
if not utils.isChannel(channel):
irc.msg(irc.called_by, "Error: Invalid channel name %r." % channel)
irc.reply("Error: Invalid channel name %r." % channel)
return
if utils.isInternalServer(irc, u):
irc.proto.kickServer(u, channel, targetu, reason)
@ -155,20 +155,20 @@ def mode(irc, source, args):
try:
modesource, target, modes = args[0], args[1], args[2:]
except IndexError:
irc.msg(irc.called_by, 'Error: Not enough arguments. Needs 3: source nick, target, modes to set.')
irc.reply('Error: Not enough arguments. Needs 3: source nick, target, modes to set.')
return
target = utils.nickToUid(irc, target) or target
extclient = target in irc.users and not utils.isInternalClient(irc, target)
parsedmodes = utils.parseModes(irc, target, modes)
ischannel = target in irc.channels
if not (target in irc.users or ischannel):
irc.msg(irc.called_by, "Error: Invalid channel or nick %r." % target)
irc.reply("Error: Invalid channel or nick %r." % target)
return
elif not parsedmodes:
irc.msg(irc.called_by, "Error: No valid modes were given.")
irc.reply("Error: No valid modes were given.")
return
elif not (ischannel or utils.isManipulatableClient(irc, target)):
irc.msg(irc.called_by, "Error: Can only set modes on channels or non-protected PyLink clients.")
irc.reply("Error: Can only set modes on channels or non-protected PyLink clients.")
return
if utils.isInternalServer(irc, modesource):
# Setting modes from a server.
@ -189,21 +189,21 @@ def msg(irc, source, args):
try:
msgsource, target, text = args[0], args[1], ' '.join(args[2:])
except IndexError:
irc.msg(irc.called_by, 'Error: Not enough arguments. Needs 3: source nick, target, text.')
irc.reply('Error: Not enough arguments. Needs 3: source nick, target, text.')
return
sourceuid = utils.nickToUid(irc, msgsource)
if not sourceuid:
irc.msg(irc.called_by, 'Error: Unknown user %r.' % msgsource)
irc.reply('Error: Unknown user %r.' % msgsource)
return
if not utils.isChannel(target):
real_target = utils.nickToUid(irc, target)
if real_target is None:
irc.msg(irc.called_by, 'Error: Unknown user %r.' % target)
irc.reply('Error: Unknown user %r.' % target)
return
else:
real_target = target
if not text:
irc.msg(irc.called_by, 'Error: No text given.')
irc.reply('Error: No text given.')
return
irc.proto.messageClient(sourceuid, real_target, text)
irc.callHooks([sourceuid, 'PYLINK_BOTSPLUGIN_MSG', {'target': real_target, 'text': text, 'parse_as': 'PRIVMSG'}])

View File

@ -2,8 +2,6 @@
import sys
import os
from time import ctime
import itertools
import gc
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import utils
@ -19,36 +17,10 @@ def status(irc, source, args):
Returns your current PyLink login status."""
identified = irc.users[source].identified
if identified:
irc.msg(irc.called_by, 'You are identified as \x02%s\x02.' % identified)
irc.reply('You are identified as \x02%s\x02.' % identified)
else:
irc.msg(irc.called_by, 'You are not identified as anyone.')
irc.msg(irc.called_by, 'Operator access: \x02%s\x02' % bool(utils.isOper(irc, source)))
@utils.add_cmd
def identify(irc, source, args):
"""<username> <password>
Logs in to PyLink using the configured administrator account."""
if utils.isChannel(irc.called_by):
irc.msg(irc.called_by, 'Error: This command must be sent in private. '
'(Would you really type a password inside a public channel?)')
try:
username, password = args[0], args[1]
except IndexError:
irc.msg(source, 'Error: Not enough arguments.')
return
# Usernames are case-insensitive, passwords are NOT.
if username.lower() == irc.conf['login']['user'].lower() and password == irc.conf['login']['password']:
realuser = irc.conf['login']['user']
irc.users[source].identified = realuser
irc.msg(source, 'Successfully logged in as %s.' % realuser)
log.info("(%s) Successful login to %r by %s.",
irc.name, username, utils.getHostmask(irc, source))
else:
irc.msg(source, 'Error: Incorrect credentials.')
u = irc.users[source]
log.warning("(%s) Failed login to %r from %s.",
irc.name, username, utils.getHostmask(irc, source))
irc.reply('You are not identified as anyone.')
irc.reply('Operator access: \x02%s\x02' % bool(utils.isOper(irc, source)))
def listcommands(irc, source, args):
"""takes no arguments.
@ -60,8 +32,8 @@ def listcommands(irc, source, args):
nfuncs = len(world.commands[cmd])
if nfuncs > 1:
cmds[idx] = '%s(x%s)' % (cmd, nfuncs)
irc.msg(irc.called_by, 'Available commands include: %s' % ', '.join(cmds))
irc.msg(irc.called_by, 'To see help on a specific command, type \x02help <command>\x02.')
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
@ -80,7 +52,7 @@ def help(irc, source, args):
else:
funcs = world.commands[command]
if len(funcs) > 1:
irc.msg(irc.called_by, 'The following \x02%s\x02 plugins bind to the \x02%s\x02 command: %s'
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__
@ -91,7 +63,7 @@ def help(irc, source, args):
# arguments the command takes.
lines[0] = '\x02%s %s\x02 (plugin: %r)' % (command, lines[0], mod)
for line in lines:
irc.msg(irc.called_by, line.strip())
irc.reply(line.strip())
else:
irc.msg(source, "Error: Command %r (from plugin %r) "
"doesn't offer any help." % (command, mod))
@ -105,14 +77,14 @@ def showuser(irc, source, args):
try:
target = args[0]
except IndexError:
irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: nick.")
irc.reply("Error: Not enough arguments. Needs 1: nick.")
return
u = utils.nickToUid(irc, target) or target
# Only show private info if the person is calling 'showuser' on themselves,
# or is an oper.
verbose = utils.isOper(irc, source) or u == source
if u not in irc.users:
irc.msg(irc.called_by, 'Error: Unknown user %r.' % target)
irc.reply('Error: Unknown user %r.' % target)
return
f = lambda s: irc.msg(source, s)
@ -140,10 +112,10 @@ def showchan(irc, source, args):
try:
channel = utils.toLower(irc, args[0])
except IndexError:
irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: channel.")
irc.reply("Error: Not enough arguments. Needs 1: channel.")
return
if channel not in irc.channels:
irc.msg(irc.called_by, 'Error: Unknown channel %r.' % channel)
irc.reply('Error: Unknown channel %r.' % channel)
return
f = lambda s: irc.msg(source, s)
@ -178,141 +150,20 @@ def showchan(irc, source, args):
f('\x02User list\x02: %s' % ' '.join(nicklist[:20]))
nicklist = nicklist[20:]
@utils.add_cmd
def shutdown(irc, source, args):
"""takes no arguments.
Exits PyLink by disconnecting all networks."""
utils.checkAuthenticated(irc, source, allowOper=False)
u = irc.users[source]
log.info('(%s) SHUTDOWN requested by "%s!%s@%s", exiting...', irc.name, u.nick,
u.ident, u.host)
for ircobj in world.networkobjects.values():
# Disable auto-connect first by setting the time to negative.
ircobj.serverdata['autoconnect'] = -1
ircobj.aborted.set()
@utils.add_cmd
def version(irc, source, args):
"""takes no arguments.
Returns the version of the currently running PyLink instance."""
irc.msg(irc.called_by, "PyLink version \x02%s\x02, released under the Mozilla Public License version 2.0." % world.version)
irc.msg(irc.called_by, "The source of this program is available at \x02%s\x02." % world.source)
irc.reply("PyLink version \x02%s\x02, released under the Mozilla Public License version 2.0." % world.version)
irc.reply("The source of this program is available at \x02%s\x02." % world.source)
@utils.add_cmd
def echo(irc, source, args):
"""<text>
Echoes the text given."""
irc.msg(irc.called_by, ' '.join(args))
def load(irc, source, args):
"""<plugin name>.
Loads a plugin from the plugin folder."""
utils.checkAuthenticated(irc, source, allowOper=False)
try:
name = args[0]
except IndexError:
irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: plugin name.")
return
if name in world.plugins:
irc.msg(irc.called_by, "Error: %r is already loaded." % name)
return
try:
world.plugins[name] = pl = utils.loadModuleFromFolder(name, world.plugins_folder)
except ImportError as e:
if str(e) == ('No module named %r' % name):
log.exception('Failed to load plugin %r: The plugin could not be found.', name)
else:
log.exception('Failed to load plugin %r: ImportError.', name)
raise
else:
if hasattr(pl, 'main'):
log.debug('Calling main() function of plugin %r', pl)
pl.main(irc)
irc.msg(irc.called_by, "Loaded plugin %r." % name)
utils.add_cmd(load)
def unload(irc, source, args):
"""<plugin name>.
Unloads a currently loaded plugin."""
utils.checkAuthenticated(irc, source, allowOper=False)
try:
name = args[0]
except IndexError:
irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: plugin name.")
return
if name == 'commands':
irc.msg(irc.called_by, "Error: Cannot unload the commands plugin!")
return
elif name in world.plugins:
pl = world.plugins[name]
log.debug('sys.getrefcount of plugin %s is %s', pl, sys.getrefcount(pl))
# Remove any command functions set by the plugin.
for cmdname, cmdfuncs in world.commands.copy().items():
log.debug('cmdname=%s, cmdfuncs=%s', cmdname, cmdfuncs)
for cmdfunc in cmdfuncs:
log.debug('__module__ of cmdfunc %s is %s', cmdfunc, cmdfunc.__module__)
if cmdfunc.__module__ == name:
log.debug('Removing %s from world.commands[%s]', cmdfunc, cmdname)
world.commands[cmdname].remove(cmdfunc)
# If the cmdfunc list is empty, remove it.
if not cmdfuncs:
log.debug("Removing world.commands[%s] (it's empty now)", cmdname)
del world.commands[cmdname]
# Remove any command hooks set by the plugin.
for hookname, hookfuncs in world.hooks.copy().items():
for hookfunc in hookfuncs:
if hookfunc.__module__ == name:
world.hooks[hookname].remove(hookfunc)
# If the hookfuncs list is empty, remove it.
if not hookfuncs:
del world.hooks[hookname]
# Remove whois handlers too.
for f in world.whois_handlers:
if f.__module__ == name:
world.whois_handlers.remove(f)
# Call the die() function in the plugin, if present.
if hasattr(pl, 'die'):
try:
pl.die(irc)
except: # But don't allow it to crash the server.
log.exception('(%s) Error occurred in die() of plugin %s, skipping...', irc.name, pl)
# Delete it from memory (hopefully).
del world.plugins[name]
if name in sys.modules:
del sys.modules[name]
if name in globals():
del globals()[name]
# Garbage collect.
gc.collect()
irc.msg(irc.called_by, "Unloaded plugin %r." % name)
return True # We succeeded, make it clear (this status is used by reload() below)
else:
irc.msg(irc.called_by, "Unknown plugin %r." % name)
utils.add_cmd(unload)
@utils.add_cmd
def reload(irc, source, args):
"""<plugin name>.
Loads a plugin from the plugin folder."""
try:
name = args[0]
except IndexError:
irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: plugin name.")
return
if unload(irc, source, args):
load(irc, source, args)
irc.reply(' '.join(args))
@utils.add_cmd
def rehash(irc, source, args):
@ -327,7 +178,7 @@ def rehash(irc, source, args):
new_conf = conf.validateConf(conf.loadConf(fname))
except Exception as e: # Something went wrong, abort.
log.exception("Error REHASH'ing config: ")
irc.msg(irc.called_by, "Error loading configuration file: %s: %s", type(e).__name__, e)
irc.reply("Error loading configuration file: %s: %s", type(e).__name__, e)
return
conf.conf = new_conf
for network, ircobj in world.networkobjects.copy().items():
@ -347,4 +198,25 @@ def rehash(irc, source, args):
if network not in world.networkobjects:
proto = utils.getProtoModule(sdata['protocol'])
world.networkobjects[network] = classes.Irc(network, proto, new_conf)
irc.msg(irc.called_by, "Done.")
irc.reply("Done.")
loglevels = {'DEBUG': 10, 'INFO': 20, 'WARNING': 30, 'ERROR': 40, 'CRITICAL': 50}
@utils.add_cmd
def loglevel(irc, source, args):
"""<level>
Sets the log level to the given <level>. <level> must be either DEBUG, INFO, WARNING, ERROR, or CRITICAL.
If no log level is given, shows the current one."""
utils.checkAuthenticated(irc, source, allowOper=False)
try:
level = args[0].upper()
try:
loglevel = loglevels[level]
except KeyError:
irc.reply('Error: Unknown log level "%s".' % level)
return
else:
log.setLevel(loglevel)
irc.reply("Done.")
except IndexError:
irc.reply(log.getEffectiveLevel())

6
plugins/exec.py Executable file → Normal file
View File

@ -17,7 +17,7 @@ def _exec(irc, source, args):
utils.checkAuthenticated(irc, source, allowOper=False)
args = ' '.join(args)
if not args.strip():
irc.msg(irc.called_by, 'No code entered!')
irc.reply('No code entered!')
return
log.info('(%s) Executing %r for %s', irc.name, args, utils.getHostmask(irc, source))
exec(args, globals(), locals())
@ -31,8 +31,8 @@ def _eval(irc, source, args):
utils.checkAuthenticated(irc, source, allowOper=False)
args = ' '.join(args)
if not args.strip():
irc.msg(irc.called_by, 'No code entered!')
irc.reply('No code entered!')
return
log.info('(%s) Evaluating %r for %s', irc.name, args, utils.getHostmask(irc, source))
irc.msg(irc.called_by, eval(args))
irc.reply(eval(args))
utils.add_cmd(_eval, 'eval')

0
plugins/fantasy.py Executable file → Normal file
View File

View File

@ -22,12 +22,12 @@ def disconnect(irc, source, args):
netname = args[0]
network = world.networkobjects[netname]
except IndexError: # No argument given.
irc.msg(irc.called_by, 'Error: Not enough arguments (needs 1: network name (case sensitive)).')
irc.reply('Error: Not enough arguments (needs 1: network name (case sensitive)).')
return
except KeyError: # Unknown network.
irc.msg(irc.called_by, 'Error: No such network "%s" (case sensitive).' % netname)
irc.reply('Error: No such network "%s" (case sensitive).' % netname)
return
irc.msg(irc.called_by, "Done.")
irc.reply("Done.")
# Abort the connection! Simple as that.
network.aborted.set()
@ -41,18 +41,18 @@ def connect(irc, source, args):
netname = args[0]
network = world.networkobjects[netname]
except IndexError: # No argument given.
irc.msg(irc.called_by, 'Error: Not enough arguments (needs 1: network name (case sensitive)).')
irc.reply('Error: Not enough arguments (needs 1: network name (case sensitive)).')
return
except KeyError: # Unknown network.
irc.msg(irc.called_by, 'Error: No such network "%s" (case sensitive).' % netname)
irc.reply('Error: No such network "%s" (case sensitive).' % netname)
return
if network.connection_thread.is_alive():
irc.msg(irc.called_by, 'Error: Network "%s" seems to be already connected.' % netname)
irc.reply('Error: Network "%s" seems to be already connected.' % netname)
else: # Reconnect the network!
network.initVars()
network.connection_thread = threading.Thread(target=network.connect)
network.connection_thread.start()
irc.msg(irc.called_by, "Done.")
irc.reply("Done.")
@utils.add_cmd
def autoconnect(irc, source, args):
@ -66,13 +66,13 @@ def autoconnect(irc, source, args):
seconds = float(args[1])
network = world.networkobjects[netname]
except IndexError: # Arguments not given.
irc.msg(irc.called_by, 'Error: Not enough arguments (needs 2: network name (case sensitive), autoconnect time (in seconds)).')
irc.reply('Error: Not enough arguments (needs 2: network name (case sensitive), autoconnect time (in seconds)).')
return
except KeyError: # Unknown network.
irc.msg(irc.called_by, 'Error: No such network "%s" (case sensitive).' % netname)
irc.reply('Error: No such network "%s" (case sensitive).' % netname)
return
except ValueError:
irc.msg(irc.called_by, 'Error: Invalid argument "%s" for <seconds>.' % seconds)
irc.reply('Error: Invalid argument "%s" for <seconds>.' % seconds)
return
network.serverdata['autoconnect'] = seconds
irc.msg(irc.called_by, "Done.")
irc.reply("Done.")

View File

@ -363,7 +363,8 @@ def initializeChannel(irc, channel):
# Send our users and channel modes to the other nets
log.debug('(%s) initializeChannel: joining our (%s) users: %s', irc.name, remotenet, irc.channels[channel].users)
relayJoins(irc, channel, irc.channels[channel].users, irc.channels[channel].ts)
irc.proto.joinClient(irc.pseudoclient.uid, channel)
if irc.pseudoclient.uid not in irc.channels[channel].users:
irc.proto.joinClient(irc.pseudoclient.uid, channel)
def removeChannel(irc, channel):
"""Destroys a relay channel by parting all of its users."""
@ -822,8 +823,8 @@ def handle_kick(irc, source, command, args):
remoteirc.proto.kickServer(rsid, remotechan, real_target, text)
# If the target isn't on any channels, quit them.
if origuser and origuser[0] != remoteirc.name and not remoteirc.users[real_target].channels:
del relayusers[origuser][remoteirc.name]
if remoteirc != irc and (not remoteirc.users[real_target].channels) and not origuser:
del relayusers[(irc.name, target)][remoteirc.name]
remoteirc.proto.quitClient(real_target, 'Left all shared channels.')
if origuser and not irc.users[target].channels:
@ -1011,7 +1012,7 @@ def handle_disconnect(irc, numeric, command, args):
del relayusers[k][irc.name]
if k[0] == irc.name:
try:
handle_quit(irc, k[1], 'PYLINK_DISCONNECT', {'text': 'Home network lost connection.'})
handle_quit(irc, k[1], 'PYLINK_DISCONNECT', {'text': 'Relay network lost connection.'})
del relayusers[k]
except KeyError:
pass
@ -1024,7 +1025,7 @@ def handle_disconnect(irc, numeric, command, args):
except KeyError:
continue
else:
ircobj.proto.squitServer(ircobj.sid, rsid, text='Home network lost connection.')
ircobj.proto.squitServer(ircobj.sid, rsid, text='Relay network lost connection.')
del relayservers[name][irc.name]
try:
del relayservers[irc.name]
@ -1074,22 +1075,22 @@ def create(irc, source, args):
try:
channel = utils.toLower(irc, args[0])
except IndexError:
irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: channel.")
irc.reply("Error: Not enough arguments. Needs 1: channel.")
return
if not utils.isChannel(channel):
irc.msg(irc.called_by, 'Error: Invalid channel %r.' % channel)
irc.reply('Error: Invalid channel %r.' % channel)
return
if source not in irc.channels[channel].users:
irc.msg(irc.called_by, 'Error: You must be in %r to complete this operation.' % channel)
irc.reply('Error: You must be in %r to complete this operation.' % channel)
return
utils.checkAuthenticated(irc, source)
localentry = getRelay((irc.name, channel))
if localentry:
irc.msg(irc.called_by, 'Error: Channel %r is already part of a relay.' % channel)
irc.reply('Error: Channel %r is already part of a relay.' % channel)
return
db[(irc.name, channel)] = {'claim': [irc.name], 'links': set(), 'blocked_nets': set()}
initializeChannel(irc, channel)
irc.msg(irc.called_by, 'Done.')
irc.reply('Done.')
@utils.add_cmd
def destroy(irc, source, args):
@ -1099,10 +1100,10 @@ def destroy(irc, source, args):
try:
channel = utils.toLower(irc, args[0])
except IndexError:
irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: channel.")
irc.reply("Error: Not enough arguments. Needs 1: channel.")
return
if not utils.isChannel(channel):
irc.msg(irc.called_by, 'Error: Invalid channel %r.' % channel)
irc.reply('Error: Invalid channel %r.' % channel)
return
utils.checkAuthenticated(irc, source)
@ -1112,9 +1113,9 @@ def destroy(irc, source, args):
removeChannel(world.networkobjects.get(link[0]), link[1])
removeChannel(irc, channel)
del db[entry]
irc.msg(irc.called_by, 'Done.')
irc.reply('Done.')
else:
irc.msg(irc.called_by, 'Error: No such relay %r exists.' % channel)
irc.reply('Error: No such relay %r exists.' % channel)
return
@utils.add_cmd
@ -1127,7 +1128,7 @@ def link(irc, source, args):
channel = utils.toLower(irc, args[1])
remotenet = args[0].lower()
except IndexError:
irc.msg(irc.called_by, "Error: Not enough arguments. Needs 2-3: remote netname, channel, local channel name (optional).")
irc.reply("Error: Not enough arguments. Needs 2-3: remote netname, channel, local channel name (optional).")
return
try:
localchan = utils.toLower(irc, args[2])
@ -1135,37 +1136,37 @@ def link(irc, source, args):
localchan = channel
for c in (channel, localchan):
if not utils.isChannel(c):
irc.msg(irc.called_by, 'Error: Invalid channel %r.' % c)
irc.reply('Error: Invalid channel %r.' % c)
return
if source not in irc.channels[localchan].users:
irc.msg(irc.called_by, 'Error: You must be in %r to complete this operation.' % localchan)
irc.reply('Error: You must be in %r to complete this operation.' % localchan)
return
utils.checkAuthenticated(irc, source)
if remotenet not in world.networkobjects:
irc.msg(irc.called_by, 'Error: No network named %r exists.' % remotenet)
irc.reply('Error: No network named %r exists.' % remotenet)
return
localentry = getRelay((irc.name, localchan))
if localentry:
irc.msg(irc.called_by, 'Error: Channel %r is already part of a relay.' % localchan)
irc.reply('Error: Channel %r is already part of a relay.' % localchan)
return
try:
entry = db[(remotenet, channel)]
except KeyError:
irc.msg(irc.called_by, 'Error: No such relay %r exists.' % channel)
irc.reply('Error: No such relay %r exists.' % channel)
return
else:
if irc.name in entry['blocked_nets']:
irc.msg(irc.called_by, 'Error: Access denied (network is banned from linking to this channel).')
irc.reply('Error: Access denied (network is banned from linking to this channel).')
return
for link in entry['links']:
if link[0] == irc.name:
irc.msg(irc.called_by, "Error: Remote channel '%s%s' is already"
irc.reply("Error: Remote channel '%s%s' is already"
" linked here as %r." % (remotenet,
channel, link[1]))
return
entry['links'].add((irc.name, localchan))
initializeChannel(irc, localchan)
irc.msg(irc.called_by, 'Done.')
irc.reply('Done.')
@utils.add_cmd
def delink(irc, source, args):
@ -1176,7 +1177,7 @@ def delink(irc, source, args):
try:
channel = utils.toLower(irc, args[0])
except IndexError:
irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1-2: channel, remote netname (optional).")
irc.reply("Error: Not enough arguments. Needs 1-2: channel, remote netname (optional).")
return
try:
remotenet = args[1].lower()
@ -1184,13 +1185,13 @@ def delink(irc, source, args):
remotenet = None
utils.checkAuthenticated(irc, source)
if not utils.isChannel(channel):
irc.msg(irc.called_by, 'Error: Invalid channel %r.' % channel)
irc.reply('Error: Invalid channel %r.' % channel)
return
entry = getRelay((irc.name, channel))
if entry:
if entry[0] == irc.name: # We own this channel.
if not remotenet:
irc.msg(irc.called_by, "Error: You must select a network to "
irc.reply("Error: You must select a network to "
"delink, or use the 'destroy' command to remove "
"this relay entirely (it was created on the current "
"network).")
@ -1203,9 +1204,9 @@ def delink(irc, source, args):
else:
removeChannel(irc, channel)
db[entry]['links'].remove((irc.name, channel))
irc.msg(irc.called_by, 'Done.')
irc.reply('Done.')
else:
irc.msg(irc.called_by, 'Error: No such relay %r.' % channel)
irc.reply('Error: No such relay %r.' % channel)
@utils.add_cmd
def linked(irc, source, args):
@ -1247,37 +1248,37 @@ def linkacl(irc, source, args):
cmd = args[0].lower()
channel = utils.toLower(irc, args[1])
except IndexError:
irc.msg(irc.called_by, missingargs)
irc.reply(missingargs)
return
if not utils.isChannel(channel):
irc.msg(irc.called_by, 'Error: Invalid channel %r.' % channel)
irc.reply('Error: Invalid channel %r.' % channel)
return
relay = getRelay((irc.name, channel))
if not relay:
irc.msg(irc.called_by, 'Error: No such relay %r exists.' % channel)
irc.reply('Error: No such relay %r exists.' % channel)
return
if cmd == 'list':
s = 'Blocked networks for \x02%s\x02: \x02%s\x02' % (channel, ', '.join(db[relay]['blocked_nets']) or '(empty)')
irc.msg(irc.called_by, s)
irc.reply(s)
return
try:
remotenet = args[2]
except IndexError:
irc.msg(irc.called_by, missingargs)
irc.reply(missingargs)
return
if cmd == 'deny':
db[relay]['blocked_nets'].add(remotenet)
irc.msg(irc.called_by, 'Done.')
irc.reply('Done.')
elif cmd == 'allow':
try:
db[relay]['blocked_nets'].remove(remotenet)
except KeyError:
irc.msg(irc.called_by, 'Error: Network %r is not on the blacklist for %r.' % (remotenet, channel))
irc.reply('Error: Network %r is not on the blacklist for %r.' % (remotenet, channel))
else:
irc.msg(irc.called_by, 'Done.')
irc.reply('Done.')
else:
irc.msg(irc.called_by, 'Error: Unknown subcommand %r: valid ones are ALLOW, DENY, and LIST.' % cmd)
irc.reply('Error: Unknown subcommand %r: valid ones are ALLOW, DENY, and LIST.' % cmd)
@utils.add_cmd
def showuser(irc, source, args):
@ -1322,7 +1323,7 @@ def save(irc, source, args):
Saves the relay database to disk."""
utils.checkAuthenticated(irc, source)
exportDB()
irc.msg(irc.called_by, 'Done.')
irc.reply('Done.')
@utils.add_cmd
def claim(irc, source, args):
@ -1335,19 +1336,19 @@ def claim(irc, source, args):
try:
channel = utils.toLower(irc, args[0])
except IndexError:
irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1-2: channel, list of networks (optional).")
irc.reply("Error: Not enough arguments. Needs 1-2: channel, list of networks (optional).")
return
# We override getRelay() here to limit the search to the current network.
relay = (irc.name, channel)
if relay not in db:
irc.msg(irc.called_by, 'Error: No such relay %r exists.' % channel)
irc.reply('Error: No such relay %r exists.' % channel)
return
claimed = db[relay]["claim"]
try:
nets = args[1].strip()
except IndexError: # No networks given.
irc.msg(irc.called_by, 'Channel \x02%s\x02 is claimed by: %s' %
irc.reply('Channel \x02%s\x02 is claimed by: %s' %
(channel, ', '.join(claimed) or '\x1D(none)\x1D'))
else:
if nets == '-' or not nets:
@ -1355,5 +1356,5 @@ def claim(irc, source, args):
else:
claimed = set(nets.split(','))
db[relay]["claim"] = claimed
irc.msg(irc.called_by, 'CLAIM for channel \x02%s\x02 set to: %s' %
irc.reply('CLAIM for channel \x02%s\x02 set to: %s' %
(channel, ', '.join(claimed) or '\x1D(none)\x1D'))

View File

@ -23,7 +23,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
# are called with the right hooks.
self.hook_map = {'FJOIN': 'JOIN', 'RSQUIT': 'SQUIT', 'FMODE': 'MODE',
'FTOPIC': 'TOPIC', 'OPERTYPE': 'MODE', 'FHOST': 'CHGHOST',
'FIDENT': 'CHGIDENT', 'FNAME': 'CHGNAME'}
'FIDENT': 'CHGIDENT', 'FNAME': 'CHGNAME', 'SVSTOPIC': 'TOPIC'}
self.sidgen = utils.TS6SIDGenerator(self.irc)
self.uidgen = {}
@ -514,6 +514,9 @@ class InspIRCdProtocol(TS6BaseProtocol):
self.irc.channels[channel].topicset = True
return {'channel': channel, 'setter': setter, 'ts': ts, 'topic': topic}
# SVSTOPIC is used by InspIRCd module m_topiclock - its arguments are the same as FTOPIC
handle_svstopic = handle_ftopic
def handle_invite(self, numeric, command, args):
"""Handles incoming INVITEs."""
# <- :70MAAAAAC INVITE 0ALAAAAAA #blah 0

View File

@ -578,9 +578,9 @@ class TS6Protocol(TS6BaseProtocol):
log.debug('Applying modes %s for %s', parsedmodes, uid)
utils.applyModes(self.irc, uid, parsedmodes)
self.irc.servers[numeric].users.add(uid)
# Call the OPERED UP hook if +o is in the mode list.
if ('o', None) in parsedmodes:
otype = 'Server_Administrator' if ('a', None) in parsedmodes else 'IRC_Operator'
# Call the OPERED UP hook if +o is being added to the mode list.
if ('+o', None) in parsedmodes:
otype = 'Server_Administrator' if ('+a', None) in parsedmodes else 'IRC_Operator'
self.irc.callHooks([uid, 'PYLINK_CLIENT_OPERED', {'text': otype}])
return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host, 'ident': ident, 'ip': ip}

View File

@ -104,15 +104,18 @@ class TS6SIDGenerator():
return sid
def add_cmd(func, name=None):
"""Binds a command to the given command name."""
if name is None:
name = func.__name__
name = name.lower()
world.commands[name].append(func)
return func
def add_hook(func, command):
"""Add a hook <func> for command <command>."""
"""Binds a hook function to the given command."""
command = command.upper()
world.hooks[command].append(func)
return func
def toLower(irc, text):
"""Returns a lowercase representation of text based on the IRC object's