Added a parser function decorator which allows module functions to be registered as a PRIVMSG callback. Added !botlist and !rollcall for tildeverse's RFC on standardized bot behavior.

This commit is contained in:
Josh K 2018-09-12 22:16:03 -04:00
parent 84f11b9ddf
commit 8ae7a6970c
3 changed files with 51 additions and 14 deletions

53
bot.py
View File

@ -13,12 +13,26 @@ from protocol import IrcProtocol
import modules
# global module map, maps registered commands for each loaded module
# global module map, maps registered commands and parsers for each loaded module
# persists across reloads
try: _MOD_MAP
except NameError:
_MOD_MAP = {}
def add_module( name ):
""" adds a new named empty module definition to the global module map
'fixes' passed module name by removing top-level package name
returns newly created empty module object """
mod_name = name[name.find( '.' ) + 1: ]
if mod_name not in _MOD_MAP.keys():
logging.info( 'Registering \'%s\' module...', # \'%s\' (%s)...',
mod_name )
_MOD_MAP[mod_name] = {'name':mod_name,'cmds':{},'parsers':[]}
return _MOD_MAP[mod_name]
def command( name, admin=False ):
"""
Command registering decorator.
@ -29,27 +43,32 @@ def command( name, admin=False ):
containing the nick that sent, channel it was sent in, and reference to connection object.
"""
def __deco__( func ):
# "fixed" module name. removes top-level 'module' package name
mod_name = func.__module__[func.__module__.find( '.' ) + 1: ]
if mod_name not in _MOD_MAP.keys():
logging.info( 'Registering \'%s\' module commands...', # \'%s\' (%s)...',
mod_name ) #, name, func.__name__ )
mod = add_module( func.__module__ )
# set admin flag if needed
if admin: func.__dict__['admin'] = True
if not func.__doc__: func.__doc__ = 'No help available for command'
func.__doc__ += ' {}(Module: {})'.format( admin and '(Admin-Only) ' or '', mod_name )
func.__doc__ += ' {}(Module: {})'.format( admin and '(Admin-Only) ' or '', mod['name'] )
if mod_name not in _MOD_MAP.keys():
_MOD_MAP[mod_name] = {'cmds':{}}
_MOD_MAP[mod_name]['cmds'][name.lower()] = func
mod['cmds'][name.lower()] = func
return func #__inner__ # func
return __deco__
def parser():
"""
message parser decorator.
decorated function gets passed con, dst, nick, msg on each privmsg
"""
def __deco__( func ):
mod = add_module( func.__module__ )
mod['parsers'].append( func )
return func
return __deco__
class Bot:
""" Bot class """
def __init__( self, loop, cfg ):
@ -94,6 +113,13 @@ class Bot:
self, parms, {'con':con,'dst':dst,'nick':nick} ) )
break
# callback registered parsers, not for self messages though
if nick != self.cfg['user']['nick']:
# call each registered parser
for v in _MOD_MAP.values():
for p in v['parsers']:
p( con, dst, nick, msg )
# core commands
# join / part
@ -127,6 +153,7 @@ async def cmd_help( b, p, c ):
if not p:
con.say_to( dst, 'No command specified. See list of commands with \'list\'.' )
return
helpstr = ''
@ -162,7 +189,7 @@ async def cmd_list( b, p, c ):
async def cmd_modules( b, p, c ):
""" List all currently loaded modules. List commands within using 'list <module>' """
cn = c['con']
modlist = [m for m in _MOD_MAP.keys()]
modlist = list(_MOD_MAP.keys())
cn.say_to( c['dst'], '> {}'.format( modlist ) )
# list active connections
@ -206,7 +233,7 @@ async def cmd_connect( b, p, c ):
new_sv_cfg = {'host':p[1],'port':6697,'name':p[0]}
ncon = IrcProtocol( b._loop, con.cfg['usr'] )
# add message callback
ncon.add_message_callback( b._on_irc_message )
ncon.add_message_callback( b._on_priv_message )
b._con.append( ncon )
b._loop.create_task( ncon.do_connect( new_sv_cfg ) )
con.say_to( c['dst'], 'OMW...' )

View File

@ -10,7 +10,7 @@ import gzip
import requests
from bot import command
from bot import command, parser
# public python GAE api url. thanks sopel
PY_APP_URL = 'https://tumbolia-sopel.appspot.com/py/'
@ -42,6 +42,15 @@ async def cmd_eval( b, p, c ):
c['con'].say_to( c['dst'], '> {}'.format( rstr ) )
# parser test
@parser()
def parse_misc( c, d, n, m ):
if m.split()[0].lower() == '!botlist' or m.split()[0].lower() == '!rollcall':
pfx = ';' # HAX
c.say_to( d, ('> dustbot | Owner: {} | '
'Source: https://tildegit.org/slipyx/dustbot | Prefix: \'{}\'. | '
'Commands: See \'{}list\'.').format( c.cfg['usr']['owner'],pfx,pfx ) )
@command( 'rfk' )
async def cmd_rfk( b, p, c ):
""" Try to find kitten! """

View File

@ -177,6 +177,7 @@ class IrcProtocol( asyncio.Protocol ):
# helper func for sending a privmsg to specified destination
def say_to( self, dst, msg ):
msg = msg.replace( '\n', ' ' ).replace( '\r', '' )
self.log( '{} >> {}'.format( dst, msg ) )
self.send( 'PRIVMSG {} :{}'.format( dst, msg ) )
# create connection task