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:
parent
84f11b9ddf
commit
8ae7a6970c
53
bot.py
53
bot.py
|
@ -13,12 +13,26 @@ from protocol import IrcProtocol
|
||||||
|
|
||||||
import modules
|
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
|
# persists across reloads
|
||||||
try: _MOD_MAP
|
try: _MOD_MAP
|
||||||
except NameError:
|
except NameError:
|
||||||
_MOD_MAP = {}
|
_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 ):
|
def command( name, admin=False ):
|
||||||
"""
|
"""
|
||||||
Command registering decorator.
|
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.
|
containing the nick that sent, channel it was sent in, and reference to connection object.
|
||||||
"""
|
"""
|
||||||
def __deco__( func ):
|
def __deco__( func ):
|
||||||
# "fixed" module name. removes top-level 'module' package name
|
mod = add_module( func.__module__ )
|
||||||
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__ )
|
|
||||||
|
|
||||||
# set admin flag if needed
|
# set admin flag if needed
|
||||||
if admin: func.__dict__['admin'] = True
|
if admin: func.__dict__['admin'] = True
|
||||||
|
|
||||||
if not func.__doc__: func.__doc__ = 'No help available for command'
|
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['cmds'][name.lower()] = func
|
||||||
_MOD_MAP[mod_name] = {'cmds':{}}
|
|
||||||
|
|
||||||
_MOD_MAP[mod_name]['cmds'][name.lower()] = func
|
|
||||||
|
|
||||||
return func #__inner__ # func
|
return func #__inner__ # func
|
||||||
|
|
||||||
return __deco__
|
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:
|
class Bot:
|
||||||
""" Bot class """
|
""" Bot class """
|
||||||
def __init__( self, loop, cfg ):
|
def __init__( self, loop, cfg ):
|
||||||
|
@ -94,6 +113,13 @@ class Bot:
|
||||||
self, parms, {'con':con,'dst':dst,'nick':nick} ) )
|
self, parms, {'con':con,'dst':dst,'nick':nick} ) )
|
||||||
break
|
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
|
# core commands
|
||||||
|
|
||||||
# join / part
|
# join / part
|
||||||
|
@ -127,6 +153,7 @@ async def cmd_help( b, p, c ):
|
||||||
|
|
||||||
if not p:
|
if not p:
|
||||||
con.say_to( dst, 'No command specified. See list of commands with \'list\'.' )
|
con.say_to( dst, 'No command specified. See list of commands with \'list\'.' )
|
||||||
|
return
|
||||||
|
|
||||||
helpstr = ''
|
helpstr = ''
|
||||||
|
|
||||||
|
@ -162,7 +189,7 @@ async def cmd_list( b, p, c ):
|
||||||
async def cmd_modules( b, p, c ):
|
async def cmd_modules( b, p, c ):
|
||||||
""" List all currently loaded modules. List commands within using 'list <module>' """
|
""" List all currently loaded modules. List commands within using 'list <module>' """
|
||||||
cn = c['con']
|
cn = c['con']
|
||||||
modlist = [m for m in _MOD_MAP.keys()]
|
modlist = list(_MOD_MAP.keys())
|
||||||
cn.say_to( c['dst'], '> {}'.format( modlist ) )
|
cn.say_to( c['dst'], '> {}'.format( modlist ) )
|
||||||
|
|
||||||
# list active connections
|
# 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]}
|
new_sv_cfg = {'host':p[1],'port':6697,'name':p[0]}
|
||||||
ncon = IrcProtocol( b._loop, con.cfg['usr'] )
|
ncon = IrcProtocol( b._loop, con.cfg['usr'] )
|
||||||
# add message callback
|
# add message callback
|
||||||
ncon.add_message_callback( b._on_irc_message )
|
ncon.add_message_callback( b._on_priv_message )
|
||||||
b._con.append( ncon )
|
b._con.append( ncon )
|
||||||
b._loop.create_task( ncon.do_connect( new_sv_cfg ) )
|
b._loop.create_task( ncon.do_connect( new_sv_cfg ) )
|
||||||
con.say_to( c['dst'], 'OMW...' )
|
con.say_to( c['dst'], 'OMW...' )
|
||||||
|
|
|
@ -10,7 +10,7 @@ import gzip
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from bot import command
|
from bot import command, parser
|
||||||
|
|
||||||
# public python GAE api url. thanks sopel
|
# public python GAE api url. thanks sopel
|
||||||
PY_APP_URL = 'https://tumbolia-sopel.appspot.com/py/'
|
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 ) )
|
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' )
|
@command( 'rfk' )
|
||||||
async def cmd_rfk( b, p, c ):
|
async def cmd_rfk( b, p, c ):
|
||||||
""" Try to find kitten! """
|
""" Try to find kitten! """
|
||||||
|
|
|
@ -177,6 +177,7 @@ class IrcProtocol( asyncio.Protocol ):
|
||||||
# helper func for sending a privmsg to specified destination
|
# helper func for sending a privmsg to specified destination
|
||||||
def say_to( self, dst, msg ):
|
def say_to( self, dst, msg ):
|
||||||
msg = msg.replace( '\n', ' ' ).replace( '\r', '' )
|
msg = msg.replace( '\n', ' ' ).replace( '\r', '' )
|
||||||
|
self.log( '{} >> {}'.format( dst, msg ) )
|
||||||
self.send( 'PRIVMSG {} :{}'.format( dst, msg ) )
|
self.send( 'PRIVMSG {} :{}'.format( dst, msg ) )
|
||||||
|
|
||||||
# create connection task
|
# create connection task
|
||||||
|
|
Loading…
Reference in New Issue