from sid import __url__, __doc__
+log = logging.getLogger(__package__)
+
def botcmd(*args, **kwargs):
"""Decorator for bot command functions
#: Defaults to "!"
prefix = '!'
- def __init__(self, jid, password, room, nick, log_file=None,
+ def __init__(self, jid, password, room, nick,
log_level=logging.INFO):
super(MUCBot, self).__init__(jid, password)
# Clean sphinx autodoc for self documentation
# (cf. MUCBot.help)
self.__doc__ = None
- self.log = logging.getLogger(__package__)
+ self.log = log
self.plugins = list()
self.commands = dict()
self.room = room
self.nick = nick
- self.__set_logger(log_file, log_level)
+ #: Keep track of MUC presences: {'nick': presence}
+ self.muc_presences = {}
+ self.__set_logger(log_level)
self.__seen = dict()
self.register_plugin('xep_0030') # Service Discovery
self.register_plugin('xep_0045') # Multi-User Chat
self.add_event_handler('message', self.message)
self.add_event_handler('got_online', self._view)
+ # keep track of join/parts
+ self.add_event_handler(f'muc::{self.room}::got_offline', self._muc_got_offline)
+ self.add_event_handler(f'muc::{self.room}::got_online', self._muc_got_online)
+ self.add_event_handler(f'muc::{self.room}::presence', self._muc_got_presence)
+
# Handles disconnection
self.add_event_handler('disconnected', self.disconn)
if inspect.ismethod(value) and \
getattr(value, '_bot_command', False):
name = getattr(value, '_bot_command_name')
- self.log.debug('Registered command: %s', name)
+ log.debug('Registered command: %s', name)
self.commands[name] = value
- def __set_logger(self, log_file=None, log_level=logging.INFO):
- """Create console/file handler"""
- log_fd = open(log_file, 'w') if log_file else None
- chandler = logging.StreamHandler(log_fd)
- formatter = logging.Formatter(
- '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
- )
- chandler.setFormatter(formatter)
- self.log.addHandler(chandler)
- self.log.setLevel(log_level)
- self.log.debug('set logger, log level : %s', log_level)
+ def __set_logger(self, log_level):
+ """Set logging level"""
+ log.setLevel(log_level)
+ log.debug('set logger, log level : %s', log_level)
+
+ def _muc_got_online(self, pres):
+ """Keep track of MUC participants"""
+ fjid = pres['muc']['jid']
+ nick = pres['muc']['nick']
+ role = pres['muc']['role']
+ affi = pres['muc']['affiliation']
+ user = fjid if fjid.full else nick
+ self.muc_presences.update({nick: pres})
+ log.debug('Participants: + %s:%s/%s (len:%s)',
+ user, role, affi,len(self.muc_presences))
+
+ def _muc_got_offline(self, pres):
+ """Keep track of MUC participants"""
+ fjid = pres['muc']['jid']
+ user = fjid if fjid.full else pres['muc']['nick']
+ try:
+ self.muc_presences.pop(pres['muc']['nick'])
+ except KeyError:
+ log.error('KeyError removing participant: "%s"', user)
+ log.debug('Participants: - %s got offline (len:%s)',
+ user, len(self.muc_presences))
+
+ def _muc_got_presence(self, pres):
+ """Keep track of MUC participants"""
+ nick = pres['muc']['nick']
+ fjid = pres['muc']['jid']
+ role = pres['muc']['role']
+ affi = pres['muc']['affiliation']
+ user = fjid if fjid.full else nick
+ log.debug('Participants: u %s:%s/%s (len:%s)',
+ user, role, affi,len(self.muc_presences))
+ self.muc_presences.update({nick: pres})
def disconn(self, event):
"""disconnected handler"""
msg = ": %s" % event if event else "‽"
- self.log.info('Disconnected from server%s', msg)
+ log.info('Disconnected from server%s', msg)
self.connect()
def message(self, msg):
Parses message received to detect :py:obj:`prefix`
"""
if msg['type'] not in ('groupchat', 'chat'):
- self.log.warning('Unhandled message')
+ log.warning('Unhandled message')
return
if msg['mucnick'] == self.nick:
return
body = msg['body'].strip()
if not body.startswith(MUCBot.prefix):
return
- self.log.debug(msg['from'])
+ log.debug(msg['from'])
if msg['from'] not in self.__seen and msg['type'] == 'chat':
- self.log.warning('Will not handle direct message'
+ log.warning('Will not handle direct message'
'from unseen jid: %s', msg['from'])
return
args = body[1:].split()
cmd = args.pop(0)
if cmd not in self.commands:
return
- self.log.debug('cmd: %s', cmd)
+ log.debug('cmd: %s', cmd)
if args:
- self.log.debug('arg: %s', args)
+ log.debug('arg: %s', args)
try:
self.commands[cmd](msg, args)
except Exception as err:
reply = ''.join(traceback.format_exc())
- self.log.exception('An error occurred processing: %s: %s', body, reply)
- if self.log.level < 10 and reply:
+ log.exception('An error occurred processing: %s: %s', body, reply)
+ if log.level < 10 and reply:
self.send_message(mto=msg['from'].bare, mbody=reply,
mtype='groupchat')
await self.plugin['xep_0045'].join_muc_wait(self.room,
self.nick,
# Do not fetch history
- seconds=0,
+ maxstanzas=0,
# If a room password is needed, use:
# password=the_room_password,
)
if inspect.ismethod(value) and \
getattr(value, '_bot_command', False):
name = getattr(value, '_bot_command_name')
- self.log.debug('Registered command: %s', name)
+ log.debug('Registered command: %s', name)
+ self.commands[name] = value
+
+ def unregister_bot_plugin(self, plugin):
+ for name, value in inspect.getmembers(plugin):
+ if inspect.ismethod(value) and \
+ getattr(value, '_bot_command', False):
+ name = getattr(value, '_bot_command_name')
+ log.debug('Unegistered command: %s', name)
self.commands[name] = value
+ self.plugins.remove(plugin)
def foreach_plugin(self, method, *args, **kwds):
for plugin in self.plugins:
- self.log.debug('calling %s for %s', method, plugin)
+ log.debug('calling %s for %s', method, plugin)
getattr(plugin, method)(*args, **kwds)
def shutdown_plugins(self):
# TODO: also use event session_end|disconnected?
- self.log.info('shuting down')
+ log.info('shuting down')
for plugin in self.plugins:
- self.log.debug('shuting down %s', plugin)
+ log.debug('shuting down %s', plugin)
getattr(plugin, 'shutdown')()
@botcmd