This commit is contained in:
.[d]. 2022-08-27 06:58:33 -05:00
parent 2bd0228c63
commit 959c0d7def
6 changed files with 1253 additions and 0 deletions

@ -0,0 +1,528 @@
# -*- 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 stan"
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('')

@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
from irc3 import event
from irc3 import rfc
__doc__ = '''
==============================================
:mod:`irc3.plugins.core` Core plugin
==============================================
Core events
.. autoclass:: Core
:members:
..
>>> from irc3.testing import IrcBot
Usage::
>>> bot = IrcBot()
>>> bot.include('irc3.plugins.core')
'''
class Core:
def __init__(self, bot):
self.bot = bot
self.timeout = int(self.bot.config.get('timeout'))
self.max_lag = int(self.bot.config.get('max_lag'))
self.reconn_handle = None
self.ping_handle = None
self.nick_handle = None
self.before_connect_events = [
event(rfc.CONNECTED, self.connected),
event(r"^:\S+ 005 \S+ (?P<data>.+) :\S+.*",
self.set_config),
]
def connection_made(self, client=None):
# handle server config
config = self.bot.defaults['server_config'].copy()
self.bot.config['server_config'] = config
self.bot.detach_events(*self.before_connect_events)
self.bot.attach_events(insert=True, *self.before_connect_events)
# ping/ping
self.connection_made_at = self.bot.loop.time()
self.pong(event='CONNECT', data='')
def connected(self, **kwargs):
"""triger the server_ready event"""
self.bot.log.info('Server config: %r', self.bot.server_config)
# recompile when I'm sure of my nickname
self.bot.config['nick'] = kwargs['me']
self.bot.recompile()
# Let all plugins know that server can handle commands
self.bot.notify('server_ready')
# detach useless events
self.bot.detach_events(*self.before_connect_events)
def reconnect(self): # pragma: no cover
self.bot.log.info(
"We're waiting a ping for too long. Trying to reconnect...")
self.bot.loop.call_soon(
self.bot.protocol.connection_lost,
'No pong reply'
)
self.pong(event='RECONNECT', data='')
@event(rfc.PONG)
def pong(self, event='PONG', data='', **kw): # pragma: no cover
"""P0NG/PING"""
self.bot.log.debug('%s ping-pong (%s)', event, data)
if self.reconn_handle is not None:
self.reconn_handle.cancel()
self.reconn_handle = self.bot.loop.call_later(self.timeout,
self.reconnect)
if self.ping_handle is not None:
self.ping_handle.cancel()
self.ping_handle = self.bot.loop.call_later(
self.timeout - self.max_lag, self.bot.send,
'PING :%s' % int(self.bot.loop.time()))
@event(rfc.PING)
def ping(self, data):
"""PING reply"""
self.bot.send('PONG :' + data)
self.pong(event='PING', data=data)
@event(rfc.NEW_NICK)
def recompile(self, nick=None, new_nick=None, **kw):
"""recompile regexp on new nick"""
if self.bot.nick == nick.nick:
self.bot.config['nick'] = new_nick
self.bot.recompile()
@event(rfc.ERR_NICK)
def badnick(self, me=None, nick=None, **kw):
"""Use alt nick on nick error"""
if me == '*':
self.bot.set_nick(self.bot.nick + '_')
self.bot.log.debug('Trying to regain nickname in 30s...')
self.nick_handle = self.bot.loop.call_later(
30, self.bot.set_nick, self.bot.original_nick)
def set_config(self, data=None, **kwargs):
"""Store server config"""
config = self.bot.config['server_config']
for opt in data.split(' '):
if '=' in opt:
opt, value = opt.split('=', 1)
else:
value = True
if opt.isupper():
config[opt] = value

@ -0,0 +1,101 @@
# -*- 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

@ -0,0 +1,165 @@
# -*- coding: utf-8 -*- ############################################################### SOF
import irc3, os
from irc3.plugins.command import command
from irc3.plugins.cron import cron
from irc3.plugins import core
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.keyid=""
dr1p.token=""
dr1p.home=""
###########################################################################################
@irc3.plugin
class Plugin:
#######################################################################################
def __init__(self,bot):
self.bot=bot
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"
dr1p.keyid=self.hydra_id(1)
dr1p.token=self.hydra_id(0)
#######################################################################################
def hydra_id(self,mode=1):
hydra=""
for i in range(7): hydra+=hex(rint(0,255))[2:].zfill(2).upper()
hydra+=hex(int(datetime.now().timestamp()))[-4:].upper()
hydra=list(hydra)
shuffle(hydra)
if mode:
hydra=''.join(hydra)
else:
hydra=''.join(hydra)[6:14]
return hydra
#######################################################################################
def server_ready(self):
if not dr1p.designation=='core':
self.bot.privmsg("maple",f"[hydra:{dr1p.keyid}] - 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'):
_keyid=data.split("[hydra:")[1].split("]")[0]
_diyek=_keyid[::-1]
msg=f'[KEYID:{_keyid}] - [DIYEK:{_diyek}] - 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('DIYEK')==-1:
_keyid=data.split(":")[1].split("]")[0]
if _keyid.lower()==dr1p.keyid.lower():
if not data.find("] - [DIYEK:")==-1:
_diyek=data.split("] - [DIYEK:")[1].split("]")[0]
if _keyid.lower()==_diyek[::-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.keyid}] - "
else:
try:
msg=f"{dr1p.color}[hydra:{dr1p.keyid}] - "
except:
dr1p.color="\x0303"
msg=f"{dr1p.color}[hydra:{dr1p.keyid}] - "
if mask.nick!=self.bot.config['nick']:
if target!=dr1p.home: return
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.keyid}] - core - maple online - purpose: {dr1p.purpose}"
self.bot.privmsg(dr1p.home,msg)
else:
msg=f"[hydra:{dr1p.keyid}] - dupe - hydra online - purpose: {dr1p.purpose}"
self.bot.privmsg(dr1p.home,msg)
if kw['event']=='PART':
if dr1p.designation=="core":
msg=f"[maple:{dr1p.keyid}] -"
else:
msg=f"[hydra:{dr1p.keyid}] -"
self.bot.privmsg("maple",msg+f"parted {target} - {data}")
if kw['event']=='QUIT':
if dr1p.designation=="core":
msg=f"[maple:{dr1p.keyid}] -"
else:
msg=f"[hydra:{dr1p.keyid}] -"
self.bot.privmsg("maple",msg+f"quit {target} - {data}")
####################################################################################### EOF

@ -0,0 +1,48 @@
# -*- 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

@ -0,0 +1,293 @@
# -*- 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