diff --git a/sopel_modules/SpiceBot/Commands.py b/sopel_modules/SpiceBot/Commands.py index c746c1e..bd12f52 100644 --- a/sopel_modules/SpiceBot/Commands.py +++ b/sopel_modules/SpiceBot/Commands.py @@ -107,27 +107,24 @@ class BotCommands(): self.lock.acquire() command_type = command_dict["comtype"] - validcoms = command_dict["validcoms"] - - if not isinstance(validcoms, list): - validcoms = [validcoms] - if command_type not in list(self.dict['commands'].keys()): self.dict['commands'][command_type] = dict() - dict_from_file = dict() - # default command to filename - if "validcoms" not in list(dict_from_file.keys()): - dict_from_file["validcoms"] = validcoms + if "validcoms" not in list(command_dict.keys()): + command_dict["validcoms"] = [command_dict["filename"]] - maincom = dict_from_file["validcoms"][0] - if len(dict_from_file["validcoms"]) > 1: - comaliases = spicemanip.main(dict_from_file["validcoms"], '2+', 'list') + validcoms = command_dict["validcoms"] + if not isinstance(validcoms, list): + validcoms = [validcoms] + + maincom = command_dict["validcoms"][0] + if len(command_dict["validcoms"]) > 1: + comaliases = spicemanip.main(command_dict["validcoms"], '2+', 'list') else: comaliases = [] - self.dict['commands'][command_type][maincom] = dict_from_file + self.dict['commands'][command_type][maincom] = command_dict for comalias in comaliases: if comalias not in list(self.dict['commands'][command_type].keys()): self.dict['commands'][command_type][comalias] = {"aliasfor": maincom} @@ -194,6 +191,11 @@ class BotCommands(): module_file_lines.append(line) module_file.close() + # gather file stats + slashsplit = str(modulefile).split("/") + filename = slashsplit[-1] + filename_base = os.path.basename(filename).rsplit('.', 1)[0] + detected_lines = [] for line in module_file_lines: @@ -245,7 +247,12 @@ class BotCommands(): validcoms.remove(regexcom) if len(validcoms): - validcomdict = {"comtype": comtype, "validcoms": validcoms} + validcomdict = { + "comtype": comtype, + "validcoms": validcoms, + "filepath": str(modulefile), + "filename": filename_base, + } filelinelist.append(validcomdict) currentsuccesslines += 1 except Exception as e: @@ -257,8 +264,8 @@ class BotCommands(): self.dict['counts'] += 1 if len(filelinelist): - for atlinefound in filelinelist: - self.register(atlinefound) + for vcomdict in filelinelist: + self.register(vcomdict) commands = BotCommands() diff --git a/sopel_modules/SpiceBot/DictComs.py b/sopel_modules/SpiceBot/DictComs.py new file mode 100644 index 0000000..e2f4b75 --- /dev/null +++ b/sopel_modules/SpiceBot/DictComs.py @@ -0,0 +1,309 @@ +# coding=utf8 +from __future__ import unicode_literals, absolute_import, division, print_function +""" +This is the SpiceBot JSON Commands system. + +This Class stores commands in an easy to access manner +""" + +import os + +from sopel.config.types import StaticSection, ListAttribute +import sopel_modules + +from .Config import config as botconfig +from .Tools import read_directory_json_to_dict +from .Commands import commands as botcommands + +import spicemanip + +import requests +from fake_useragent import UserAgent +from lxml import html +import urllib + + +class SpiceBot_DictComs_MainSection(StaticSection): + extra = ListAttribute('extra') + + +class BotJSONCommands(): + """This Logs all commands known to the bot""" + def __init__(self): + def __init__(self): + self.setup_dictcoms() + self.header = {'User-Agent': str(UserAgent().chrome)} + self.valid_com_types = [ + 'simple', 'fillintheblank', 'targetplusreason', + 'sayings', "readfromfile", "readfromurl", + "ascii_art", "gif", "translate", "responses", + "feeds", "search" + ] + + dir_to_scan = [] + for plugin_dir in set(sopel_modules.__path__): + configsdir = os.path.join(plugin_dir, "SpiceBot_Configs") + gifcfgdir = os.path.join(configsdir, "dictcoms") + dir_to_scan.append(gifcfgdir) + + if len(botconfig.SpiceBot_DictComs.extra): + for extragifcfgdir in botconfig.SpiceBot_DictComs.extra: + dir_to_scan.append(extragifcfgdir) + + valid_dictcom_dict = read_directory_json_to_dict(dir_to_scan, "Dictionary Commands", "SpiceBot_DictComs") + + for dict_from_file in list(valid_dictcom_dict.keys()): + + dict_from_file["comtype"] = "prefix_command" + + # default command to filename + if "validcoms" not in list(dict_from_file.keys()): + dict_from_file["validcoms"] = [dict_from_file["filename"]] + elif dict_from_file["validcoms"] == []: + dict_from_file["validcoms"] = [dict_from_file["filename"]] + elif not isinstance(dict_from_file['validcoms'], list): + dict_from_file["validcoms"] = [dict_from_file["validcoms"]] + + maincom = dict_from_file["validcoms"][0] + + # check for tuple dict keys and split + validkeys = [x for x in list(dict_from_file.keys())] + for validkey in validkeys: + if isinstance(validkey, tuple): + tuple_bak = validkey + tuple_contents_bak = dict_from_file[validkey] + del dict_from_file[validkey] + for var in tuple_bak: + dict_from_file[var] = tuple_contents_bak + + # check that type is set, use cases will inherit this if not set + if "type" not in list(dict_from_file.keys()): + foldername = dict_from_file["filepath"].split("/" + dict_from_file["filename"])[0] + foldername = str(foldername).split("/")[-1] + dict_from_file["type"] = foldername + if dict_from_file["type"] not in self.valid_com_types: + dict_from_file["type"] = 'simple' + + # Don't process these. + keysprocessed = [] + keysprocessed.extend(["validcoms", "filepath", "filename"]) + + # the command must have an author + if "author" not in list(dict_from_file.keys()): + dict_from_file["author"] = "deathbybandaid" + keysprocessed.append("author") + + # the command must have a contributors list + if "contributors" not in list(dict_from_file.keys()): + dict_from_file["contributors"] = [] + if not isinstance(dict_from_file["contributors"], list): + dict_from_file["contributors"] = [dict_from_file["contributors"]] + if "deathbybandaid" not in dict_from_file["contributors"]: + dict_from_file["contributors"].append("deathbybandaid") + if dict_from_file["author"] not in dict_from_file["contributors"]: + dict_from_file["contributors"].append(dict_from_file["author"]) + keysprocessed.append("contributors") + + if "hardcoded_channel_block" not in list(dict_from_file.keys()): + dict_from_file["hardcoded_channel_block"] = [] + keysprocessed.append("hardcoded_channel_block") + + if "example" not in list(dict_from_file.keys()): + dict_from_file["example"] = str(botconfig.core.prefix_list[0] + maincom) + + if "exampleresponse" not in list(dict_from_file.keys()): + dict_from_file["exampleresponse"] = None + + if "description" not in list(dict_from_file.keys()): + dict_from_file["description"] = None + + if "privs" not in list(dict_from_file.keys()): + dict_from_file["privs"] = [] + + keysprocessed.extend(["validcoms", "filepath", "filename", "description", "exampleresponse", "example", "privs"]) + + # handle basic required dict handling + dict_required = ["?default"] + dict_from_file = self.bot_dict_use_cases(maincom, dict_from_file, dict_required) + keysprocessed.extend(dict_required) + + # remove later + keysprocessed.append("type") + + # all other keys not processed above are considered potential use cases + otherkeys = [] + for otherkey in list(dict_from_file.keys()): + if otherkey not in keysprocessed: + otherkeys.append(otherkey) + if otherkeys != []: + dict_from_file = self.bot_dict_use_cases(maincom, dict_from_file, otherkeys) + keysprocessed.extend(otherkeys) + + botcommands.register(dict_from_file) + + def bot_dict_use_cases(self, maincom, dict_from_file, process_list): + + for mustbe in process_list: + + # All of the above need to be in the dict if not + if mustbe not in list(dict_from_file.keys()): + dict_from_file[mustbe] = dict() + + # verify if already there, that the key is a dict + if not isinstance(dict_from_file[mustbe], dict): + dict_from_file[mustbe] = dict() + + # Each usecase for the command must have a type, flat files inherit this type + if "type" not in list(dict_from_file[mustbe].keys()): + if "type" in list(dict_from_file.keys()): + dict_from_file[mustbe]["type"] = dict_from_file["type"] + else: + dict_from_file[mustbe]["type"] = "simple" + + # each usecase needs to know if it can be updated. Default is false + if "updates_enabled" not in list(dict_from_file[mustbe].keys()): + dict_from_file[mustbe]["updates_enabled"] = False + if dict_from_file[mustbe]["updates_enabled"]: + if dict_from_file[mustbe]["updates_enabled"] not in ["shared", "user"]: + dict_from_file[mustbe]["updates_enabled"] = "shared" + + # each usecase needs to know if it needs a target + if "target_required" not in list(dict_from_file[mustbe].keys()): + if dict_from_file[mustbe]["type"] in ['target', 'targetplusreason']: + dict_from_file[mustbe]["target_required"] = True + else: + dict_from_file[mustbe]["target_required"] = False + if "target_backup" not in list(dict_from_file[mustbe].keys()): + dict_from_file[mustbe]["target_backup"] = False + if "target_bypass" not in list(dict_from_file[mustbe].keys()): + dict_from_file[mustbe]["target_bypass"] = [] + + # special target reactions + for reason in ['self', 'bot', 'bots', 'offline', 'unknown', 'privmsg', 'diffchannel', 'diffbot']: + if 'react_'+reason not in list(dict_from_file[mustbe].keys()): + dict_from_file[mustbe]['react_'+reason] = False + + # each usecase needs to know if it needs input for fillintheblank + if "blank_required" not in list(dict_from_file[mustbe].keys()): + if dict_from_file[mustbe]["type"] in ['fillintheblank', 'targetplusreason', "translate"]: + dict_from_file[mustbe]["blank_required"] = True + else: + dict_from_file[mustbe]["blank_required"] = False + if "blank_backup" not in list(dict_from_file[mustbe].keys()): + dict_from_file[mustbe]["blank_backup"] = False + if "blank_fail" not in list(dict_from_file[mustbe].keys()): + dict_from_file[mustbe]["blank_fail"] = ["This command requires input."] + if not isinstance(dict_from_file[mustbe]["blank_fail"], list): + dict_from_file[mustbe]["blank_fail"] = [dict_from_file[mustbe]["blank_fail"]] + + if "blank_phrasehandle" not in list(dict_from_file[mustbe].keys()): + dict_from_file[mustbe]["blank_phrasehandle"] = False + if dict_from_file[mustbe]["blank_phrasehandle"]: + if not isinstance(dict_from_file[mustbe]["blank_phrasehandle"], list): + dict_from_file[mustbe]["blank_phrasehandle"] = [dict_from_file[mustbe]["blank_phrasehandle"]] + + if "response_fail" not in list(dict_from_file[mustbe].keys()): + dict_from_file[mustbe]["response_fail"] = False + if dict_from_file[mustbe]["response_fail"]: + if not isinstance(dict_from_file[mustbe]["response_fail"], list): + dict_from_file[mustbe]["response_fail"] = [dict_from_file[mustbe]["response_fail"]] + + # if dict_from_file[mustbe]["updates_enabled"]: + # adjust_nick_array(bot, str(bot.nick), 'long', 'sayings', maincom + "_" + str(mustbe), dict_from_file[mustbe]["responses"], 'startup') + # dict_from_file[mustbe]["responses"] = get_nick_value(bot, str(bot.nick), 'long', 'sayings', maincom + "_" + str(mustbe)) or [] + + # each usecase needs a response + if "responses" not in list(dict_from_file[mustbe].keys()): + dict_from_file[mustbe]["responses"] = [] + + # verify responses are in list form + if not isinstance(dict_from_file[mustbe]["responses"], list): + # TODO read text files + # if dict_from_file[mustbe]["responses"] in bot.memory["botdict"]["tempvals"]['txt_files'].keys(): + # dict_from_file[mustbe]["responses"] = bot.memory["botdict"]["tempvals"]['txt_files'][dict_from_file[mustbe]["responses"]] + if str(dict_from_file[mustbe]["responses"]).startswith(tuple(["https://", "http://"])): + try: + page = requests.get(dict_from_file[mustbe]["responses"], headers=self.header) + except Exception as e: + page = e + page = None + + if page and not str(page.status_code).startswith(tuple(["4", "5"])): + htmlfile = urllib.request.urlopen(dict_from_file[mustbe]["responses"]) + lines = htmlfile.read().splitlines() + dict_from_file[mustbe]["responses"] = lines + else: + dict_from_file[mustbe]["responses"] = [dict_from_file[mustbe]["responses"]] + + # each usecase needs a prefixtext + if "prefixtext" not in list(dict_from_file[mustbe].keys()): + dict_from_file[mustbe]["prefixtext"] = False + if dict_from_file[mustbe]["prefixtext"]: + if not isinstance(dict_from_file[mustbe]["prefixtext"], list): + dict_from_file[mustbe]["prefixtext"] = [dict_from_file[mustbe]["prefixtext"]] + + # each usecase needs a suffixtext + if "suffixtext" not in list(dict_from_file[mustbe].keys()): + dict_from_file[mustbe]["suffixtext"] = False + if dict_from_file[mustbe]["suffixtext"]: + if not isinstance(dict_from_file[mustbe]["suffixtext"], list): + dict_from_file[mustbe]["suffixtext"] = [dict_from_file[mustbe]["suffixtext"]] + + # Translations + if "translations" not in list(dict_from_file[mustbe].keys()): + dict_from_file[mustbe]["translations"] = False + if dict_from_file[mustbe]["translations"]: + if not isinstance(dict_from_file[mustbe]["translations"], list): + dict_from_file[mustbe]["translations"] = [dict_from_file[mustbe]["translations"]] + + # make sure we have the smaller variation list + if "replyvariation" not in list(dict_from_file[mustbe].keys()): + dict_from_file[mustbe]["replyvariation"] = [] + if not isinstance(dict_from_file[mustbe]["replyvariation"], list): + dict_from_file[mustbe]["replyvariation"] = [dict_from_file[mustbe]["replyvariation"]] + + # This is to provide functionality for flat dictionaries responses + if dict_from_file[mustbe]["responses"] == [] and mustbe == "?default": + if "responses" in list(dict_from_file.keys()): + if isinstance(dict_from_file["responses"], list): + dict_from_file[mustbe]["responses"].extend(dict_from_file["responses"]) + else: + dict_from_file[mustbe]["responses"].append(dict_from_file["responses"]) + del dict_from_file["responses"] + + # Verify responses list is not empty + if dict_from_file[mustbe]["responses"] == []: + dict_from_file[mustbe]["responses"].append("No " + str(mustbe) + " responses set for " + str(maincom) + ".") + + # Some commands run query mode + if "search_fail" not in list(dict_from_file[mustbe].keys()): + dict_from_file[mustbe]["search_fail"] = None + if dict_from_file[mustbe]["search_fail"]: + if not isinstance(dict_from_file[mustbe]["search_fail"], list): + dict_from_file[mustbe]["search_fail"] = [dict_from_file[mustbe]["search_fail"]] + + if "selection_allowed" not in list(dict_from_file[mustbe].keys()): + dict_from_file[mustbe]["selection_allowed"] = True + + # Translations + if "randnum" not in list(dict_from_file[mustbe].keys()): + dict_from_file[mustbe]["randnum"] = False + if dict_from_file[mustbe]["randnum"]: + if not isinstance(dict_from_file[mustbe]["randnum"], list): + dict_from_file[mustbe]["randnum"] = [0, 50] + if len(dict_from_file[mustbe]["randnum"]) == 1: + dict_from_file[mustbe]["randnum"] = [0, dict_from_file[mustbe]["randnum"][0]] + + if "hardcoded_channel_block" not in list(dict_from_file[mustbe].keys()): + dict_from_file[mustbe]["hardcoded_channel_block"] = [] + + if "privs" not in list(dict_from_file[mustbe].keys()): + dict_from_file[mustbe]["privs"] = [] + + return dict_from_file + + def setup_dictcoms(self): + botconfig.define_section("SpiceBot_DictComs", SpiceBot_DictComs_MainSection, validate=False) + + +jsoncommands = BotJSONCommands() diff --git a/sopel_modules/SpiceBot/Gif.py b/sopel_modules/SpiceBot/Gif.py index f39ac9c..dab7b39 100644 --- a/sopel_modules/SpiceBot/Gif.py +++ b/sopel_modules/SpiceBot/Gif.py @@ -42,8 +42,8 @@ class BotGif(): gifcfgdir = os.path.join(configsdir, "gifapi") dir_to_scan.append(gifcfgdir) - if len(botconfig.SopelGifSearch.extra): - for extragifcfgdir in botconfig.SopelGifSearch.extra: + if len(botconfig.SpiceBot_Gif.extra): + for extragifcfgdir in botconfig.SpiceBot_Gif.extra: dir_to_scan.append(extragifcfgdir) valid_gif_api_dict = read_directory_json_to_dict(dir_to_scan, "Gif API", "SpiceBot_Gif") @@ -62,13 +62,13 @@ class BotGif(): for validgifapi in list(self.valid_api.keys()): command_dict = { "comtype": "gif_prefix", - "validcoms": validgifapi + "validcoms": [validgifapi] } botcommands.dict['counts'] += 1 botcommands.register(command_dict) def setup_gif(self): - botconfig.define_section("SopelGifSearch", SpiceBot_Gif_MainSection, validate=False) + botconfig.define_section("SpiceBot_Gif", SpiceBot_Gif_MainSection, validate=False) def get_gif(self, searchdict): @@ -90,7 +90,7 @@ class BotGif(): for remx in query_defaults["gifsearchremove"]: searchdict["gifsearch"].remove(remx) - if botconfig.SopelGifSearch.nsfw: + if botconfig.SpiceBot_Gif.nsfw: query_defaults["nsfw"] = True # verify query is there to search diff --git a/sopel_modules/SpiceBot/Tools.py b/sopel_modules/SpiceBot/Tools.py index ef61bee..185241c 100644 --- a/sopel_modules/SpiceBot/Tools.py +++ b/sopel_modules/SpiceBot/Tools.py @@ -352,14 +352,18 @@ def read_directory_json_to_dict(directories, configtypename="Config File", log_f # Close File inf.close() - if filereadgood and isinstance(dict_from_file, dict): - filecount += 1 - slashsplit = str(filepath).split("/") - filename = slashsplit[-1] - filename_base = os.path.basename(filename).rsplit('.', 1)[0] - configs_dict[filename_base] = dict_from_file - else: + # gather file stats + slashsplit = str(filepath).split("/") + filename = slashsplit[-1] + filename_base = os.path.basename(filename).rsplit('.', 1)[0] + + if not filereadgood or not isinstance(dict_from_file, dict): fileopenfail += 1 + else: + filecount += 1 + dict_from_file["filepath"] = str(filepath) + dict_from_file["filename"] = str(filename_base) + configs_dict[filename_base] = dict_from_file if filecount: if logging: diff --git a/sopel_modules/SpiceBot/__init__.py b/sopel_modules/SpiceBot/__init__.py index fac2d5c..03d0ca4 100644 --- a/sopel_modules/SpiceBot/__init__.py +++ b/sopel_modules/SpiceBot/__init__.py @@ -26,6 +26,7 @@ from .Google import * from .Gif import * from .Sherlock import * from .Reddit import * +# from .DictComs import * from .configure import * diff --git a/sopel_modules/SpiceBot/configure.py b/sopel_modules/SpiceBot/configure.py index 2c317c6..473258b 100644 --- a/sopel_modules/SpiceBot/configure.py +++ b/sopel_modules/SpiceBot/configure.py @@ -8,6 +8,7 @@ from .Google import SpiceBot_Google_MainSection from .AI import SpiceBot_AI_MainSection from .Update import SpiceBot_Update_MainSection from .Gif import SpiceBot_Gif_MainSection +from .DictComs import SpiceBot_DictComs_MainSection from .Reddit import SpiceBot_Reddit_MainSection from .Kick import SpiceBot_Kick from .osd import SpiceBot_OSD @@ -48,9 +49,12 @@ def configure(config): config.SpiceBot_Google.configure_setting('search_api', 'api key for google searching') config.define_section("SpiceBot_Gif", SpiceBot_Gif_MainSection, validate=False) - config.SopelGifSearch.configure_setting('extra', 'SpiceBot_Gif API Extra directory') - config.SopelGifSearch.configure_setting('nsfw', 'SpiceBot_Gif API nsfw content') + config.SpiceBot_Gif.configure_setting('extra', 'SpiceBot_Gif API Extra directory') + config.SpiceBot_Gif.configure_setting('nsfw', 'SpiceBot_Gif API nsfw content') config.define_section("SpiceBot_Reddit", SpiceBot_Reddit_MainSection, validate=False) - config.SopelGifSearch.configure_setting('client_id', 'SpiceBot_Reddit client_id') - config.SopelGifSearch.configure_setting('client_secret', 'SpiceBot_Reddit client_secret') + config.SpiceBot_Gif.configure_setting('client_id', 'SpiceBot_Reddit client_id') + config.SpiceBot_Gif.configure_setting('client_secret', 'SpiceBot_Reddit client_secret') + + config.define_section("SpiceBot_DictComs", SpiceBot_DictComs_MainSection, validate=False) + config.SpiceBot_DictComs.configure_setting('extra', 'SpiceBot_DictComs API Extra directory') diff --git a/sopel_modules/SpiceBot/setup.py b/sopel_modules/SpiceBot/setup.py index afab024..e3038b8 100644 --- a/sopel_modules/SpiceBot/setup.py +++ b/sopel_modules/SpiceBot/setup.py @@ -19,6 +19,7 @@ from .Reddit import SpiceBot_Reddit_MainSection from .osd import SopelWrapperOSD, ToolsOSD, SopelOSD, SpiceBot_OSD from .Kick import SopelWrapperKICK, SopelKICK, SpiceBot_Kick from .Gif import SpiceBot_Gif_MainSection +from .DictComs import SpiceBot_DictComs_MainSection from .AI import SpiceBot_AI_MainSection @@ -58,6 +59,8 @@ def setup(bot): setup_reddit(bot) + setup_dictcoms(bot) + def setup_config(bot): logs.log('SpiceBot_Config', "Setting Up Configuration") @@ -147,8 +150,12 @@ def setup_kick(bot): def setup_gif(bot): - bot.config.define_section("SopelGifSearch", SpiceBot_Gif_MainSection, validate=False) + bot.config.define_section("SpiceBot_Gif", SpiceBot_Gif_MainSection, validate=False) def setup_reddit(bot): bot.config.define_section("SpiceBot_Reddit", SpiceBot_Reddit_MainSection, validate=False) + + +def setup_dictcoms(bot): + bot.config.define_section("SpiceBot_DictComs", SpiceBot_DictComs_MainSection, validate=False)