From d305f8adf3fa7c4f154f003bdf16ce42b1895ffd Mon Sep 17 00:00:00 2001 From: nico Date: Sat, 6 Oct 2018 13:16:27 +0200 Subject: small improvements * leading 0 fix * validation function improvements * moved strings.py and misc files to /common/ --- main.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) (limited to 'main.py') diff --git a/main.py b/main.py index 2625728..cc0d282 100644 --- a/main.py +++ b/main.py @@ -17,7 +17,7 @@ import logging from argparse import ArgumentParser from slixmpp.exceptions import XMPPError -from classes.strings import StaticAnswers +from common.strings import StaticAnswers from classes.functions import Version, LastActivity, ContactInfo, HandleError from classes.xep import XEPRequest @@ -46,7 +46,7 @@ class QueryBot(slixmpp.ClientXMPP): for rooms in self.room.split(sep=","): self.plugin['xep_0045'].join_muc(rooms, self.nick, wait=True) - def validate_domain(self, wordlist, index): + def validate(self, wordlist, index): """ validation method to reduce malformed querys and unnecessary connection attempts :param wordlist: words separated by " " from the message @@ -71,8 +71,8 @@ class QueryBot(slixmpp.ClientXMPP): # check if number keyword is used if true check if target is assignable elif argument in StaticAnswers().keys(arg='list', keyword='number_keywords'): try: - target = wordlist[index + 1] - return True + if wordlist[index + 1]: + return True except IndexError: # except an IndexError if target is not assignable return False @@ -124,27 +124,26 @@ class QueryBot(slixmpp.ClientXMPP): # if so queue the keyword and the postion in the string if x[1] == y: # only add job to queue if domain is valid - if self.validate_domain(words, x[0]): + if self.validate(words, x[0]): queue.append({str(y): x[0]}) # queue for job in queue: - for key in job: - keyword = key - index = job[key] + for keyword in job: + index = job[keyword] if keyword == '!help': reply.append(StaticAnswers().gen_help()) continue + target = words[index + 1] try: - target = words[index + 1] if keyword == '!uptime': - last_activity = yield from self['xep_0012'].get_last_activity(target) + last_activity = yield from self['xep_0012'].get_last_activity(jid=target, cached=False) reply.append(LastActivity(last_activity, msg, target).format_values()) elif keyword == "!version": - version = yield from self['xep_0092'].get_version(target) + version = yield from self['xep_0092'].get_version(jid=target, cached=False) reply.append(Version(version, msg, target).format_version()) elif keyword == "!contact": @@ -155,7 +154,7 @@ class QueryBot(slixmpp.ClientXMPP): reply.append(XEPRequest(msg, target).format()) except XMPPError as error: - reply.append(HandleError(error, msg, key, target).build_report()) + reply.append(HandleError(error, msg, keyword, target).build_report()) # remove None type from list and send all elements if list(filter(None.__ne__, reply)) and reply: -- cgit v1.2.3-18-g5258 From 31ddc8aeb47a19ff038678fb55992338aad0b6b1 Mon Sep 17 00:00:00 2001 From: nico Date: Wed, 10 Oct 2018 01:34:56 +0200 Subject: new async logic --- main.py | 57 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 25 deletions(-) (limited to 'main.py') diff --git a/main.py b/main.py index cc0d282..ef4345d 100644 --- a/main.py +++ b/main.py @@ -17,6 +17,7 @@ import logging from argparse import ArgumentParser from slixmpp.exceptions import XMPPError +import common.misc as misc from common.strings import StaticAnswers from classes.functions import Version, LastActivity, ContactInfo, HandleError from classes.xep import XEPRequest @@ -29,10 +30,16 @@ class QueryBot(slixmpp.ClientXMPP): self.room = room self.nick = nick + self.data = { + 'words': list(), + 'reply': list(), + 'queue': list() + } + # session start event, starting point for the presence and roster requests self.add_event_handler('session_start', self.start) - # register handler to recieve both groupchat and normal message events + # register recieve handler for both groupchat and normal message events self.add_event_handler('message', self.message) def start(self, event): @@ -97,68 +104,68 @@ class QueryBot(slixmpp.ClientXMPP): return reply_dedup - @asyncio.coroutine - def message(self, msg): + async def message(self, msg): """ :param msg: received message stanza """ - # init empty reply list - reply = list() + data = self.data # catch self messages to prevent self flooding if msg['mucnick'] == self.nick: return + elif self.nick in msg['body']: # add pre predefined text to reply list - reply.append(StaticAnswers(msg['mucnick']).gen_answer()) + data['reply'].append(StaticAnswers(msg['mucnick']).gen_answer()) # building the queue # double splitting to exclude whitespaces - words = " ".join(msg['body'].split()).split(sep=" ") - queue = list() + data['words'] = " ".join(msg['body'].split()).split(sep=" ") # check all words in side the message for possible hits - for x in enumerate(words): + for x in enumerate(data['words']): # check word for match in keywords list for y in StaticAnswers().keys(arg='list'): - # if so queue the keyword and the postion in the string + # if so queue the keyword and the position in the string if x[1] == y: # only add job to queue if domain is valid - if self.validate(words, x[0]): - queue.append({str(y): x[0]}) + if misc.validate(data['words'], x[0]): + data['queue'].append({str(y): x[0]}) # queue - for job in queue: + for job in data['queue']: for keyword in job: index = job[keyword] if keyword == '!help': - reply.append(StaticAnswers().gen_help()) + data['reply'].append(StaticAnswers().gen_help()) continue - target = words[index + 1] + target = data['words'][index + 1] try: if keyword == '!uptime': - last_activity = yield from self['xep_0012'].get_last_activity(jid=target, cached=False) - reply.append(LastActivity(last_activity, msg, target).format_values()) + last_activity = await self['xep_0012'].get_last_activity(jid=target) + self.data['reply'].append(LastActivity(last_activity, msg, target).format_values()) elif keyword == "!version": - version = yield from self['xep_0092'].get_version(jid=target, cached=False) - reply.append(Version(version, msg, target).format_version()) + version = await self['xep_0092'].get_version(jid=target) + self.data['reply'].append(Version(version, msg, target).format_version()) + elif keyword == "!contact": - contact = yield from self['xep_0030'].get_info(jid=target, cached=False) - reply.append(ContactInfo(contact, msg, target).format_contact()) + last_activity = await self['xep_0012'].get_last_activity(jid=target) + self.data['reply'].append(LastActivity(last_activity, msg, target).format_values()) + elif keyword == "!xep": - reply.append(XEPRequest(msg, target).format()) + data['reply'].append(XEPRequest(msg, target).format()) except XMPPError as error: - reply.append(HandleError(error, msg, keyword, target).build_report()) + data['reply'].append(HandleError(error, msg, keyword, target).build_report()) # remove None type from list and send all elements - if list(filter(None.__ne__, reply)) and reply: - reply = self.deduplicate(reply) + if list(filter(None.__ne__, data['reply'])) and data['reply']: + reply = misc.deduplicate(data['reply']) self.send_message(mto=msg['from'].bare, mbody="\n".join(reply), mtype=msg['type']) -- cgit v1.2.3-18-g5258 From 559ab280ca705bca200823a0493308b10aba1dd4 Mon Sep 17 00:00:00 2001 From: nico Date: Thu, 11 Oct 2018 19:28:18 +0200 Subject: * new uptime.py file - removed asyncio from import * fixed bug with muc jid parser * outsorced validate and dedup to misc.py --- main.py | 90 ++++++++++++++++------------------------------------------------- 1 file changed, 22 insertions(+), 68 deletions(-) (limited to 'main.py') diff --git a/main.py b/main.py index ef4345d..c285e2f 100644 --- a/main.py +++ b/main.py @@ -1,16 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ - Slixmpp: The Slick XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of Slixmpp. + James the MagicXMPP Bot + build with Slick XMPP Library + Copyright (C) 2018 Nico Wellpott See the file LICENSE for copying permission. """ -import asyncio import slixmpp import ssl -import validators import configparser import logging @@ -19,7 +17,9 @@ from slixmpp.exceptions import XMPPError import common.misc as misc from common.strings import StaticAnswers -from classes.functions import Version, LastActivity, ContactInfo, HandleError +from classes.functions import Version, ContactInfo, HandleError +from classes.servercontact import ServerContact +from classes.uptime import LastActivity from classes.xep import XEPRequest @@ -39,7 +39,7 @@ class QueryBot(slixmpp.ClientXMPP): # session start event, starting point for the presence and roster requests self.add_event_handler('session_start', self.start) - # register recieve handler for both groupchat and normal message events + # register receive handler for both groupchat and normal message events self.add_event_handler('message', self.message) def start(self, event): @@ -50,65 +50,19 @@ class QueryBot(slixmpp.ClientXMPP): self.get_roster() # If a room password is needed, use: password=the_room_password - for rooms in self.room.split(sep=","): - self.plugin['xep_0045'].join_muc(rooms, self.nick, wait=True) - - def validate(self, wordlist, index): - """ - validation method to reduce malformed querys and unnecessary connection attempts - :param wordlist: words separated by " " from the message - :param index: keyword index inside the message - :return: true if valid - """ - # keyword inside the message - argument = wordlist[index] - - # check if argument is in the argument list - if argument in StaticAnswers().keys(arg='list'): - # if argument uses a domain check for occurence in list and check domain - if argument in StaticAnswers().keys(arg='list', keyword='domain_keywords'): - try: - target = wordlist[index + 1] - if validators.domain(target): - return True - except IndexError: - # except an IndexError if a keywords is the last word in the message - return False - - # check if number keyword is used if true check if target is assignable - elif argument in StaticAnswers().keys(arg='list', keyword='number_keywords'): - try: - if wordlist[index + 1]: - return True - except IndexError: - # except an IndexError if target is not assignable - return False - # check if argument is inside no_arg list - elif argument in StaticAnswers().keys(arg='list', keyword="no_arg_keywords"): - return True - else: - return False - else: - return False - - def deduplicate(self, reply): - """ - deduplication method for the result list - :param list reply: list containing strings - :return: list containing unique strings - """ - reply_dedup = list() - for item in reply: - if item not in reply_dedup: - reply_dedup.append(item) - - return reply_dedup + if self.room: + for rooms in self.room.split(sep=","): + self.plugin['xep_0045'].join_muc(rooms, self.nick, wait=True) async def message(self, msg): """ :param msg: received message stanza """ - data = self.data + data = { + 'words': list(), + 'reply': list(), + 'queue': list() + } # catch self messages to prevent self flooding if msg['mucnick'] == self.nick: @@ -144,18 +98,18 @@ class QueryBot(slixmpp.ClientXMPP): target = data['words'][index + 1] try: if keyword == '!uptime': - last_activity = await self['xep_0012'].get_last_activity(jid=target) - self.data['reply'].append(LastActivity(last_activity, msg, target).format_values()) + data['reply'].append(LastActivity(self, msg, target).format_values()) + + #last_activity = await self['xep_0012'].get_last_activity(jid=target) + #data['reply'].append(LastActivity(last_activity, msg, target).format_values()) elif keyword == "!version": version = await self['xep_0092'].get_version(jid=target) - self.data['reply'].append(Version(version, msg, target).format_version()) - + data['reply'].append(Version(version, msg, target).format_version()) elif keyword == "!contact": - last_activity = await self['xep_0012'].get_last_activity(jid=target) - self.data['reply'].append(LastActivity(last_activity, msg, target).format_values()) - + contact = await self['xep_0030'].get_info(jid=target, cached=False) + data['reply'].append(ServerContact(contact, msg, target).format_contact()) elif keyword == "!xep": data['reply'].append(XEPRequest(msg, target).format()) -- cgit v1.2.3-18-g5258 From 0c313565f2b649366f7382dc1b3f28a3e80f4ffc Mon Sep 17 00:00:00 2001 From: nico Date: Tue, 6 Nov 2018 23:43:11 +0100 Subject: simplification and major rework * updated gitignore file * partly reworked servercontact implementation * complete rework of uptime, version * part rework of xep requests + added more comments to xep requests + added opt_arg to version, xep and contact * complete rework of validate function * updated HandleError function * part rework of StaticStrings function + implemented data dictionary to hold all data in main bot + added message_ids * complete rework of queue building and deduplication --- main.py | 118 +++++++++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 79 insertions(+), 39 deletions(-) (limited to 'main.py') diff --git a/main.py b/main.py index c285e2f..42d5349 100644 --- a/main.py +++ b/main.py @@ -17,8 +17,8 @@ from slixmpp.exceptions import XMPPError import common.misc as misc from common.strings import StaticAnswers -from classes.functions import Version, ContactInfo, HandleError from classes.servercontact import ServerContact +from classes.version import Version from classes.uptime import LastActivity from classes.xep import XEPRequest @@ -29,6 +29,14 @@ class QueryBot(slixmpp.ClientXMPP): self.ssl_version = ssl.PROTOCOL_TLSv1_2 self.room = room self.nick = nick + self.use_message_ids = True + + self.functions = { + "!uptime": LastActivity(), + "!contact": ServerContact(), + "!version": Version(), + "!xep": XEPRequest() + } self.data = { 'words': list(), @@ -72,56 +80,88 @@ class QueryBot(slixmpp.ClientXMPP): # add pre predefined text to reply list data['reply'].append(StaticAnswers(msg['mucnick']).gen_answer()) - # building the queue - # double splitting to exclude whitespaces - data['words'] = " ".join(msg['body'].split()).split(sep=" ") - - # check all words in side the message for possible hits - for x in enumerate(data['words']): - # check word for match in keywords list - for y in StaticAnswers().keys(arg='list'): - # if so queue the keyword and the position in the string - if x[1] == y: - # only add job to queue if domain is valid - if misc.validate(data['words'], x[0]): - data['queue'].append({str(y): x[0]}) + data = self.build_queue(data, msg) # queue for job in data['queue']: - for keyword in job: - index = job[keyword] + keys = list(job.keys()) + keyword = keys[0] - if keyword == '!help': - data['reply'].append(StaticAnswers().gen_help()) - continue + target = job[keyword][0] + opt_arg = job[keyword][1] + query = None - target = data['words'][index + 1] - try: - if keyword == '!uptime': - data['reply'].append(LastActivity(self, msg, target).format_values()) + if keyword == '!help': + data['reply'].append(StaticAnswers().gen_help()) + continue - #last_activity = await self['xep_0012'].get_last_activity(jid=target) - #data['reply'].append(LastActivity(last_activity, msg, target).format_values()) + try: + if keyword == "!uptime": + query = await self['xep_0012'].get_last_activity(jid=target) - elif keyword == "!version": - version = await self['xep_0092'].get_version(jid=target) - data['reply'].append(Version(version, msg, target).format_version()) + elif keyword == "!version": + query = await self['xep_0092'].get_version(jid=target) - elif keyword == "!contact": - contact = await self['xep_0030'].get_info(jid=target, cached=False) - data['reply'].append(ServerContact(contact, msg, target).format_contact()) + elif keyword == "!contact": + query = await self['xep_0030'].get_info(jid=target, cached=False) - elif keyword == "!xep": - data['reply'].append(XEPRequest(msg, target).format()) + except XMPPError as error: + logging.INFO(misc.HandleError(error, keyword, target).report()) + data['reply'].append(misc.HandleError(error, keyword, target).report()) + continue - except XMPPError as error: - data['reply'].append(HandleError(error, msg, keyword, target).build_report()) + data["reply"].append(self.functions[keyword].format(query=query, target=target, opt_arg=opt_arg)) # remove None type from list and send all elements if list(filter(None.__ne__, data['reply'])) and data['reply']: - reply = misc.deduplicate(data['reply']) + + # 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"] self.send_message(mto=msg['from'].bare, mbody="\n".join(reply), mtype=msg['type']) + 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 + if __name__ == '__main__': # command line arguments. @@ -130,12 +170,12 @@ if __name__ == '__main__': 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) - parser.add_argument('-D', '--dev', help='set logging to console', action='store_const', dest='logfile', const="", - default='bot.log') + parser.add_argument('-D', '--dev', help='set logging to console', action='store_const', dest='logfile', + const="", default='bot.log') args = parser.parse_args() # logging - logging.basicConfig(filename=args.logfile, level=args.loglevel, format='%(levelname)-8s %(message)s') + logging.basicConfig(filename=args.logfile, level=logging.INFO, format='%(levelname)-8s %(message)s') logger = logging.getLogger(__name__) # configfile -- cgit v1.2.3-18-g5258 From d7fc664d3be4634a693a24fa98ff3f15d2c97c41 Mon Sep 17 00:00:00 2001 From: nico Date: Wed, 7 Nov 2018 00:37:24 +0100 Subject: * corrected CamelCase * corrected logging.INFO to .info * small changes to HandleError class --- main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'main.py') diff --git a/main.py b/main.py index 42d5349..5f8f220 100644 --- a/main.py +++ b/main.py @@ -60,6 +60,7 @@ class QueryBot(slixmpp.ClientXMPP): # If a room password is needed, use: password=the_room_password if self.room: for rooms in self.room.split(sep=","): + logging.debug("joining: %s" % rooms) self.plugin['xep_0045'].join_muc(rooms, self.nick, wait=True) async def message(self, msg): @@ -106,7 +107,7 @@ class QueryBot(slixmpp.ClientXMPP): query = await self['xep_0030'].get_info(jid=target, cached=False) except XMPPError as error: - logging.INFO(misc.HandleError(error, keyword, target).report()) + logging.info(misc.HandleError(error, keyword, target).report()) data['reply'].append(misc.HandleError(error, keyword, target).report()) continue -- cgit v1.2.3-18-g5258 From 146a4efc7f9bb97caaa97ddd02f1ce244cb4deb0 Mon Sep 17 00:00:00 2001 From: nico Date: Wed, 7 Nov 2018 00:49:32 +0100 Subject: small fixup + added timestamp to logging output * finished up HandleError output --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'main.py') diff --git a/main.py b/main.py index 5f8f220..97a1110 100644 --- a/main.py +++ b/main.py @@ -176,7 +176,7 @@ if __name__ == '__main__': args = parser.parse_args() # logging - logging.basicConfig(filename=args.logfile, level=logging.INFO, format='%(levelname)-8s %(message)s') + logging.basicConfig(filename=args.logfile, level=logging.INFO, format='%(levelname)s: %(asctime)s: %(message)s') logger = logging.getLogger(__name__) # configfile -- cgit v1.2.3-18-g5258 From de56a9315cef894a2f3c3a9b39cad4ed10f55491 Mon Sep 17 00:00:00 2001 From: nico Date: Wed, 7 Nov 2018 01:14:45 +0100 Subject: small error correction * +x to main.py + added catch for a None response as some xeps have addition tags some do not have --- main.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 main.py (limited to 'main.py') diff --git a/main.py b/main.py old mode 100644 new mode 100755 -- cgit v1.2.3-18-g5258 From cd1442e216abf564daceaef5fe45555587eef69b Mon Sep 17 00:00:00 2001 From: nico Date: Fri, 9 Nov 2018 19:42:21 +0100 Subject: code quality improvements - remove unused variable * better iteration of xdata nodes - removed unnecessary else --- main.py | 6 ------ 1 file changed, 6 deletions(-) (limited to 'main.py') diff --git a/main.py b/main.py index 97a1110..24171cd 100755 --- a/main.py +++ b/main.py @@ -38,12 +38,6 @@ class QueryBot(slixmpp.ClientXMPP): "!xep": XEPRequest() } - self.data = { - 'words': list(), - 'reply': list(), - 'queue': list() - } - # session start event, starting point for the presence and roster requests self.add_event_handler('session_start', self.start) -- cgit v1.2.3-18-g5258 From 1b13bdfd926e394cab2a2edd15ecabf0afcc4cf2 Mon Sep 17 00:00:00 2001 From: nico Date: Sun, 11 Nov 2018 03:09:57 +0100 Subject: correct logging parameter * fix loglevel be set to the correct logging parameter --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'main.py') diff --git a/main.py b/main.py index 24171cd..50f9b03 100755 --- a/main.py +++ b/main.py @@ -170,7 +170,7 @@ if __name__ == '__main__': args = parser.parse_args() # logging - logging.basicConfig(filename=args.logfile, level=logging.INFO, format='%(levelname)s: %(asctime)s: %(message)s') + logging.basicConfig(filename=args.logfile, level=args.loglevel, format='%(levelname)s: %(asctime)s: %(message)s') logger = logging.getLogger(__name__) # configfile -- cgit v1.2.3-18-g5258