mirror of
https://github.com/jlu5/PyLink.git
synced 2025-02-16 13:31:02 +01:00
Merge branch 'master' into wip/unrealircd
This commit is contained in:
commit
cedcb9b11a
@ -178,11 +178,13 @@ class Irc():
|
|||||||
return
|
return
|
||||||
|
|
||||||
def callCommand(self, source, text):
|
def callCommand(self, source, text):
|
||||||
|
"""Calls a PyLink bot command."""
|
||||||
cmd_args = text.strip().split(' ')
|
cmd_args = text.strip().split(' ')
|
||||||
cmd = cmd_args[0].lower()
|
cmd = cmd_args[0].lower()
|
||||||
cmd_args = cmd_args[1:]
|
cmd_args = cmd_args[1:]
|
||||||
if cmd not in world.commands:
|
if cmd not in world.commands:
|
||||||
self.msg(self.called_by or source, 'Error: Unknown command %r.' % cmd)
|
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
|
return
|
||||||
log.info('(%s) Calling command %r for %s', self.name, cmd, utils.getHostmask(self, source))
|
log.info('(%s) Calling command %r for %s', self.name, cmd, utils.getHostmask(self, source))
|
||||||
for func in world.commands[cmd]:
|
for func in world.commands[cmd]:
|
||||||
@ -206,6 +208,10 @@ class Irc():
|
|||||||
cmd = 'PYLINK_SELF_PRIVMSG'
|
cmd = 'PYLINK_SELF_PRIVMSG'
|
||||||
self.callHooks([source, cmd, {'target': target, 'text': text}])
|
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):
|
def _disconnect(self):
|
||||||
log.debug('(%s) Canceling pingTimer at %s due to _disconnect() call', self.name, time.time())
|
log.debug('(%s) Canceling pingTimer at %s due to _disconnect() call', self.name, time.time())
|
||||||
self.connected.clear()
|
self.connected.clear()
|
||||||
|
160
coreplugin.py
160
coreplugin.py
@ -1,4 +1,7 @@
|
|||||||
## coreplugin.py - Core PyLink plugin
|
# coreplugin.py - Implements core PyLink functions as a plugin
|
||||||
|
|
||||||
|
import gc
|
||||||
|
import sys
|
||||||
|
|
||||||
import utils
|
import utils
|
||||||
from log import log
|
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)):
|
if ('-o', None) in modes and (target == irc.pseudoclient.uid or not utils.isManipulatableClient(irc, target)):
|
||||||
irc.proto.modeServer(irc.sid, target, {('+o', None)})
|
irc.proto.modeServer(irc.sid, target, {('+o', None)})
|
||||||
utils.add_hook(handle_mode, 'MODE')
|
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)
|
||||||
|
@ -20,7 +20,7 @@ def spawnclient(irc, source, args):
|
|||||||
try:
|
try:
|
||||||
nick, ident, host = args[:3]
|
nick, ident, host = args[:3]
|
||||||
except ValueError:
|
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
|
return
|
||||||
irc.proto.spawnClient(nick, ident, host, manipulatable=True)
|
irc.proto.spawnClient(nick, ident, host, manipulatable=True)
|
||||||
|
|
||||||
@ -33,15 +33,15 @@ def quit(irc, source, args):
|
|||||||
try:
|
try:
|
||||||
nick = args[0]
|
nick = args[0]
|
||||||
except IndexError:
|
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
|
return
|
||||||
if irc.pseudoclient.uid == utils.nickToUid(irc, nick):
|
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
|
return
|
||||||
u = utils.nickToUid(irc, nick)
|
u = utils.nickToUid(irc, nick)
|
||||||
quitmsg = ' '.join(args[1:]) or 'Client Quit'
|
quitmsg = ' '.join(args[1:]) or 'Client Quit'
|
||||||
if not utils.isManipulatableClient(irc, u):
|
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
|
return
|
||||||
irc.proto.quitClient(u, quitmsg)
|
irc.proto.quitClient(u, quitmsg)
|
||||||
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_QUIT', {'text': quitmsg, 'parse_as': 'QUIT'}])
|
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_QUIT', {'text': quitmsg, 'parse_as': 'QUIT'}])
|
||||||
@ -57,15 +57,15 @@ def joinclient(irc, source, args):
|
|||||||
if not clist:
|
if not clist:
|
||||||
raise IndexError
|
raise IndexError
|
||||||
except 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
|
return
|
||||||
u = utils.nickToUid(irc, nick)
|
u = utils.nickToUid(irc, nick)
|
||||||
if not utils.isManipulatableClient(irc, u):
|
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
|
return
|
||||||
for channel in clist:
|
for channel in clist:
|
||||||
if not utils.isChannel(channel):
|
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
|
return
|
||||||
irc.proto.joinClient(u, channel)
|
irc.proto.joinClient(u, channel)
|
||||||
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_JOIN', {'channel': channel, 'users': [u],
|
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_JOIN', {'channel': channel, 'users': [u],
|
||||||
@ -83,16 +83,16 @@ def nick(irc, source, args):
|
|||||||
nick = args[0]
|
nick = args[0]
|
||||||
newnick = args[1]
|
newnick = args[1]
|
||||||
except IndexError:
|
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
|
return
|
||||||
u = utils.nickToUid(irc, nick)
|
u = utils.nickToUid(irc, nick)
|
||||||
if newnick in ('0', u):
|
if newnick in ('0', u):
|
||||||
newnick = u
|
newnick = u
|
||||||
elif not utils.isNick(newnick):
|
elif not utils.isNick(newnick):
|
||||||
irc.msg(irc.called_by, 'Error: Invalid nickname %r.' % newnick)
|
irc.reply('Error: Invalid nickname %r.' % newnick)
|
||||||
return
|
return
|
||||||
elif not utils.isManipulatableClient(irc, u):
|
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
|
return
|
||||||
irc.proto.nickClient(u, newnick)
|
irc.proto.nickClient(u, newnick)
|
||||||
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_NICK', {'newnick': newnick, 'oldnick': nick, 'parse_as': 'NICK'}])
|
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(',')
|
clist = args[1].split(',')
|
||||||
reason = ' '.join(args[2:])
|
reason = ' '.join(args[2:])
|
||||||
except 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
|
return
|
||||||
u = utils.nickToUid(irc, nick)
|
u = utils.nickToUid(irc, nick)
|
||||||
if not utils.isManipulatableClient(irc, u):
|
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
|
return
|
||||||
for channel in clist:
|
for channel in clist:
|
||||||
if not utils.isChannel(channel):
|
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
|
return
|
||||||
irc.proto.partClient(u, channel, reason)
|
irc.proto.partClient(u, channel, reason)
|
||||||
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_PART', {'channels': clist, 'text': reason, 'parse_as': 'PART'}])
|
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]
|
target = args[2]
|
||||||
reason = ' '.join(args[3:])
|
reason = ' '.join(args[3:])
|
||||||
except IndexError:
|
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
|
return
|
||||||
u = utils.nickToUid(irc, nick) or nick
|
u = utils.nickToUid(irc, nick) or nick
|
||||||
targetu = utils.nickToUid(irc, target)
|
targetu = utils.nickToUid(irc, target)
|
||||||
if not utils.isChannel(channel):
|
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
|
return
|
||||||
if utils.isInternalServer(irc, u):
|
if utils.isInternalServer(irc, u):
|
||||||
irc.proto.kickServer(u, channel, targetu, reason)
|
irc.proto.kickServer(u, channel, targetu, reason)
|
||||||
@ -155,20 +155,20 @@ def mode(irc, source, args):
|
|||||||
try:
|
try:
|
||||||
modesource, target, modes = args[0], args[1], args[2:]
|
modesource, target, modes = args[0], args[1], args[2:]
|
||||||
except IndexError:
|
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
|
return
|
||||||
target = utils.nickToUid(irc, target) or target
|
target = utils.nickToUid(irc, target) or target
|
||||||
extclient = target in irc.users and not utils.isInternalClient(irc, target)
|
extclient = target in irc.users and not utils.isInternalClient(irc, target)
|
||||||
parsedmodes = utils.parseModes(irc, target, modes)
|
parsedmodes = utils.parseModes(irc, target, modes)
|
||||||
ischannel = target in irc.channels
|
ischannel = target in irc.channels
|
||||||
if not (target in irc.users or ischannel):
|
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
|
return
|
||||||
elif not parsedmodes:
|
elif not parsedmodes:
|
||||||
irc.msg(irc.called_by, "Error: No valid modes were given.")
|
irc.reply("Error: No valid modes were given.")
|
||||||
return
|
return
|
||||||
elif not (ischannel or utils.isManipulatableClient(irc, target)):
|
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
|
return
|
||||||
if utils.isInternalServer(irc, modesource):
|
if utils.isInternalServer(irc, modesource):
|
||||||
# Setting modes from a server.
|
# Setting modes from a server.
|
||||||
@ -189,21 +189,21 @@ def msg(irc, source, args):
|
|||||||
try:
|
try:
|
||||||
msgsource, target, text = args[0], args[1], ' '.join(args[2:])
|
msgsource, target, text = args[0], args[1], ' '.join(args[2:])
|
||||||
except IndexError:
|
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
|
return
|
||||||
sourceuid = utils.nickToUid(irc, msgsource)
|
sourceuid = utils.nickToUid(irc, msgsource)
|
||||||
if not sourceuid:
|
if not sourceuid:
|
||||||
irc.msg(irc.called_by, 'Error: Unknown user %r.' % msgsource)
|
irc.reply('Error: Unknown user %r.' % msgsource)
|
||||||
return
|
return
|
||||||
if not utils.isChannel(target):
|
if not utils.isChannel(target):
|
||||||
real_target = utils.nickToUid(irc, target)
|
real_target = utils.nickToUid(irc, target)
|
||||||
if real_target is None:
|
if real_target is None:
|
||||||
irc.msg(irc.called_by, 'Error: Unknown user %r.' % target)
|
irc.reply('Error: Unknown user %r.' % target)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
real_target = target
|
real_target = target
|
||||||
if not text:
|
if not text:
|
||||||
irc.msg(irc.called_by, 'Error: No text given.')
|
irc.reply('Error: No text given.')
|
||||||
return
|
return
|
||||||
irc.proto.messageClient(sourceuid, real_target, text)
|
irc.proto.messageClient(sourceuid, real_target, text)
|
||||||
irc.callHooks([sourceuid, 'PYLINK_BOTSPLUGIN_MSG', {'target': real_target, 'text': text, 'parse_as': 'PRIVMSG'}])
|
irc.callHooks([sourceuid, 'PYLINK_BOTSPLUGIN_MSG', {'target': real_target, 'text': text, 'parse_as': 'PRIVMSG'}])
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
from time import ctime
|
from time import ctime
|
||||||
import itertools
|
|
||||||
import gc
|
|
||||||
|
|
||||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
import utils
|
import utils
|
||||||
@ -19,36 +17,10 @@ def status(irc, source, args):
|
|||||||
Returns your current PyLink login status."""
|
Returns your current PyLink login status."""
|
||||||
identified = irc.users[source].identified
|
identified = irc.users[source].identified
|
||||||
if 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:
|
else:
|
||||||
irc.msg(irc.called_by, 'You are not identified as anyone.')
|
irc.reply('You are not identified as anyone.')
|
||||||
irc.msg(irc.called_by, 'Operator access: \x02%s\x02' % bool(utils.isOper(irc, source)))
|
irc.reply('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))
|
|
||||||
|
|
||||||
def listcommands(irc, source, args):
|
def listcommands(irc, source, args):
|
||||||
"""takes no arguments.
|
"""takes no arguments.
|
||||||
@ -60,8 +32,8 @@ def listcommands(irc, source, args):
|
|||||||
nfuncs = len(world.commands[cmd])
|
nfuncs = len(world.commands[cmd])
|
||||||
if nfuncs > 1:
|
if nfuncs > 1:
|
||||||
cmds[idx] = '%s(x%s)' % (cmd, nfuncs)
|
cmds[idx] = '%s(x%s)' % (cmd, nfuncs)
|
||||||
irc.msg(irc.called_by, 'Available commands include: %s' % ', '.join(cmds))
|
irc.reply('Available commands include: %s' % ', '.join(cmds))
|
||||||
irc.msg(irc.called_by, 'To see help on a specific command, type \x02help <command>\x02.')
|
irc.reply('To see help on a specific command, type \x02help <command>\x02.')
|
||||||
utils.add_cmd(listcommands, 'list')
|
utils.add_cmd(listcommands, 'list')
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
@ -80,7 +52,7 @@ def help(irc, source, args):
|
|||||||
else:
|
else:
|
||||||
funcs = world.commands[command]
|
funcs = world.commands[command]
|
||||||
if len(funcs) > 1:
|
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])))
|
% (len(funcs), command, ', '.join([func.__module__ for func in funcs])))
|
||||||
for func in funcs:
|
for func in funcs:
|
||||||
doc = func.__doc__
|
doc = func.__doc__
|
||||||
@ -91,7 +63,7 @@ def help(irc, source, args):
|
|||||||
# arguments the command takes.
|
# arguments the command takes.
|
||||||
lines[0] = '\x02%s %s\x02 (plugin: %r)' % (command, lines[0], mod)
|
lines[0] = '\x02%s %s\x02 (plugin: %r)' % (command, lines[0], mod)
|
||||||
for line in lines:
|
for line in lines:
|
||||||
irc.msg(irc.called_by, line.strip())
|
irc.reply(line.strip())
|
||||||
else:
|
else:
|
||||||
irc.msg(source, "Error: Command %r (from plugin %r) "
|
irc.msg(source, "Error: Command %r (from plugin %r) "
|
||||||
"doesn't offer any help." % (command, mod))
|
"doesn't offer any help." % (command, mod))
|
||||||
@ -105,14 +77,14 @@ def showuser(irc, source, args):
|
|||||||
try:
|
try:
|
||||||
target = args[0]
|
target = args[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: nick.")
|
irc.reply("Error: Not enough arguments. Needs 1: nick.")
|
||||||
return
|
return
|
||||||
u = utils.nickToUid(irc, target) or target
|
u = utils.nickToUid(irc, target) or target
|
||||||
# Only show private info if the person is calling 'showuser' on themselves,
|
# Only show private info if the person is calling 'showuser' on themselves,
|
||||||
# or is an oper.
|
# or is an oper.
|
||||||
verbose = utils.isOper(irc, source) or u == source
|
verbose = utils.isOper(irc, source) or u == source
|
||||||
if u not in irc.users:
|
if u not in irc.users:
|
||||||
irc.msg(irc.called_by, 'Error: Unknown user %r.' % target)
|
irc.reply('Error: Unknown user %r.' % target)
|
||||||
return
|
return
|
||||||
|
|
||||||
f = lambda s: irc.msg(source, s)
|
f = lambda s: irc.msg(source, s)
|
||||||
@ -140,10 +112,10 @@ def showchan(irc, source, args):
|
|||||||
try:
|
try:
|
||||||
channel = utils.toLower(irc, args[0])
|
channel = utils.toLower(irc, args[0])
|
||||||
except IndexError:
|
except IndexError:
|
||||||
irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: channel.")
|
irc.reply("Error: Not enough arguments. Needs 1: channel.")
|
||||||
return
|
return
|
||||||
if channel not in irc.channels:
|
if channel not in irc.channels:
|
||||||
irc.msg(irc.called_by, 'Error: Unknown channel %r.' % channel)
|
irc.reply('Error: Unknown channel %r.' % channel)
|
||||||
return
|
return
|
||||||
|
|
||||||
f = lambda s: irc.msg(source, s)
|
f = lambda s: irc.msg(source, s)
|
||||||
@ -178,141 +150,20 @@ def showchan(irc, source, args):
|
|||||||
f('\x02User list\x02: %s' % ' '.join(nicklist[:20]))
|
f('\x02User list\x02: %s' % ' '.join(nicklist[:20]))
|
||||||
nicklist = 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
|
@utils.add_cmd
|
||||||
def version(irc, source, args):
|
def version(irc, source, args):
|
||||||
"""takes no arguments.
|
"""takes no arguments.
|
||||||
|
|
||||||
Returns the version of the currently running PyLink instance."""
|
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.reply("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("The source of this program is available at \x02%s\x02." % world.source)
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
def echo(irc, source, args):
|
def echo(irc, source, args):
|
||||||
"""<text>
|
"""<text>
|
||||||
|
|
||||||
Echoes the text given."""
|
Echoes the text given."""
|
||||||
irc.msg(irc.called_by, ' '.join(args))
|
irc.reply(' '.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)
|
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
def rehash(irc, source, args):
|
def rehash(irc, source, args):
|
||||||
@ -327,7 +178,7 @@ def rehash(irc, source, args):
|
|||||||
new_conf = conf.validateConf(conf.loadConf(fname))
|
new_conf = conf.validateConf(conf.loadConf(fname))
|
||||||
except Exception as e: # Something went wrong, abort.
|
except Exception as e: # Something went wrong, abort.
|
||||||
log.exception("Error REHASH'ing config: ")
|
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
|
return
|
||||||
conf.conf = new_conf
|
conf.conf = new_conf
|
||||||
for network, ircobj in world.networkobjects.copy().items():
|
for network, ircobj in world.networkobjects.copy().items():
|
||||||
@ -347,4 +198,25 @@ def rehash(irc, source, args):
|
|||||||
if network not in world.networkobjects:
|
if network not in world.networkobjects:
|
||||||
proto = utils.getProtoModule(sdata['protocol'])
|
proto = utils.getProtoModule(sdata['protocol'])
|
||||||
world.networkobjects[network] = classes.Irc(network, proto, new_conf)
|
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
6
plugins/exec.py
Executable file → Normal file
@ -17,7 +17,7 @@ def _exec(irc, source, args):
|
|||||||
utils.checkAuthenticated(irc, source, allowOper=False)
|
utils.checkAuthenticated(irc, source, allowOper=False)
|
||||||
args = ' '.join(args)
|
args = ' '.join(args)
|
||||||
if not args.strip():
|
if not args.strip():
|
||||||
irc.msg(irc.called_by, 'No code entered!')
|
irc.reply('No code entered!')
|
||||||
return
|
return
|
||||||
log.info('(%s) Executing %r for %s', irc.name, args, utils.getHostmask(irc, source))
|
log.info('(%s) Executing %r for %s', irc.name, args, utils.getHostmask(irc, source))
|
||||||
exec(args, globals(), locals())
|
exec(args, globals(), locals())
|
||||||
@ -31,8 +31,8 @@ def _eval(irc, source, args):
|
|||||||
utils.checkAuthenticated(irc, source, allowOper=False)
|
utils.checkAuthenticated(irc, source, allowOper=False)
|
||||||
args = ' '.join(args)
|
args = ' '.join(args)
|
||||||
if not args.strip():
|
if not args.strip():
|
||||||
irc.msg(irc.called_by, 'No code entered!')
|
irc.reply('No code entered!')
|
||||||
return
|
return
|
||||||
log.info('(%s) Evaluating %r for %s', irc.name, args, utils.getHostmask(irc, source))
|
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')
|
utils.add_cmd(_eval, 'eval')
|
||||||
|
0
plugins/fantasy.py
Executable file → Normal file
0
plugins/fantasy.py
Executable file → Normal file
@ -22,12 +22,12 @@ def disconnect(irc, source, args):
|
|||||||
netname = args[0]
|
netname = args[0]
|
||||||
network = world.networkobjects[netname]
|
network = world.networkobjects[netname]
|
||||||
except IndexError: # No argument given.
|
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
|
return
|
||||||
except KeyError: # Unknown network.
|
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
|
return
|
||||||
irc.msg(irc.called_by, "Done.")
|
irc.reply("Done.")
|
||||||
# Abort the connection! Simple as that.
|
# Abort the connection! Simple as that.
|
||||||
network.aborted.set()
|
network.aborted.set()
|
||||||
|
|
||||||
@ -41,18 +41,18 @@ def connect(irc, source, args):
|
|||||||
netname = args[0]
|
netname = args[0]
|
||||||
network = world.networkobjects[netname]
|
network = world.networkobjects[netname]
|
||||||
except IndexError: # No argument given.
|
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
|
return
|
||||||
except KeyError: # Unknown network.
|
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
|
return
|
||||||
if network.connection_thread.is_alive():
|
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!
|
else: # Reconnect the network!
|
||||||
network.initVars()
|
network.initVars()
|
||||||
network.connection_thread = threading.Thread(target=network.connect)
|
network.connection_thread = threading.Thread(target=network.connect)
|
||||||
network.connection_thread.start()
|
network.connection_thread.start()
|
||||||
irc.msg(irc.called_by, "Done.")
|
irc.reply("Done.")
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
def autoconnect(irc, source, args):
|
def autoconnect(irc, source, args):
|
||||||
@ -66,13 +66,13 @@ def autoconnect(irc, source, args):
|
|||||||
seconds = float(args[1])
|
seconds = float(args[1])
|
||||||
network = world.networkobjects[netname]
|
network = world.networkobjects[netname]
|
||||||
except IndexError: # Arguments not given.
|
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
|
return
|
||||||
except KeyError: # Unknown network.
|
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
|
return
|
||||||
except ValueError:
|
except ValueError:
|
||||||
irc.msg(irc.called_by, 'Error: Invalid argument "%s" for <seconds>.' % seconds)
|
irc.reply('Error: Invalid argument "%s" for <seconds>.' % seconds)
|
||||||
return
|
return
|
||||||
network.serverdata['autoconnect'] = seconds
|
network.serverdata['autoconnect'] = seconds
|
||||||
irc.msg(irc.called_by, "Done.")
|
irc.reply("Done.")
|
||||||
|
@ -363,7 +363,8 @@ def initializeChannel(irc, channel):
|
|||||||
# Send our users and channel modes to the other nets
|
# 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)
|
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)
|
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):
|
def removeChannel(irc, channel):
|
||||||
"""Destroys a relay channel by parting all of its users."""
|
"""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)
|
remoteirc.proto.kickServer(rsid, remotechan, real_target, text)
|
||||||
|
|
||||||
# If the target isn't on any channels, quit them.
|
# If the target isn't on any channels, quit them.
|
||||||
if origuser and origuser[0] != remoteirc.name and not remoteirc.users[real_target].channels:
|
if remoteirc != irc and (not remoteirc.users[real_target].channels) and not origuser:
|
||||||
del relayusers[origuser][remoteirc.name]
|
del relayusers[(irc.name, target)][remoteirc.name]
|
||||||
remoteirc.proto.quitClient(real_target, 'Left all shared channels.')
|
remoteirc.proto.quitClient(real_target, 'Left all shared channels.')
|
||||||
|
|
||||||
if origuser and not irc.users[target].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]
|
del relayusers[k][irc.name]
|
||||||
if k[0] == irc.name:
|
if k[0] == irc.name:
|
||||||
try:
|
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]
|
del relayusers[k]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
@ -1024,7 +1025,7 @@ def handle_disconnect(irc, numeric, command, args):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
continue
|
continue
|
||||||
else:
|
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]
|
del relayservers[name][irc.name]
|
||||||
try:
|
try:
|
||||||
del relayservers[irc.name]
|
del relayservers[irc.name]
|
||||||
@ -1074,22 +1075,22 @@ def create(irc, source, args):
|
|||||||
try:
|
try:
|
||||||
channel = utils.toLower(irc, args[0])
|
channel = utils.toLower(irc, args[0])
|
||||||
except IndexError:
|
except IndexError:
|
||||||
irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: channel.")
|
irc.reply("Error: Not enough arguments. Needs 1: channel.")
|
||||||
return
|
return
|
||||||
if not utils.isChannel(channel):
|
if not utils.isChannel(channel):
|
||||||
irc.msg(irc.called_by, 'Error: Invalid channel %r.' % channel)
|
irc.reply('Error: Invalid channel %r.' % channel)
|
||||||
return
|
return
|
||||||
if source not in irc.channels[channel].users:
|
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
|
return
|
||||||
utils.checkAuthenticated(irc, source)
|
utils.checkAuthenticated(irc, source)
|
||||||
localentry = getRelay((irc.name, channel))
|
localentry = getRelay((irc.name, channel))
|
||||||
if localentry:
|
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
|
return
|
||||||
db[(irc.name, channel)] = {'claim': [irc.name], 'links': set(), 'blocked_nets': set()}
|
db[(irc.name, channel)] = {'claim': [irc.name], 'links': set(), 'blocked_nets': set()}
|
||||||
initializeChannel(irc, channel)
|
initializeChannel(irc, channel)
|
||||||
irc.msg(irc.called_by, 'Done.')
|
irc.reply('Done.')
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
def destroy(irc, source, args):
|
def destroy(irc, source, args):
|
||||||
@ -1099,10 +1100,10 @@ def destroy(irc, source, args):
|
|||||||
try:
|
try:
|
||||||
channel = utils.toLower(irc, args[0])
|
channel = utils.toLower(irc, args[0])
|
||||||
except IndexError:
|
except IndexError:
|
||||||
irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: channel.")
|
irc.reply("Error: Not enough arguments. Needs 1: channel.")
|
||||||
return
|
return
|
||||||
if not utils.isChannel(channel):
|
if not utils.isChannel(channel):
|
||||||
irc.msg(irc.called_by, 'Error: Invalid channel %r.' % channel)
|
irc.reply('Error: Invalid channel %r.' % channel)
|
||||||
return
|
return
|
||||||
utils.checkAuthenticated(irc, source)
|
utils.checkAuthenticated(irc, source)
|
||||||
|
|
||||||
@ -1112,9 +1113,9 @@ def destroy(irc, source, args):
|
|||||||
removeChannel(world.networkobjects.get(link[0]), link[1])
|
removeChannel(world.networkobjects.get(link[0]), link[1])
|
||||||
removeChannel(irc, channel)
|
removeChannel(irc, channel)
|
||||||
del db[entry]
|
del db[entry]
|
||||||
irc.msg(irc.called_by, 'Done.')
|
irc.reply('Done.')
|
||||||
else:
|
else:
|
||||||
irc.msg(irc.called_by, 'Error: No such relay %r exists.' % channel)
|
irc.reply('Error: No such relay %r exists.' % channel)
|
||||||
return
|
return
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
@ -1127,7 +1128,7 @@ def link(irc, source, args):
|
|||||||
channel = utils.toLower(irc, args[1])
|
channel = utils.toLower(irc, args[1])
|
||||||
remotenet = args[0].lower()
|
remotenet = args[0].lower()
|
||||||
except IndexError:
|
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
|
return
|
||||||
try:
|
try:
|
||||||
localchan = utils.toLower(irc, args[2])
|
localchan = utils.toLower(irc, args[2])
|
||||||
@ -1135,37 +1136,37 @@ def link(irc, source, args):
|
|||||||
localchan = channel
|
localchan = channel
|
||||||
for c in (channel, localchan):
|
for c in (channel, localchan):
|
||||||
if not utils.isChannel(c):
|
if not utils.isChannel(c):
|
||||||
irc.msg(irc.called_by, 'Error: Invalid channel %r.' % c)
|
irc.reply('Error: Invalid channel %r.' % c)
|
||||||
return
|
return
|
||||||
if source not in irc.channels[localchan].users:
|
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
|
return
|
||||||
utils.checkAuthenticated(irc, source)
|
utils.checkAuthenticated(irc, source)
|
||||||
if remotenet not in world.networkobjects:
|
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
|
return
|
||||||
localentry = getRelay((irc.name, localchan))
|
localentry = getRelay((irc.name, localchan))
|
||||||
if localentry:
|
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
|
return
|
||||||
try:
|
try:
|
||||||
entry = db[(remotenet, channel)]
|
entry = db[(remotenet, channel)]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
irc.msg(irc.called_by, 'Error: No such relay %r exists.' % channel)
|
irc.reply('Error: No such relay %r exists.' % channel)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
if irc.name in entry['blocked_nets']:
|
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
|
return
|
||||||
for link in entry['links']:
|
for link in entry['links']:
|
||||||
if link[0] == irc.name:
|
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,
|
" linked here as %r." % (remotenet,
|
||||||
channel, link[1]))
|
channel, link[1]))
|
||||||
return
|
return
|
||||||
entry['links'].add((irc.name, localchan))
|
entry['links'].add((irc.name, localchan))
|
||||||
initializeChannel(irc, localchan)
|
initializeChannel(irc, localchan)
|
||||||
irc.msg(irc.called_by, 'Done.')
|
irc.reply('Done.')
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
def delink(irc, source, args):
|
def delink(irc, source, args):
|
||||||
@ -1176,7 +1177,7 @@ def delink(irc, source, args):
|
|||||||
try:
|
try:
|
||||||
channel = utils.toLower(irc, args[0])
|
channel = utils.toLower(irc, args[0])
|
||||||
except IndexError:
|
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
|
return
|
||||||
try:
|
try:
|
||||||
remotenet = args[1].lower()
|
remotenet = args[1].lower()
|
||||||
@ -1184,13 +1185,13 @@ def delink(irc, source, args):
|
|||||||
remotenet = None
|
remotenet = None
|
||||||
utils.checkAuthenticated(irc, source)
|
utils.checkAuthenticated(irc, source)
|
||||||
if not utils.isChannel(channel):
|
if not utils.isChannel(channel):
|
||||||
irc.msg(irc.called_by, 'Error: Invalid channel %r.' % channel)
|
irc.reply('Error: Invalid channel %r.' % channel)
|
||||||
return
|
return
|
||||||
entry = getRelay((irc.name, channel))
|
entry = getRelay((irc.name, channel))
|
||||||
if entry:
|
if entry:
|
||||||
if entry[0] == irc.name: # We own this channel.
|
if entry[0] == irc.name: # We own this channel.
|
||||||
if not remotenet:
|
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 "
|
"delink, or use the 'destroy' command to remove "
|
||||||
"this relay entirely (it was created on the current "
|
"this relay entirely (it was created on the current "
|
||||||
"network).")
|
"network).")
|
||||||
@ -1203,9 +1204,9 @@ def delink(irc, source, args):
|
|||||||
else:
|
else:
|
||||||
removeChannel(irc, channel)
|
removeChannel(irc, channel)
|
||||||
db[entry]['links'].remove((irc.name, channel))
|
db[entry]['links'].remove((irc.name, channel))
|
||||||
irc.msg(irc.called_by, 'Done.')
|
irc.reply('Done.')
|
||||||
else:
|
else:
|
||||||
irc.msg(irc.called_by, 'Error: No such relay %r.' % channel)
|
irc.reply('Error: No such relay %r.' % channel)
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
def linked(irc, source, args):
|
def linked(irc, source, args):
|
||||||
@ -1247,37 +1248,37 @@ def linkacl(irc, source, args):
|
|||||||
cmd = args[0].lower()
|
cmd = args[0].lower()
|
||||||
channel = utils.toLower(irc, args[1])
|
channel = utils.toLower(irc, args[1])
|
||||||
except IndexError:
|
except IndexError:
|
||||||
irc.msg(irc.called_by, missingargs)
|
irc.reply(missingargs)
|
||||||
return
|
return
|
||||||
if not utils.isChannel(channel):
|
if not utils.isChannel(channel):
|
||||||
irc.msg(irc.called_by, 'Error: Invalid channel %r.' % channel)
|
irc.reply('Error: Invalid channel %r.' % channel)
|
||||||
return
|
return
|
||||||
relay = getRelay((irc.name, channel))
|
relay = getRelay((irc.name, channel))
|
||||||
if not relay:
|
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
|
return
|
||||||
if cmd == 'list':
|
if cmd == 'list':
|
||||||
s = 'Blocked networks for \x02%s\x02: \x02%s\x02' % (channel, ', '.join(db[relay]['blocked_nets']) or '(empty)')
|
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
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
remotenet = args[2]
|
remotenet = args[2]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
irc.msg(irc.called_by, missingargs)
|
irc.reply(missingargs)
|
||||||
return
|
return
|
||||||
if cmd == 'deny':
|
if cmd == 'deny':
|
||||||
db[relay]['blocked_nets'].add(remotenet)
|
db[relay]['blocked_nets'].add(remotenet)
|
||||||
irc.msg(irc.called_by, 'Done.')
|
irc.reply('Done.')
|
||||||
elif cmd == 'allow':
|
elif cmd == 'allow':
|
||||||
try:
|
try:
|
||||||
db[relay]['blocked_nets'].remove(remotenet)
|
db[relay]['blocked_nets'].remove(remotenet)
|
||||||
except KeyError:
|
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:
|
else:
|
||||||
irc.msg(irc.called_by, 'Done.')
|
irc.reply('Done.')
|
||||||
else:
|
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
|
@utils.add_cmd
|
||||||
def showuser(irc, source, args):
|
def showuser(irc, source, args):
|
||||||
@ -1322,7 +1323,7 @@ def save(irc, source, args):
|
|||||||
Saves the relay database to disk."""
|
Saves the relay database to disk."""
|
||||||
utils.checkAuthenticated(irc, source)
|
utils.checkAuthenticated(irc, source)
|
||||||
exportDB()
|
exportDB()
|
||||||
irc.msg(irc.called_by, 'Done.')
|
irc.reply('Done.')
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
def claim(irc, source, args):
|
def claim(irc, source, args):
|
||||||
@ -1335,19 +1336,19 @@ def claim(irc, source, args):
|
|||||||
try:
|
try:
|
||||||
channel = utils.toLower(irc, args[0])
|
channel = utils.toLower(irc, args[0])
|
||||||
except IndexError:
|
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
|
return
|
||||||
|
|
||||||
# We override getRelay() here to limit the search to the current network.
|
# We override getRelay() here to limit the search to the current network.
|
||||||
relay = (irc.name, channel)
|
relay = (irc.name, channel)
|
||||||
if relay not in db:
|
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
|
return
|
||||||
claimed = db[relay]["claim"]
|
claimed = db[relay]["claim"]
|
||||||
try:
|
try:
|
||||||
nets = args[1].strip()
|
nets = args[1].strip()
|
||||||
except IndexError: # No networks given.
|
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'))
|
(channel, ', '.join(claimed) or '\x1D(none)\x1D'))
|
||||||
else:
|
else:
|
||||||
if nets == '-' or not nets:
|
if nets == '-' or not nets:
|
||||||
@ -1355,5 +1356,5 @@ def claim(irc, source, args):
|
|||||||
else:
|
else:
|
||||||
claimed = set(nets.split(','))
|
claimed = set(nets.split(','))
|
||||||
db[relay]["claim"] = claimed
|
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'))
|
(channel, ', '.join(claimed) or '\x1D(none)\x1D'))
|
||||||
|
@ -23,7 +23,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
# are called with the right hooks.
|
# are called with the right hooks.
|
||||||
self.hook_map = {'FJOIN': 'JOIN', 'RSQUIT': 'SQUIT', 'FMODE': 'MODE',
|
self.hook_map = {'FJOIN': 'JOIN', 'RSQUIT': 'SQUIT', 'FMODE': 'MODE',
|
||||||
'FTOPIC': 'TOPIC', 'OPERTYPE': 'MODE', 'FHOST': 'CHGHOST',
|
'FTOPIC': 'TOPIC', 'OPERTYPE': 'MODE', 'FHOST': 'CHGHOST',
|
||||||
'FIDENT': 'CHGIDENT', 'FNAME': 'CHGNAME'}
|
'FIDENT': 'CHGIDENT', 'FNAME': 'CHGNAME', 'SVSTOPIC': 'TOPIC'}
|
||||||
self.sidgen = utils.TS6SIDGenerator(self.irc)
|
self.sidgen = utils.TS6SIDGenerator(self.irc)
|
||||||
self.uidgen = {}
|
self.uidgen = {}
|
||||||
|
|
||||||
@ -514,6 +514,9 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
self.irc.channels[channel].topicset = True
|
self.irc.channels[channel].topicset = True
|
||||||
return {'channel': channel, 'setter': setter, 'ts': ts, 'topic': topic}
|
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):
|
def handle_invite(self, numeric, command, args):
|
||||||
"""Handles incoming INVITEs."""
|
"""Handles incoming INVITEs."""
|
||||||
# <- :70MAAAAAC INVITE 0ALAAAAAA #blah 0
|
# <- :70MAAAAAC INVITE 0ALAAAAAA #blah 0
|
||||||
|
@ -578,9 +578,9 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
log.debug('Applying modes %s for %s', parsedmodes, uid)
|
log.debug('Applying modes %s for %s', parsedmodes, uid)
|
||||||
utils.applyModes(self.irc, uid, parsedmodes)
|
utils.applyModes(self.irc, uid, parsedmodes)
|
||||||
self.irc.servers[numeric].users.add(uid)
|
self.irc.servers[numeric].users.add(uid)
|
||||||
# Call the OPERED UP hook if +o is in the mode list.
|
# Call the OPERED UP hook if +o is being added to the mode list.
|
||||||
if ('o', None) in parsedmodes:
|
if ('+o', None) in parsedmodes:
|
||||||
otype = 'Server_Administrator' if ('a', None) in parsedmodes else 'IRC_Operator'
|
otype = 'Server_Administrator' if ('+a', None) in parsedmodes else 'IRC_Operator'
|
||||||
self.irc.callHooks([uid, 'PYLINK_CLIENT_OPERED', {'text': otype}])
|
self.irc.callHooks([uid, 'PYLINK_CLIENT_OPERED', {'text': otype}])
|
||||||
return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host, 'ident': ident, 'ip': ip}
|
return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host, 'ident': ident, 'ip': ip}
|
||||||
|
|
||||||
|
5
utils.py
5
utils.py
@ -104,15 +104,18 @@ class TS6SIDGenerator():
|
|||||||
return sid
|
return sid
|
||||||
|
|
||||||
def add_cmd(func, name=None):
|
def add_cmd(func, name=None):
|
||||||
|
"""Binds a command to the given command name."""
|
||||||
if name is None:
|
if name is None:
|
||||||
name = func.__name__
|
name = func.__name__
|
||||||
name = name.lower()
|
name = name.lower()
|
||||||
world.commands[name].append(func)
|
world.commands[name].append(func)
|
||||||
|
return func
|
||||||
|
|
||||||
def add_hook(func, command):
|
def add_hook(func, command):
|
||||||
"""Add a hook <func> for command <command>."""
|
"""Binds a hook function to the given command."""
|
||||||
command = command.upper()
|
command = command.upper()
|
||||||
world.hooks[command].append(func)
|
world.hooks[command].append(func)
|
||||||
|
return func
|
||||||
|
|
||||||
def toLower(irc, text):
|
def toLower(irc, text):
|
||||||
"""Returns a lowercase representation of text based on the IRC object's
|
"""Returns a lowercase representation of text based on the IRC object's
|
||||||
|
Loading…
x
Reference in New Issue
Block a user