2018-10-01 23:17:09 +02:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
2018-10-11 19:28:18 +02:00
|
|
|
James the MagicXMPP Bot
|
|
|
|
build with Slick XMPP Library
|
|
|
|
Copyright (C) 2018 Nico Wellpott
|
2018-10-01 23:17:09 +02:00
|
|
|
|
|
|
|
See the file LICENSE for copying permission.
|
|
|
|
"""
|
|
|
|
import slixmpp
|
|
|
|
import ssl
|
|
|
|
import configparser
|
|
|
|
import logging
|
|
|
|
|
|
|
|
from argparse import ArgumentParser
|
|
|
|
from slixmpp.exceptions import XMPPError
|
|
|
|
|
2018-10-10 01:34:56 +02:00
|
|
|
import common.misc as misc
|
2018-10-06 13:16:27 +02:00
|
|
|
from common.strings import StaticAnswers
|
2018-10-11 19:28:18 +02:00
|
|
|
from classes.servercontact import ServerContact
|
2018-11-06 23:43:11 +01:00
|
|
|
from classes.version import Version
|
2018-10-11 19:28:18 +02:00
|
|
|
from classes.uptime import LastActivity
|
2018-10-03 23:24:36 +02:00
|
|
|
from classes.xep import XEPRequest
|
2018-10-01 23:17:09 +02:00
|
|
|
|
|
|
|
|
|
|
|
class QueryBot(slixmpp.ClientXMPP):
|
|
|
|
def __init__(self, jid, password, room, nick):
|
|
|
|
slixmpp.ClientXMPP.__init__(self, jid, password)
|
|
|
|
self.ssl_version = ssl.PROTOCOL_TLSv1_2
|
|
|
|
self.room = room
|
|
|
|
self.nick = nick
|
2018-11-06 23:43:11 +01:00
|
|
|
self.use_message_ids = True
|
|
|
|
|
|
|
|
self.functions = {
|
|
|
|
"!uptime": LastActivity(),
|
|
|
|
"!contact": ServerContact(),
|
|
|
|
"!version": Version(),
|
|
|
|
"!xep": XEPRequest()
|
|
|
|
}
|
2018-10-01 23:17:09 +02:00
|
|
|
|
|
|
|
# session start event, starting point for the presence and roster requests
|
|
|
|
self.add_event_handler('session_start', self.start)
|
|
|
|
|
2018-10-11 19:28:18 +02:00
|
|
|
# register receive handler for both groupchat and normal message events
|
2018-10-01 23:17:09 +02:00
|
|
|
self.add_event_handler('message', self.message)
|
|
|
|
|
|
|
|
def start(self, event):
|
|
|
|
"""
|
2018-10-03 23:24:36 +02:00
|
|
|
:param event -- An empty dictionary. The session_start event does not provide any additional data.
|
2018-10-01 23:17:09 +02:00
|
|
|
"""
|
|
|
|
self.send_presence()
|
|
|
|
self.get_roster()
|
|
|
|
|
|
|
|
# If a room password is needed, use: password=the_room_password
|
2018-10-11 19:28:18 +02:00
|
|
|
if self.room:
|
|
|
|
for rooms in self.room.split(sep=","):
|
2018-11-07 00:37:24 +01:00
|
|
|
logging.debug("joining: %s" % rooms)
|
2022-01-16 14:47:50 +01:00
|
|
|
# join_muc will be deprecated in slixmpp 1.8.0
|
|
|
|
# see https://slixmpp.readthedocs.io/en/latest/api/plugins/xep_0045.html?highlight=join_muc_wait#slixmpp.plugins.xep_0045.XEP_0045.join_muc_wait
|
|
|
|
# self.plugin['xep_0045'].join_muc_wait(rooms, self.nick, maxstanzas=0)
|
|
|
|
self.plugin['xep_0045'].join_muc(rooms, self.nick)
|
2018-10-01 23:17:09 +02:00
|
|
|
|
2018-10-10 01:34:56 +02:00
|
|
|
async def message(self, msg):
|
2018-10-01 23:17:09 +02:00
|
|
|
"""
|
|
|
|
:param msg: received message stanza
|
|
|
|
"""
|
2018-10-11 19:28:18 +02:00
|
|
|
data = {
|
|
|
|
'words': list(),
|
|
|
|
'reply': list(),
|
|
|
|
'queue': list()
|
|
|
|
}
|
2018-10-01 23:17:09 +02:00
|
|
|
|
|
|
|
# catch self messages to prevent self flooding
|
|
|
|
if msg['mucnick'] == self.nick:
|
|
|
|
return
|
2018-10-10 01:34:56 +02:00
|
|
|
|
2018-10-01 23:17:09 +02:00
|
|
|
elif self.nick in msg['body']:
|
|
|
|
# add pre predefined text to reply list
|
2018-10-10 01:34:56 +02:00
|
|
|
data['reply'].append(StaticAnswers(msg['mucnick']).gen_answer())
|
2018-10-01 23:17:09 +02:00
|
|
|
|
2018-11-06 23:43:11 +01:00
|
|
|
data = self.build_queue(data, msg)
|
2018-10-01 23:17:09 +02:00
|
|
|
|
|
|
|
# queue
|
2018-10-10 01:34:56 +02:00
|
|
|
for job in data['queue']:
|
2018-11-06 23:43:11 +01:00
|
|
|
keys = list(job.keys())
|
|
|
|
keyword = keys[0]
|
2018-10-01 23:17:09 +02:00
|
|
|
|
2018-11-06 23:43:11 +01:00
|
|
|
target = job[keyword][0]
|
|
|
|
opt_arg = job[keyword][1]
|
|
|
|
query = None
|
2018-10-01 23:17:09 +02:00
|
|
|
|
2018-11-06 23:43:11 +01:00
|
|
|
if keyword == '!help':
|
|
|
|
data['reply'].append(StaticAnswers().gen_help())
|
|
|
|
continue
|
2018-10-11 19:28:18 +02:00
|
|
|
|
2018-11-06 23:43:11 +01:00
|
|
|
try:
|
|
|
|
if keyword == "!uptime":
|
|
|
|
query = await self['xep_0012'].get_last_activity(jid=target)
|
2018-10-01 23:17:09 +02:00
|
|
|
|
2018-11-06 23:43:11 +01:00
|
|
|
elif keyword == "!version":
|
|
|
|
query = await self['xep_0092'].get_version(jid=target)
|
2018-10-01 23:17:09 +02:00
|
|
|
|
2018-11-06 23:43:11 +01:00
|
|
|
elif keyword == "!contact":
|
|
|
|
query = await self['xep_0030'].get_info(jid=target, cached=False)
|
2018-10-01 23:17:09 +02:00
|
|
|
|
2018-11-06 23:43:11 +01:00
|
|
|
except XMPPError as error:
|
2018-11-07 00:37:24 +01:00
|
|
|
logging.info(misc.HandleError(error, keyword, target).report())
|
2018-11-06 23:43:11 +01:00
|
|
|
data['reply'].append(misc.HandleError(error, keyword, target).report())
|
|
|
|
continue
|
2018-10-03 23:24:36 +02:00
|
|
|
|
2018-11-06 23:43:11 +01:00
|
|
|
data["reply"].append(self.functions[keyword].format(query=query, target=target, opt_arg=opt_arg))
|
2018-10-01 23:17:09 +02:00
|
|
|
|
|
|
|
# remove None type from list and send all elements
|
2018-10-10 01:34:56 +02:00
|
|
|
if list(filter(None.__ne__, data['reply'])) and data['reply']:
|
2018-11-06 23:43:11 +01:00
|
|
|
|
|
|
|
# if msg type is groupchat prepend mucnick
|
|
|
|
if msg["type"] == "groupchat":
|
|
|
|
data["reply"][0] = "%s: " % msg["mucnick"] + data["reply"][0]
|
|
|
|
|
|
|
|
# reply = misc.deduplicate(data['reply'])
|
|
|
|
reply = data["reply"]
|
2018-10-01 23:17:09 +02:00
|
|
|
self.send_message(mto=msg['from'].bare, mbody="\n".join(reply), mtype=msg['type'])
|
|
|
|
|
2018-11-06 23:43:11 +01:00
|
|
|
def build_queue(self, data, msg):
|
|
|
|
# building the queue
|
|
|
|
# double splitting to exclude whitespaces
|
|
|
|
data['words'] = " ".join(msg['body'].split()).split(sep=" ")
|
|
|
|
wordcount = len(data["words"])
|
|
|
|
|
|
|
|
# check all words in side the message for possible hits
|
|
|
|
for x in enumerate(data['words']):
|
|
|
|
# check for valid keywords
|
|
|
|
index = x[0]
|
|
|
|
keyword = x[1]
|
|
|
|
|
|
|
|
# match all words starting with ! and member of no_arg_keywords
|
|
|
|
if keyword.startswith("!") and keyword in StaticAnswers().keys("no_arg_keywords"):
|
|
|
|
data['queue'].append({keyword: [None, None]})
|
|
|
|
|
|
|
|
# matching all words starting with ! and member of keywords
|
|
|
|
elif keyword.startswith("!") and keyword in StaticAnswers().keys("keywords"):
|
|
|
|
# init variables to circumvent IndexErrors
|
|
|
|
target, opt_arg = None, None
|
|
|
|
|
|
|
|
# compare to wordcount if assignment is possible
|
|
|
|
if index + 1 < wordcount:
|
|
|
|
target = data["words"][index + 1]
|
|
|
|
|
|
|
|
if index + 2 < wordcount:
|
|
|
|
if not data["words"][index + 2].startswith("!"):
|
|
|
|
opt_arg = data["words"][index + 2]
|
|
|
|
|
|
|
|
# only add job to queue if domain is valid
|
|
|
|
if misc.validate(keyword, target):
|
|
|
|
logging.debug("Item added to queue %s" % {str(keyword): [target, opt_arg]})
|
|
|
|
data['queue'].append({str(keyword): [target, opt_arg]})
|
|
|
|
|
|
|
|
# deduplicate queue elements
|
|
|
|
data["queue"] = misc.deduplicate(data["queue"])
|
|
|
|
|
|
|
|
return data
|
|
|
|
|
2018-10-01 23:17:09 +02:00
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
# command line arguments.
|
|
|
|
parser = ArgumentParser()
|
|
|
|
parser.add_argument('-q', '--quiet', help='set logging to ERROR', action='store_const', dest='loglevel',
|
|
|
|
const=logging.ERROR, default=logging.INFO)
|
|
|
|
parser.add_argument('-d', '--debug', help='set logging to DEBUG', action='store_const', dest='loglevel',
|
|
|
|
const=logging.DEBUG, default=logging.INFO)
|
2018-11-06 23:43:11 +01:00
|
|
|
parser.add_argument('-D', '--dev', help='set logging to console', action='store_const', dest='logfile',
|
|
|
|
const="", default='bot.log')
|
2018-10-01 23:17:09 +02:00
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
# logging
|
2018-11-11 03:09:57 +01:00
|
|
|
logging.basicConfig(filename=args.logfile, level=args.loglevel, format='%(levelname)s: %(asctime)s: %(message)s')
|
2018-10-01 23:17:09 +02:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
# configfile
|
|
|
|
config = configparser.RawConfigParser()
|
|
|
|
config.read('./bot.cfg')
|
|
|
|
args.jid = config.get('Account', 'jid')
|
|
|
|
args.password = config.get('Account', 'password')
|
|
|
|
args.room = config.get('MUC', 'rooms')
|
|
|
|
args.nick = config.get('MUC', 'nick')
|
|
|
|
|
|
|
|
# init the bot and register used slixmpp plugins
|
|
|
|
xmpp = QueryBot(args.jid, args.password, args.room, args.nick)
|
|
|
|
xmpp.register_plugin('xep_0012') # Last Activity
|
|
|
|
xmpp.register_plugin('xep_0030') # Service Discovery
|
|
|
|
xmpp.register_plugin('xep_0045') # Multi-User Chat
|
|
|
|
xmpp.register_plugin('xep_0060') # PubSub
|
|
|
|
xmpp.register_plugin('xep_0085') # Chat State Notifications
|
|
|
|
xmpp.register_plugin('xep_0092') # Software Version
|
|
|
|
xmpp.register_plugin('xep_0128') # Service Discovery Extensions
|
|
|
|
xmpp.register_plugin('xep_0199') # XMPP Ping
|
|
|
|
|
|
|
|
# connect and start receiving stanzas
|
|
|
|
xmpp.connect()
|
|
|
|
xmpp.process()
|