v2.666-3
This commit is contained in:
parent
87c9849bc2
commit
2bd0228c63
@ -11,13 +11,13 @@ ssl_verify = CERT_NONE
|
||||
|
||||
includes =
|
||||
irc3.plugins.log
|
||||
irc3.plugins.logger
|
||||
irc3.plugins.logger
|
||||
plugins.command_plugin
|
||||
plugins.storage_plugin
|
||||
plugins.fifo_plugin
|
||||
plugins.sasl_custom_plugin
|
||||
plugins.net_hydra_plugin
|
||||
|
||||
|
||||
autojoins =
|
||||
${#}PalletTown
|
||||
|
||||
|
@ -1,528 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from irc3.compat import asyncio
|
||||
from irc3 import utils
|
||||
from collections import defaultdict
|
||||
import functools
|
||||
import venusian
|
||||
import fnmatch
|
||||
import logging
|
||||
import docopt
|
||||
import shlex
|
||||
import irc3
|
||||
import sys
|
||||
import re
|
||||
__doc__ = '''
|
||||
==========================================
|
||||
:mod:`irc3.plugins.command` Command plugin
|
||||
==========================================
|
||||
|
||||
Introduce a ``@command`` decorator
|
||||
|
||||
The decorator use `docopts <http://docopt.org/>`_ to parse command arguments.
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
Create a python module with some commands:
|
||||
|
||||
.. literalinclude:: ../../examples/mycommands.py
|
||||
|
||||
..
|
||||
>>> import sys
|
||||
>>> sys.path.append('examples')
|
||||
>>> from irc3.testing import IrcBot
|
||||
>>> from irc3.testing import ini2config
|
||||
|
||||
And register it::
|
||||
|
||||
>>> bot = IrcBot()
|
||||
>>> bot.include('irc3.plugins.command') # register the plugin
|
||||
>>> bot.include('mycommands') # register your commands
|
||||
|
||||
|
||||
Check the result::
|
||||
|
||||
>>> bot.test(':gawel!user@host PRIVMSG #chan :!echo foo')
|
||||
PRIVMSG #chan :foo
|
||||
|
||||
In the docstring, ``%%`` is replaced by the command character. ``!`` by
|
||||
default. You can override it by passing a ``cmd`` parameter to bot's config.
|
||||
|
||||
When a command is not public, you can't use it on a channel::
|
||||
|
||||
>>> bot.test(':gawel!user@host PRIVMSG #chan :!adduser foo pass')
|
||||
PRIVMSG gawel :You can only use the 'adduser' command in private.
|
||||
|
||||
If a command is tagged with ``show_in_help_list=False``, it won't be shown
|
||||
on the result of ``!help``.
|
||||
|
||||
>>> bot.test(':gawel!user@host PRIVMSG #chan :!help')
|
||||
PRIVMSG #chan :Available commands: !adduser, !echo, !help
|
||||
|
||||
View extra info about a command by specifying it to ``!help``.
|
||||
|
||||
>>> bot.test(':gawel!user@host PRIVMSG #chan :!help echo')
|
||||
PRIVMSG #chan :Echo command
|
||||
PRIVMSG #chan :!echo <words>...
|
||||
>>> bot.test(':gawel!user@host PRIVMSG #chan :!help nonexistant')
|
||||
PRIVMSG #chan :No such command. Try !help for an overview of all commands.
|
||||
|
||||
Guard
|
||||
=====
|
||||
|
||||
You can use a guard to prevent untrusted users to run some commands. The
|
||||
:class:`free_policy` is used by default.
|
||||
|
||||
There is two builtin policy:
|
||||
|
||||
.. autoclass:: free_policy
|
||||
|
||||
|
||||
.. autoclass:: mask_based_policy
|
||||
|
||||
Mask based guard using permissions::
|
||||
|
||||
>>> config = ini2config("""
|
||||
... [bot]
|
||||
... nick = nono
|
||||
... includes =
|
||||
... irc3.plugins.command
|
||||
... mycommands
|
||||
... [irc3.plugins.command]
|
||||
... guard = irc3.plugins.command.mask_based_policy
|
||||
... [irc3.plugins.command.masks]
|
||||
... gawel!*@* = all_permissions
|
||||
... foo!*@* = help
|
||||
... """)
|
||||
>>> bot = IrcBot(**config)
|
||||
|
||||
foo is allowed to use command without permissions::
|
||||
|
||||
>>> bot.test(':foo!u@h PRIVMSG nono :!echo got the power')
|
||||
PRIVMSG foo :got the power
|
||||
|
||||
foo is not allowed to use command except those with the help permission::
|
||||
|
||||
>>> bot.test(':foo!u@h PRIVMSG nono :!ping')
|
||||
PRIVMSG foo :You are not allowed to use the 'ping' command
|
||||
|
||||
gawel is allowed::
|
||||
|
||||
>>> bot.test(':gawel!u@h PRIVMSG nono :!ping')
|
||||
NOTICE gawel :PONG gawel!
|
||||
|
||||
Async commands
|
||||
==============
|
||||
|
||||
Commands can be coroutines:
|
||||
|
||||
.. literalinclude:: ../../examples/async_command.py
|
||||
:language: py
|
||||
|
||||
Available options
|
||||
=================
|
||||
|
||||
The plugin accept the folowing options:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[irc3.plugins.command]
|
||||
cmd = !
|
||||
use_shlex = true
|
||||
antiflood = true
|
||||
casesensitive = true
|
||||
guard = irc3.plugins.command.mask_based_policy
|
||||
|
||||
|
||||
Command arguments
|
||||
=================
|
||||
|
||||
The :func:`command` decorator accept the folowing arguments:
|
||||
|
||||
**name**: if set, use this name as the command name instead of the function
|
||||
name.
|
||||
|
||||
**permission**: if set, this permission will be required to run the command.
|
||||
See Guard section
|
||||
|
||||
**use_shlex**: if `False`, do not use `shlex` to parse command line.
|
||||
|
||||
**options_first**: if `True` use docopt's options_first options. Allow to have
|
||||
args that starts with `-` as arguments.
|
||||
|
||||
**error_format**: allow to customize error messages. must be a callable that
|
||||
accept keyword arguments `cmd`, `args` and `exc`.
|
||||
For example, `error_format="Error for {cmd}".format` will work.
|
||||
|
||||
**quiet**: if `True` don't show errors
|
||||
|
||||
**aliases**: this argument, when present, should be a list of strings. All
|
||||
those strings will become alternative command names (i.e. aliases).
|
||||
For example, command 'mycmd' with aliases=['theircmd', 'noonescmd'] could
|
||||
be called via all three names.
|
||||
|
||||
|
||||
'''
|
||||
|
||||
|
||||
class free_policy:
|
||||
"""Default policy"""
|
||||
def __init__(self, bot):
|
||||
self.context = bot
|
||||
|
||||
def __call__(self, predicates, meth, client, target, args, **kwargs):
|
||||
return meth(client, target, args)
|
||||
|
||||
|
||||
class mask_based_policy:
|
||||
"""Allow only valid masks. Able to take care or permissions"""
|
||||
|
||||
key = __name__ + '.masks'
|
||||
|
||||
def __init__(self, bot):
|
||||
self.context = bot
|
||||
self.log = logging.getLogger(__name__)
|
||||
self.log.debug('Masks: %r', self.masks)
|
||||
|
||||
@property
|
||||
def masks(self):
|
||||
masks = self.context.config[self.key]
|
||||
if hasattr(self.context, 'db'):
|
||||
# update config with storage values
|
||||
try:
|
||||
value = self.context.db[self]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
if isinstance(value, dict):
|
||||
masks.update(value)
|
||||
return masks
|
||||
|
||||
def has_permission(self, mask, permission):
|
||||
if permission is None:
|
||||
return True
|
||||
for pattern in self.masks:
|
||||
if fnmatch.fnmatch(mask, pattern):
|
||||
if not isinstance(self.masks, dict):
|
||||
return True
|
||||
perms = self.masks[pattern]
|
||||
if permission in perms or 'all_permissions' in perms:
|
||||
return True
|
||||
return False
|
||||
|
||||
def __call__(self, predicates, meth, client, target, args, **kwargs):
|
||||
if self.has_permission(client, predicates.get('permission')):
|
||||
return meth(client, target, args)
|
||||
cmd_name = predicates.get('name', meth.__name__)
|
||||
self.context.privmsg(
|
||||
client.nick,
|
||||
'You are not allowed to use the %r command' % cmd_name)
|
||||
|
||||
|
||||
def attach_command(func, depth=2, **predicates):
|
||||
commands = predicates.pop('commands',
|
||||
'irc3.plugins.command.Commands')
|
||||
category = predicates.pop('venusian_category',
|
||||
'irc3.plugins.command')
|
||||
|
||||
def callback(context, name, ob):
|
||||
obj = context.context
|
||||
if info.scope == 'class':
|
||||
callback = func.__get__(obj.get_plugin(ob), ob)
|
||||
else:
|
||||
callback = utils.wraps_with_context(func, obj)
|
||||
plugin = obj.get_plugin(utils.maybedotted(commands))
|
||||
predicates.update(module=func.__module__)
|
||||
cmd_name = predicates.get('name', func.__name__)
|
||||
if not plugin.case_sensitive:
|
||||
cmd_name = cmd_name.lower()
|
||||
plugin[cmd_name] = (predicates, callback)
|
||||
aliases = predicates.get('aliases', None)
|
||||
if aliases is not None:
|
||||
for alias in aliases:
|
||||
plugin.aliases[alias] = cmd_name
|
||||
obj.log.debug('Register command %r %r', cmd_name, aliases)
|
||||
else:
|
||||
obj.log.debug('Register command %r', cmd_name)
|
||||
info = venusian.attach(func, callback,
|
||||
category=category, depth=depth)
|
||||
|
||||
|
||||
def command(*func, **predicates):
|
||||
if func:
|
||||
func = func[0]
|
||||
attach_command(func, **predicates)
|
||||
return func
|
||||
else:
|
||||
def wrapper(func):
|
||||
attach_command(func, **predicates)
|
||||
return func
|
||||
return wrapper
|
||||
|
||||
|
||||
@irc3.plugin
|
||||
class Commands(dict):
|
||||
|
||||
__reloadable__ = False
|
||||
|
||||
requires = [
|
||||
__name__.replace('command', 'core'),
|
||||
]
|
||||
default_policy = free_policy
|
||||
case_sensitive = False
|
||||
|
||||
def __init__(self, context):
|
||||
self.context = context
|
||||
module = self.__class__.__module__
|
||||
self.config = config = context.config.get(module, {})
|
||||
self.log = logging.getLogger(module)
|
||||
self.log.debug('Config: %r', config)
|
||||
|
||||
if 'cmd' in context.config: # in case of
|
||||
config['cmd'] = context.config['cmd']
|
||||
context.config['cmd'] = self.cmd = config.get('cmd', '!')
|
||||
context.config['re_cmd'] = re.escape(self.cmd)
|
||||
|
||||
self.use_shlex = self.config.get('use_shlex', True)
|
||||
self.antiflood = self.config.get('antiflood', False)
|
||||
self.case_sensitive = self.config.get('casesensitive',
|
||||
self.case_sensitive)
|
||||
|
||||
guard = utils.maybedotted(config.get('guard', self.default_policy))
|
||||
self.log.debug('Guard: %s', guard.__name__)
|
||||
self.guard = guard(context)
|
||||
|
||||
self.error_format = utils.maybedotted(config.get('error_format',
|
||||
"Invalid arguments.".format))
|
||||
self.handles = defaultdict(Done)
|
||||
self.tasks = defaultdict(Done)
|
||||
|
||||
self.aliases = {}
|
||||
|
||||
def split_command(self, data, use_shlex=None):
|
||||
if not data:
|
||||
return []
|
||||
return shlex.split(data) if use_shlex else data.split(' ')
|
||||
|
||||
@irc3.event((r'(@(?P<tags>\S+) )?:(?P<mask>\S+) PRIVMSG (?P<target>\S+) '
|
||||
r':{re_cmd}(?P<cmd>[\w-]+)(\s+(?P<data>\S.*)|(\s*$))'))
|
||||
def on_command(self, cmd, mask=None, target=None, client=None, **kw):
|
||||
if not self.case_sensitive:
|
||||
cmd = cmd.lower()
|
||||
cmd = self.aliases.get(cmd, cmd)
|
||||
predicates, meth = self.get(cmd, (None, None))
|
||||
if meth is not None:
|
||||
if predicates.get('public', True) is False and target.is_channel:
|
||||
self.context.privmsg(
|
||||
mask.nick,
|
||||
'You can only use the %r command in private.' % str(cmd))
|
||||
else:
|
||||
return self.do_command(predicates, meth, mask, target, **kw)
|
||||
|
||||
def do_command(self, predicates, meth, client, target, data=None, **kw):
|
||||
nick = self.context.nick or '-'
|
||||
to = client.nick if target == nick else target
|
||||
doc = meth.__doc__ or ''
|
||||
doc = [line.strip() for line in doc.strip().split('\n')]
|
||||
doc = [nick + ' ' + line.strip('%%')
|
||||
for line in doc if line.startswith('%%')]
|
||||
doc = 'Usage:' + '\n ' + '\n '.join(doc)
|
||||
if data:
|
||||
if not isinstance(data, str): # pragma: no cover
|
||||
encoding = self.context.encoding
|
||||
data = data.encode(encoding)
|
||||
try:
|
||||
data = self.split_command(
|
||||
data, use_shlex=predicates.get('use_shlex', self.use_shlex))
|
||||
except ValueError as e:
|
||||
if not predicates.get('quiet', False):
|
||||
self.context.privmsg(to, 'Invalid arguments: {}.'.format(e))
|
||||
return
|
||||
docopt_args = dict(help=False)
|
||||
if "options_first" in predicates:
|
||||
docopt_args.update(options_first=predicates["options_first"])
|
||||
cmd_name = predicates.get('name', meth.__name__)
|
||||
try:
|
||||
args = docopt.docopt(doc, [cmd_name] + data, **docopt_args)
|
||||
except docopt.DocoptExit as exc:
|
||||
if not predicates.get('quiet', False):
|
||||
args = {'cmd': cmd_name, 'args': data,
|
||||
'args_str': " ".join(data), 'exc': exc}
|
||||
error_format = predicates.get('error_format',
|
||||
self.error_format)
|
||||
self.context.privmsg(to, error_format(**args))
|
||||
else:
|
||||
uid = (cmd_name, to)
|
||||
use_client = isinstance(client, asyncio.Protocol)
|
||||
if not self.tasks[uid].done():
|
||||
self.context.notice(
|
||||
client if use_client else client.nick,
|
||||
"Another task is already running. "
|
||||
"Please be patient and don't flood me", nowait=True)
|
||||
elif not self.handles[uid].done() and self.antiflood:
|
||||
self.context.notice(
|
||||
client if use_client else client.nick,
|
||||
"Please be patient and don't flood me", nowait=True)
|
||||
else:
|
||||
# get command result
|
||||
res = self.guard(predicates, meth, client, target, args=args)
|
||||
|
||||
callback = functools.partial(self.command_callback, uid, to)
|
||||
if res is not None:
|
||||
coros = (
|
||||
asyncio.iscoroutinefunction(meth),
|
||||
asyncio.iscoroutinefunction(self.guard.__call__)
|
||||
)
|
||||
if any(coros):
|
||||
task = asyncio.ensure_future(
|
||||
res, loop=self.context.loop)
|
||||
# use a callback if command is a coroutine
|
||||
task.add_done_callback(callback)
|
||||
self.tasks[uid] = task
|
||||
return task
|
||||
else:
|
||||
# no callback needed
|
||||
callback(res)
|
||||
|
||||
def command_callback(self, uid, to, msgs):
|
||||
if isinstance(msgs, asyncio.Future): # pragma: no cover
|
||||
msgs = msgs.result()
|
||||
if msgs is not None:
|
||||
def iterator(msgs):
|
||||
for msg in msgs:
|
||||
yield to, msg
|
||||
if isinstance(msgs, str):
|
||||
msgs = [msgs]
|
||||
handle = self.context.call_many('privmsg', iterator(msgs))
|
||||
if handle is not None:
|
||||
self.handles[uid] = handle
|
||||
|
||||
@command
|
||||
def help(self, mask, target, args):
|
||||
"""Show help
|
||||
|
||||
%%help [<cmd>]
|
||||
"""
|
||||
return "not today satan"
|
||||
if args['<cmd>']:
|
||||
args = args['<cmd>']
|
||||
# Strip out self.context.config.cmd from args so we can use
|
||||
# both !help !foo and !help foo
|
||||
if args.startswith(self.context.config.cmd):
|
||||
args = args[len(self.context.config.cmd):]
|
||||
args = self.aliases.get(args, args)
|
||||
predicates, meth = self.get(args, (None, None))
|
||||
if meth is not None:
|
||||
doc = meth.__doc__ or ''
|
||||
doc = [
|
||||
line.strip() for line in doc.split('\n')
|
||||
if line.strip()
|
||||
]
|
||||
buf = ''
|
||||
for line in doc:
|
||||
if '%%' not in line and buf is not None:
|
||||
buf += line + ' '
|
||||
else:
|
||||
if buf is not None:
|
||||
for b in utils.split_message(buf, 160):
|
||||
yield b
|
||||
buf = None
|
||||
line = line.replace('%%', self.context.config.cmd)
|
||||
yield line
|
||||
aliases = predicates.get('aliases', None)
|
||||
if aliases is not None:
|
||||
yield 'Aliases: {0}'.format(','.join(sorted(aliases)))
|
||||
else:
|
||||
yield ('No such command. Try %shelp for an '
|
||||
'overview of all commands.'
|
||||
% self.context.config.cmd)
|
||||
else:
|
||||
cmds = sorted((k for (k, (p, m)) in self.items()
|
||||
if p.get('show_in_help_list', True)))
|
||||
cmds_str = ', '.join([self.cmd + k for k in cmds])
|
||||
lines = utils.split_message(
|
||||
'Available commands: %s ' % cmds_str, 160)
|
||||
for line in lines:
|
||||
yield line
|
||||
url = self.config.get('url')
|
||||
if url:
|
||||
yield 'Full help is available at ' + url
|
||||
|
||||
def __repr__(self):
|
||||
return '<Commands %s>' % sorted([self.cmd + k for k in self.keys()])
|
||||
|
||||
|
||||
class Done:
|
||||
|
||||
def done(self):
|
||||
return True
|
||||
|
||||
|
||||
@command(permission='admin', show_in_help_list=False, public=False)
|
||||
def ping(bot, mask, target, args):
|
||||
"""ping/pong
|
||||
|
||||
%%ping
|
||||
"""
|
||||
bot.send('NOTICE %(nick)s :PONG %(nick)s!' % dict(nick=mask.nick))
|
||||
|
||||
|
||||
@command(venusian_category='irc3.debug', show_in_help_list=False)
|
||||
def quote(bot, mask, target, args):
|
||||
"""send quote to the server
|
||||
|
||||
%%quote <args>...
|
||||
"""
|
||||
msg = ' '.join(args['<args>'])
|
||||
bot.log.info('quote> %r', msg)
|
||||
bot.send(msg)
|
||||
|
||||
|
||||
@command(venusian_category='irc3.debug', show_in_help_list=False)
|
||||
def reconnect(bot, mask, target, args):
|
||||
"""force reconnect
|
||||
|
||||
%%reconnect
|
||||
"""
|
||||
plugin = bot.get_plugin(utils.maybedotted('irc3.plugins.core.Core'))
|
||||
bot.loop.call_soon(plugin.reconnect)
|
||||
|
||||
|
||||
@irc3.extend
|
||||
def print_help_page(bot, file=sys.stdout):
|
||||
"""print help page"""
|
||||
def p(text):
|
||||
print(text, file=file)
|
||||
plugin = bot.get_plugin(Commands)
|
||||
title = "Available Commands for {nick} at {host}".format(**bot.config)
|
||||
p("=" * len(title))
|
||||
p(title)
|
||||
p("=" * len(title))
|
||||
p('')
|
||||
p('.. contents::')
|
||||
p('')
|
||||
modules = {}
|
||||
for name, (predicates, callback) in plugin.items():
|
||||
commands = modules.setdefault(callback.__module__, [])
|
||||
commands.append((name, callback, predicates))
|
||||
|
||||
for module in sorted(modules):
|
||||
p(module)
|
||||
p('=' * len(module))
|
||||
p('')
|
||||
for name, callback, predicates in sorted(modules[module]):
|
||||
p(name)
|
||||
p('-' * len(name))
|
||||
p('')
|
||||
doc = callback.__doc__
|
||||
doc = doc.replace('%%', bot.config.cmd)
|
||||
for line in doc.split('\n'):
|
||||
line = line.strip()
|
||||
if line.startswith(bot.config.cmd):
|
||||
line = ' ``{}``'.format(line)
|
||||
p(line)
|
||||
if 'permission' in predicates:
|
||||
p('*Require {0[permission]} permission.*'.format(predicates))
|
||||
if predicates.get('public', True) is False:
|
||||
p('*Only available in private.*')
|
||||
p('')
|
@ -1,101 +0,0 @@
|
||||
# -*- coding: utf-8 -*- ############################################################### SOF
|
||||
import os
|
||||
import irc3
|
||||
from stat import S_ISFIFO
|
||||
###########################################################################################
|
||||
@irc3.plugin
|
||||
class Fifo:
|
||||
#######################################################################################
|
||||
BLOCK_SIZE = 1024
|
||||
MAX_BUFFER_SIZE = 800
|
||||
#######################################################################################
|
||||
def __init__(self, context):
|
||||
self.context = context
|
||||
self.config = self.context.config
|
||||
self.send_blank_line = self.config.get('send_blank_line', True)
|
||||
self.runpath = self.config.get('runpath', f'{os.getcwd()}/fifo')
|
||||
if not os.path.exists(self.runpath):
|
||||
os.makedirs(self.runpath)
|
||||
self.loop = self.context.loop
|
||||
self.fifos = {}
|
||||
self.buffers = {}
|
||||
self.create_fifo(None)
|
||||
#######################################################################################
|
||||
@classmethod
|
||||
def read_fd(cls, fd):
|
||||
while True:
|
||||
try:
|
||||
return os.read(fd, cls.BLOCK_SIZE)
|
||||
except InterruptedError:
|
||||
continue
|
||||
except BlockingIOError:
|
||||
return b""
|
||||
#######################################################################################
|
||||
def handle_line(self, line, channel):
|
||||
if not line:
|
||||
return
|
||||
line = line.decode("utf8")
|
||||
if not self.send_blank_line and not line.strip():
|
||||
return
|
||||
if channel is None:
|
||||
self.context.send_line(line)
|
||||
else:
|
||||
self.context.privmsg(channel, line)
|
||||
#######################################################################################
|
||||
def data_received(self, data, channel):
|
||||
if not data:
|
||||
return
|
||||
prev = self.buffers.get(channel, b"")
|
||||
lines = (prev + data).splitlines(True)
|
||||
last = lines[-1]
|
||||
for line in lines[:-1]:
|
||||
line = line.rstrip(b'\r\n')
|
||||
self.handle_line(line, channel)
|
||||
if last.endswith(b'\n'):
|
||||
line = last.rstrip(b'\r\n')
|
||||
self.handle_line(line, channel)
|
||||
self.buffers[channel] = b""
|
||||
return
|
||||
if len(last) > self.MAX_BUFFER_SIZE:
|
||||
self.handle_line(last, channel)
|
||||
self.buffers[channel] = b""
|
||||
else:
|
||||
self.buffers[channel] = last
|
||||
#######################################################################################
|
||||
def watch_fd(self, fd, channel):
|
||||
reading = True
|
||||
|
||||
while reading:
|
||||
data = self.read_fd(fd)
|
||||
reading = len(data) == self.BLOCK_SIZE
|
||||
self.data_received(data, channel)
|
||||
#######################################################################################
|
||||
def create_fifo(self, channel):
|
||||
if channel is None:
|
||||
path = os.path.join(self.runpath, ':raw')
|
||||
else:
|
||||
path = os.path.join(self.runpath, channel.strip('#&+'))
|
||||
try:
|
||||
mode = os.stat(path).st_mode
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
if not S_ISFIFO(mode):
|
||||
self.context.log.warn(
|
||||
'file %s created without mkfifo. remove it',
|
||||
path)
|
||||
os.remove(path)
|
||||
if not os.path.exists(path):
|
||||
os.mkfifo(path)
|
||||
fd = os.open(path, os.O_RDWR | os.O_NONBLOCK)
|
||||
self.loop.add_reader(fd, self.watch_fd, fd, channel)
|
||||
self.context.log.debug("%s's fifo is %s %r",
|
||||
channel or ':raw', path, fd)
|
||||
return fd
|
||||
#######################################################################################
|
||||
@irc3.event(irc3.rfc.JOIN)
|
||||
def join(self, mask=None, channel=None, **kwargs):
|
||||
if mask.nick == self.context.nick:
|
||||
if channel not in self.fifos:
|
||||
self.fifos[channel] = self.create_fifo(channel)
|
||||
####################################################################################### EOF
|
@ -1,157 +0,0 @@
|
||||
# -*- coding: utf-8 -*- ############################################################### SOF
|
||||
from irc3.plugins.command import command
|
||||
from irc3.plugins.cron import cron
|
||||
from irc3.plugins import core
|
||||
import irc3
|
||||
import os
|
||||
from random import randint as rint
|
||||
from random import shuffle
|
||||
from datetime import datetime
|
||||
###########################################################################################
|
||||
class dr1p:
|
||||
def __init__():
|
||||
dr1p.designation=""
|
||||
dr1p.enforcing=False
|
||||
dr1p.purpose=""
|
||||
dr1p.color=""
|
||||
dr1p.token=""
|
||||
dr1p.home=""
|
||||
###########################################################################################
|
||||
@irc3.plugin
|
||||
class Plugin:
|
||||
#######################################################################################
|
||||
def __init__(self,bot):
|
||||
self.bot=bot
|
||||
token=""
|
||||
dr1p.color=""
|
||||
try:
|
||||
dr1p.purpose=os.environ['HYDRA_PURPOSE']
|
||||
dr1p.designation=os.environ['HYDRA_DESIGNATION']
|
||||
dr1p.home=os.environ['HYDRA_HOME']
|
||||
dr1p.enforcing=False
|
||||
except:
|
||||
dr1p.designation="dupe"
|
||||
dr1p.enforcing=False
|
||||
return
|
||||
if dr1p.designation=="core": dr1p.color="\x0304"
|
||||
for i in range(7): token+=hex(rint(0,255))[2:].zfill(2).upper()
|
||||
token+=hex(int(datetime.now().timestamp()))[-4:].upper()
|
||||
token=list(token)
|
||||
shuffle(token)
|
||||
dr1p.token=''.join(token)
|
||||
#######################################################################################
|
||||
def server_ready(self):
|
||||
if not dr1p.designation=='core':
|
||||
self.bot.privmsg("maple",f"[hydra:{dr1p.token}] - dupe - connected")
|
||||
else:
|
||||
self.bot.privmsg("maple",f"core - connected")
|
||||
#######################################################################################
|
||||
@irc3.event(irc3.rfc.ERR_NICK)
|
||||
def on_errnick(self,srv=None,retcode=None,me=None,nick=None,data=None):
|
||||
###################################################################################
|
||||
if not dr1p.designation=='core': return
|
||||
msg=f'err_nick - srv:{srv} - retcode:{retcode} - me:{me} - nick:{nick} - data:{data}'
|
||||
self.bot.privmsg("maple",msg.lower())
|
||||
#######################################################################################
|
||||
@irc3.event(irc3.rfc.NEW_NICK)
|
||||
def on_newnick(self,nick=None,new_nick=None):
|
||||
###################################################################################
|
||||
if not dr1p.designation=='core': return
|
||||
if nick==self.bot.config['nick'] or new_nick==self.bot.config['nick']:
|
||||
msg=f'new_nick - nick:{nick} - new_nick:{new_nick}'
|
||||
self.bot.privmsg("maple",msg.lower())
|
||||
#######################################################################################
|
||||
@irc3.event(irc3.rfc.CTCP)
|
||||
def on_ctcp(self,mask=None,event=None,target=None,ctcp=None):
|
||||
###################################################################################
|
||||
if not dr1p.designation=='core': return
|
||||
msg=f'ctcpd - mask:{mask} - event:{event} - target:{target} - ctcp:{ctcp}'
|
||||
self.bot.privmsg("maple",msg.lower())
|
||||
#######################################################################################
|
||||
@irc3.event(irc3.rfc.INVITE)
|
||||
def on_invite(self,mask=None,channel=None):
|
||||
###################################################################################
|
||||
if not dr1p.designation=='core': return
|
||||
msg=f'invited - mask:{mask} - channel:{channel}'
|
||||
self.bot.privmsg("maple",msg.lower())
|
||||
#######################################################################################
|
||||
@irc3.event(irc3.rfc.KICK)
|
||||
def on_kick(self,mask=None,event=None,channel=None,target=None,data=None):
|
||||
###################################################################################
|
||||
if not dr1p.designation=='core': return
|
||||
msg=f'kicked - mask:{mask} - event:{event} - target:{target} - data:{data}'
|
||||
self.bot.privmsg("maple",msg)
|
||||
#######################################################################################
|
||||
@irc3.event(irc3.rfc.PRIVMSG)
|
||||
def on_privmsg(self,mask=None,event=None,target=None,data=None,**kw):
|
||||
###################################################################################
|
||||
# if dr1p.enforcing==False: return
|
||||
if target!=self.bot.config['nick'] and mask.nick==self.bot.nick: return
|
||||
if mask.nick==self.bot.nick and target==self.bot.config['nick'] and dr1p.designation=='core':
|
||||
if data.endswith('dupe - connected'):
|
||||
_token=data.split("[hydra:")[1].split("]")[0]
|
||||
_nekot=_token[::-1]
|
||||
msg=f'[TOKEN:{_token}] - [NEKOT:{_nekot}] - COLOR:{rint(16,87)}'
|
||||
self.bot.privmsg(self.bot.config['nick'],msg)
|
||||
if mask.nick==self.bot.nick and target==self.bot.config['nick'] and dr1p.designation=='dupe':
|
||||
if not data.find('NEKOT')==-1:
|
||||
_token=data.split(":")[1].split("]")[0]
|
||||
if _token.lower()==dr1p.token.lower():
|
||||
if not data.find("] - [NEKOT:")==-1:
|
||||
_nekot=data.split("] - [NEKOT:")[1].split("]")[0]
|
||||
if _token.lower()==_nekot[::-1].lower():
|
||||
_color=int(data.split(" - COLOR:")[1].strip())
|
||||
if not dr1p.color:
|
||||
dr1p.color=f"\x03{str(_color)}"
|
||||
if dr1p.designation=='core':
|
||||
msg=f"{dr1p.color}[maple:{dr1p.token}] - "
|
||||
else:
|
||||
try:
|
||||
msg=f"{dr1p.color}[hydra:{dr1p.token}] - "
|
||||
except:
|
||||
dr1p.color="\x0303"
|
||||
msg=f"{dr1p.color}[hydra:{dr1p.token}] - "
|
||||
if mask.nick!=self.bot.config['nick']:
|
||||
if target!=dr1p.home: return
|
||||
msg+=f'event:{event} - mask:{mask} - target:{target} - data:'
|
||||
msg+=f'{data}'
|
||||
if kw: msg+=f" - kw:{kw}"
|
||||
self.bot.privmsg(dr1p.home,msg.lower())
|
||||
#######################################################################################
|
||||
@irc3.event(irc3.rfc.MY_PRIVMSG)
|
||||
def on_my_privmsg(self,mask=None,event=None,target=None,data=None,**kw):
|
||||
###################################################################################
|
||||
pass
|
||||
#######################################################################################
|
||||
@irc3.event(irc3.rfc.JOIN_PART_QUIT)
|
||||
def on_join_part_quit(self,mask=None,target=None,data=None,**kw):
|
||||
target=kw['channel']
|
||||
###################################################################################
|
||||
if mask.nick==self.bot.config['nick']:
|
||||
###############################################################################
|
||||
if kw['event']=='JOIN':
|
||||
self.bot.privmsg("maple",f"joined {target}".lower())
|
||||
if target!=dr1p.home:
|
||||
if dr1p.enforcing:
|
||||
reason=".[d]."
|
||||
self.bot.part(target,reason)
|
||||
self.bot.privmsg("maple",f"parted {target} - {reason}".lower())
|
||||
if dr1p.designation=="core":
|
||||
msg=f"[maple:{dr1p.token}] - core - maple online - purpose: {dr1p.purpose}"
|
||||
self.bot.privmsg(dr1p.home,msg)
|
||||
else:
|
||||
msg=f"[hydra:{dr1p.token}] - dupe - hydra online - purpose: {dr1p.purpose}"
|
||||
self.bot.privmsg(dr1p.home,msg)
|
||||
if kw['event']=='PART':
|
||||
if dr1p.designation=="core":
|
||||
msg=f"[maple:{dr1p.token}] -"
|
||||
else:
|
||||
msg=f"[hydra:{dr1p.token}] -"
|
||||
self.bot.privmsg("maple",msg+f"parted {target} - {data}")
|
||||
if kw['event']=='QUIT':
|
||||
if dr1p.designation=="core":
|
||||
msg=f"[maple:{dr1p.token}] -"
|
||||
else:
|
||||
msg=f"[hydra:{dr1p.token}] -"
|
||||
self.bot.privmsg("maple",msg+f"quit {target} - {data}")
|
||||
####################################################################################### EOF
|
@ -1,48 +0,0 @@
|
||||
# -*- coding: utf-8 -*- ########################################################## SOF
|
||||
import irc3, os, base64
|
||||
######################################################################################
|
||||
BOT_SASL_USERNAME=os.environ['BOT_SASL_USERNAME']
|
||||
BOT_SASL_PASSWORD=os.environ['BOT_SASL_PASSWORD']
|
||||
######################################################################################
|
||||
@irc3.plugin
|
||||
class DR1PSASL:
|
||||
##################################################################################
|
||||
def __init__(self, bot):
|
||||
print('<<< _sasl_custom_plugin >>> [ custom sasl initiated ]')
|
||||
self.bot=bot
|
||||
self.auth=(f'{BOT_SASL_USERNAME}\0{BOT_SASL_USERNAME}\0{BOT_SASL_PASSWORD}')
|
||||
self.auth=base64.encodebytes(self.auth.encode('utf8'))
|
||||
self.auth=self.auth.decode('utf8').rstrip('\n')
|
||||
self.events = [
|
||||
irc3.event(r'^:\S+ CAP \S+ LS :(?P<data>.*)', self.cap_ls),
|
||||
irc3.event(r'^:\S+ CAP \S+ ACK sasl', self.cap_ack),
|
||||
irc3.event(r'AUTHENTICATE +', self.authenticate),
|
||||
irc3.event(r'^:\S+ 903 \S+ :Authentication successful',self.cap_end),
|
||||
]
|
||||
##################################################################################
|
||||
def connection_ready(self, *args, **kwargs):
|
||||
print('<<< _sasl_custom_plugin >>> [ CAP LS ]')
|
||||
self.bot.send('CAP LS\r\n')
|
||||
self.bot.attach_events(*self.events)
|
||||
##################################################################################
|
||||
def cap_ls(self, data=None, **kwargs):
|
||||
print('<<< _sasl_custom_plugin >>> [ CAP REQ :sasl ]')
|
||||
if 'sasl' in data.lower():
|
||||
self.bot.send_line('CAP REQ :sasl')
|
||||
else:
|
||||
self.cap_end()
|
||||
##################################################################################
|
||||
def cap_ack(self, **kwargs):
|
||||
print('<<< _sasl_custom_plugin >>> [ AUTHENTICATE PLAIN ]')
|
||||
self.bot.send_line('AUTHENTICATE PLAIN')
|
||||
##################################################################################
|
||||
def authenticate(self, **kwargs):
|
||||
print(f'<<< _sasl_custom_plugin >>> [ AUTHENTICATE {self.auth} ]')
|
||||
self.bot.send_line(f'AUTHENTICATE {self.auth}\n')
|
||||
##################################################################################
|
||||
def cap_end(self, **kwargs):
|
||||
print('<<< _sasl_custom_plugin >>> [ CAP END ]')
|
||||
self.bot.send_line('CAP END\r\n')
|
||||
self.bot.detach_events(*self.events)
|
||||
##################################################################################
|
||||
################################################################################## EOF
|
@ -1,293 +0,0 @@
|
||||
# -*- coding: utf-8 -*- ############################################################### SOF
|
||||
import os
|
||||
try:
|
||||
import ujson as json
|
||||
except ImportError:
|
||||
import json
|
||||
import irc3
|
||||
import shelve
|
||||
###########################################################################################
|
||||
class Shelve:
|
||||
#######################################################################################
|
||||
def __init__(self, uri=None, **kwargs):
|
||||
self.filename = uri[9:]
|
||||
self.db = shelve.open(self.filename)
|
||||
#######################################################################################
|
||||
def set(self, key, value):
|
||||
self.db[key] = value
|
||||
self.db.sync()
|
||||
#######################################################################################
|
||||
def get(self, key):
|
||||
return self.db[key]
|
||||
#######################################################################################
|
||||
def delete(self, key):
|
||||
del self.db[key]
|
||||
self.sync()
|
||||
#######################################################################################
|
||||
def contains(self, key):
|
||||
return key in self.db
|
||||
#######################################################################################
|
||||
def sync(self):
|
||||
self.db.sync()
|
||||
#######################################################################################
|
||||
def close(self):
|
||||
self.db.close()
|
||||
###########################################################################################
|
||||
class JSON:
|
||||
def __init__(self, uri=None, **kwargs):
|
||||
self.filename = uri[7:]
|
||||
if os.path.isfile(self.filename): # pragma: no cover
|
||||
with open(self.filename) as fd:
|
||||
self.db = json.load(fd)
|
||||
else:
|
||||
self.db = {}
|
||||
#######################################################################################
|
||||
def set(self, key, value):
|
||||
self.db[key] = value
|
||||
self.sync()
|
||||
#######################################################################################
|
||||
def get(self, key):
|
||||
return self.db[key]
|
||||
#######################################################################################
|
||||
def delete(self, key):
|
||||
del self.db[key]
|
||||
self.sync()
|
||||
#######################################################################################
|
||||
def contains(self, key):
|
||||
return key in self.db
|
||||
#######################################################################################
|
||||
def sync(self):
|
||||
with open(self.filename, 'w') as fd:
|
||||
json.dump(self.db, fd, indent=2, sort_keys=True)
|
||||
#######################################################################################
|
||||
def close(self):
|
||||
self.sync()
|
||||
###########################################################################################
|
||||
class Redis:
|
||||
def __init__(self, uri=None, **kwargs):
|
||||
ConnectionPool = irc3.utils.maybedotted(
|
||||
'redis.connection.ConnectionPool')
|
||||
pool = ConnectionPool.from_url(uri)
|
||||
StrictRedis = irc3.utils.maybedotted('redis.client.StrictRedis')
|
||||
self.db = StrictRedis(connection_pool=pool)
|
||||
#######################################################################################
|
||||
def set(self, key, value):
|
||||
self.db.hmset(key, value)
|
||||
#######################################################################################
|
||||
def get(self, key):
|
||||
keys = self.db.hkeys(key)
|
||||
if not keys:
|
||||
raise KeyError()
|
||||
values = self.db.hmget(key, keys)
|
||||
keys = [k.decode('utf8') for k in keys]
|
||||
values = [v.decode('utf8') for v in values]
|
||||
values = dict(zip(keys, values))
|
||||
return values
|
||||
#######################################################################################
|
||||
def delete(self, key):
|
||||
self.db.delete(key)
|
||||
#######################################################################################
|
||||
def contains(self, key):
|
||||
return self.db.exists(key)
|
||||
#######################################################################################
|
||||
def flushdb(self):
|
||||
self.db.flushdb()
|
||||
#######################################################################################
|
||||
def sync(self):
|
||||
self.db.save()
|
||||
#######################################################################################
|
||||
def close(self):
|
||||
self.sync()
|
||||
###########################################################################################
|
||||
class SQLite:
|
||||
CREATE_TABLE = """
|
||||
CREATE TABLE IF NOT EXISTS
|
||||
irc3_storage (
|
||||
key text not null,
|
||||
value text default '',
|
||||
PRIMARY KEY (key)
|
||||
);
|
||||
"""
|
||||
UPSERT = """
|
||||
INSERT OR REPLACE INTO irc3_storage(key,value) VALUES(?, ?);
|
||||
"""
|
||||
#######################################################################################
|
||||
def __init__(self, uri=None, **kwargs):
|
||||
self.sqlite = irc3.utils.maybedotted('sqlite3')
|
||||
self.uri = uri.split('://')[-1]
|
||||
conn = self.sqlite.connect(self.uri)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(self.CREATE_TABLE)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
#######################################################################################
|
||||
def set(self, key, value):
|
||||
conn = self.sqlite.connect(self.uri)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(self.UPSERT, (key, json.dumps(value)))
|
||||
cursor.fetchall()
|
||||
conn.commit()
|
||||
conn.close()
|
||||
#######################################################################################
|
||||
def get(self, key):
|
||||
value = None
|
||||
conn = self.sqlite.connect(self.uri)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT value FROM irc3_storage where key=?;", (key,))
|
||||
for row in cursor.fetchall():
|
||||
value = json.loads(row[0])
|
||||
break
|
||||
cursor.close()
|
||||
conn.close()
|
||||
if value is None:
|
||||
raise KeyError(key)
|
||||
return value
|
||||
#######################################################################################
|
||||
def delete(self, key):
|
||||
conn = self.sqlite.connect(self.uri)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("DELETE FROM irc3_storage where key=?;", (key,))
|
||||
cursor.close()
|
||||
conn.commit()
|
||||
conn.close()
|
||||
#######################################################################################
|
||||
def contains(self, key):
|
||||
conn = self.sqlite.connect(self.uri)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT value FROM irc3_storage where key=?;", (key,))
|
||||
res = False
|
||||
if len(list(cursor.fetchall())) == 1:
|
||||
res = True
|
||||
cursor.close()
|
||||
conn.close()
|
||||
return res
|
||||
#######################################################################################
|
||||
def flushdb(self):
|
||||
conn = self.sqlite.connect(self.uri)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("DROP TABLE IF EXISTS irc3_storage;")
|
||||
cursor.execute(self.CREATE_TABLE)
|
||||
cursor.close()
|
||||
conn.commit()
|
||||
conn.close()
|
||||
#######################################################################################
|
||||
def sync(self):
|
||||
pass
|
||||
#######################################################################################
|
||||
def close(self):
|
||||
pass
|
||||
###########################################################################################
|
||||
@irc3.plugin
|
||||
class Storage:
|
||||
backends = {
|
||||
'shelve': Shelve,
|
||||
'json': JSON,
|
||||
'unix': Redis,
|
||||
'redis': Redis,
|
||||
'rediss': Redis,
|
||||
'sqlite': SQLite,
|
||||
}
|
||||
#######################################################################################
|
||||
def __init__(self, context):
|
||||
uri = context.config.storage
|
||||
name = uri.split('://', 1)[0]
|
||||
try:
|
||||
factory = self.backends[name]
|
||||
except KeyError: # pragma: no cover
|
||||
raise LookupError('No such backend %s' % name)
|
||||
self.backend = factory(uri)
|
||||
self.context = context
|
||||
self.context.db = self
|
||||
#######################################################################################
|
||||
def setdefault(self, key_, **kwargs):
|
||||
"""Update storage value for key with kwargs iif the keys doesn't
|
||||
exist. Return stored values"""
|
||||
stored = self[key_]
|
||||
changed = False
|
||||
for k, v in kwargs.items():
|
||||
if k not in stored:
|
||||
stored[k] = v
|
||||
changed = True
|
||||
else:
|
||||
kwargs[k] = stored[k]
|
||||
if changed:
|
||||
self[key_] = stored
|
||||
return kwargs
|
||||
#######################################################################################
|
||||
def get(self, key_, default=None):
|
||||
"""Get storage value for key or return default"""
|
||||
if key_ not in self:
|
||||
return default
|
||||
else:
|
||||
return self[key_]
|
||||
#######################################################################################
|
||||
def getlist(self, key_, default=None):
|
||||
"""Get storage value (as list) for key or return default"""
|
||||
if key_ not in self:
|
||||
return default
|
||||
else:
|
||||
value = self[key_]
|
||||
value = [(int(i), v) for i, v in value.items()]
|
||||
return [v for k, v in sorted(value)]
|
||||
#######################################################################################
|
||||
def set(self, key_, **kwargs):
|
||||
"""Update storage value for key with kwargs"""
|
||||
stored = self.get(key_, dict())
|
||||
changed = False
|
||||
for k, v in kwargs.items():
|
||||
if k not in stored or stored[k] != v:
|
||||
stored[k] = v
|
||||
changed = True
|
||||
if changed:
|
||||
self[key_] = stored
|
||||
#######################################################################################
|
||||
def setlist(self, key_, value):
|
||||
"""Update storage value (as list)"""
|
||||
value = dict([(str(i), v) for i, v in enumerate(value)])
|
||||
if key_ in self:
|
||||
del self[key_]
|
||||
self.set(key_, **value)
|
||||
#######################################################################################
|
||||
def __setitem__(self, key, value):
|
||||
"""Set storage value for key"""
|
||||
key = getattr(key, '__module__', key)
|
||||
if not isinstance(value, dict): # pragma: no cover
|
||||
raise TypeError('value must be a dict')
|
||||
try:
|
||||
return self.backend.set(key, value)
|
||||
except Exception as e: # pragma: no cover
|
||||
self.context.log.exception(e)
|
||||
raise
|
||||
#######################################################################################
|
||||
def __getitem__(self, key):
|
||||
"""Get storage value for key"""
|
||||
key = getattr(key, '__module__', key)
|
||||
try:
|
||||
return self.backend.get(key)
|
||||
except KeyError:
|
||||
raise KeyError(key)
|
||||
except Exception as e: # pragma: no cover
|
||||
self.context.log.exception(e)
|
||||
raise
|
||||
#######################################################################################
|
||||
def __delitem__(self, key):
|
||||
"""Delete key in storage"""
|
||||
key = getattr(key, '__module__', key)
|
||||
try:
|
||||
self.backend.delete(key)
|
||||
except Exception as e: # pragma: no cover
|
||||
self.context.log.exception(e)
|
||||
raise
|
||||
#######################################################################################
|
||||
def __contains__(self, key):
|
||||
"""Return True if storage contains key"""
|
||||
key = getattr(key, '__module__', key)
|
||||
try:
|
||||
return self.backend.contains(key)
|
||||
except Exception as e: # pragma: no cover
|
||||
self.context.log.exception(e)
|
||||
raise
|
||||
#######################################################################################
|
||||
def SIGINT(self):
|
||||
self.backend.close()
|
||||
####################################################################################### EOF
|
Loading…
Reference in New Issue
Block a user