This commit is contained in:
.[d]. 2022-08-27 06:58:02 -05:00
parent 87c9849bc2
commit 2bd0228c63
6 changed files with 2 additions and 1129 deletions

@ -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