SpiceBot-migrated/sopel_modules/SpiceBot/Commands.py

367 lines
14 KiB
Python

# coding=utf8
from __future__ import unicode_literals, absolute_import, division, print_function
"""
This is the SpiceBot Commands system.
This Class stores commands in an easy to access manner
"""
import sopel
import os
import threading
import spicemanip
from .Logs import logs
from .Config import config as botconfig
from .Database import db as botdb
from .Events import events
from .Prerun import prerun
from .Tools import letters_in_string, similar_list
class BotCommands():
"""This Logs all commands known to the bot"""
def __init__(self):
self.lock = threading.Lock()
self.dict = {
"counts": 0,
"commands": {
'module': {},
'nickname': {},
'rule': {}
},
'disabled': {}
}
def find_command_type(self, command):
for commandstype in self.dict['commands'].keys():
if commandstype not in ['rule']:
for com in self.dict['commands'][commandstype].keys():
if com.lower() == command.lower():
return commandstype
return None
def get_commands_disabled(self, channel):
if not len(list(self.dict['disabled'])):
self.dict['disabled'] = botdb.get_channel_value(channel, 'commands_disabled') or {}
return self.dict['disabled']
def check_commands_disabled(self, command, channel):
if command in self.get_commands_disabled(channel).keys():
return True
else:
return False
def get_realcom(self, command, trigger_command_type):
realcom = command
if trigger_command_type not in self.dict['commands'].keys():
return realcom
if command not in self.dict['commands'][trigger_command_type].keys():
return realcom
if "aliasfor" in self.dict['commands'][trigger_command_type][command].keys():
realcom = self.dict['commands'][trigger_command_type][command]
return realcom
def set_command_disabled(self, command, channel, timestamp, reason, bywhom):
if not len(list(self.dict['disabled'])):
self.dict['disabled'] = botdb.get_channel_value(channel, 'commands_disabled') or {}
self.dict['disabled'][command] = {"reason": reason, "timestamp": timestamp, "disabledby": bywhom}
botdb.set_channel_value(channel, 'commands_disabled', self.dict['disabled'])
def unset_command_disabled(self, command, channel):
if not len(list(self.dict['disabled'])):
self.dict['disabled'] = botdb.get_channel_value(channel, 'commands_disabled') or {}
if command in self.dict['disabled'].keys():
del self.dict['disabled'][command]
botdb.set_channel_value(channel, 'commands_disabled', self.dict['disabled'])
def register(self, command_dict):
self.lock.acquire()
command_type = command_dict["comtype"]
validcoms = command_dict["validcoms"]
if not isinstance(validcoms, list):
validcoms = [validcoms]
if command_type not in self.dict['commands'].keys():
self.dict['commands'][command_type] = dict()
dict_from_file = dict()
# default command to filename
if "validcoms" not in dict_from_file.keys():
dict_from_file["validcoms"] = validcoms
maincom = dict_from_file["validcoms"][0]
if len(dict_from_file["validcoms"]) > 1:
comaliases = spicemanip.main(dict_from_file["validcoms"], '2+', 'list')
else:
comaliases = []
self.dict['commands'][command_type][maincom] = dict_from_file
for comalias in comaliases:
if comalias not in self.dict['commands'][command_type].keys():
self.dict['commands'][command_type][comalias] = {"aliasfor": maincom}
self.lock.release()
def module_directory_list(self):
filepathlisting = []
# main Modules directory
main_dir = os.path.dirname(os.path.abspath(sopel.__file__))
modules_dir = os.path.join(main_dir, 'modules')
filepathlisting.append(modules_dir)
# Home Directory
home_modules_dir = os.path.join(botconfig.homedir, 'modules')
if os.path.isdir(home_modules_dir):
filepathlisting.append(home_modules_dir)
# pypi installed
try:
import sopel_modules
for plugin_dir in set(sopel_modules.__path__):
for pathname in os.listdir(plugin_dir):
pypi_modules_dir = os.path.join(plugin_dir, pathname)
filepathlisting.append(pypi_modules_dir)
except Exception as e:
logs.log('SpiceBot_CommandsQuery', "sopel_modules not loaded :" + str(e))
# Extra directories
for directory in botconfig.extra:
filepathlisting.append(directory)
return filepathlisting
def module_files_list(self):
filepathlisting = self.module_directory_list()
filepathlist = []
for directory in filepathlisting:
for pathname in os.listdir(directory):
path = os.path.join(directory, pathname)
if (os.path.isfile(path) and path.endswith('.py') and not path.startswith('_')):
if pathname not in ["SpiceBot_dummycommand.py"]:
filepathlist.append(str(path))
# CoreTasks
main_dir = os.path.dirname(os.path.abspath(sopel.__file__))
ct_path = os.path.join(main_dir, 'coretasks.py')
filepathlist.append(ct_path)
return filepathlist
def module_files_parse(self):
filepathlist = self.module_files_list()
for modulefile in filepathlist:
module_file_lines = []
module_file = open(modulefile, 'r')
lines = module_file.readlines()
for line in lines:
module_file_lines.append(line)
module_file.close()
detected_lines = []
for line in module_file_lines:
if str(line).startswith("@"):
line = str(line)[1:]
if str(line).startswith(tuple(["commands", "module.commands", "sopel.module.commands"])):
line = str(line).split("commands")[-1]
line = "commands" + line
detected_lines.append(line)
elif str(line).startswith(tuple(["nickname_commands", "module.nickname_commands", "sopel.module.nickname_commands"])):
line = str(line).split("nickname_commands")[-1]
line = "nickname_commands" + line
detected_lines.append(line)
elif str(line).startswith(tuple(["rule", "module.rule", "sopel.module.rule"])):
line = str(line).split("rule")[-1]
line = "rule" + line
else:
line = None
if line:
detected_lines.append(line)
if len(detected_lines):
filelinelist = []
currentsuccesslines = 0
for detected_line in detected_lines:
# Commands
if str(detected_line).startswith("commands"):
comtype = "module"
validcoms = eval(str(detected_line).split("commands")[-1])
elif str(detected_line).startswith("nickname_commands"):
comtype = "nickname"
validcoms = eval(str(detected_line).split("nickname_commands")[-1])
elif str(detected_line).startswith("rule"):
comtype = "rule"
validcoms = eval(str(detected_line).split("rule")[-1])
if isinstance(validcoms, tuple):
validcoms = list(validcoms)
else:
validcoms = [validcoms]
for regexcom in ["(.*)", '^\?(.*)']:
if regexcom in validcoms:
while regexcom in validcoms:
validcoms.remove(regexcom)
if len(validcoms):
validcomdict = {"comtype": comtype, "validcoms": validcoms}
filelinelist.append(validcomdict)
currentsuccesslines += 1
if currentsuccesslines:
self.dict['counts'] += 1
if len(filelinelist):
for atlinefound in filelinelist:
self.register(atlinefound)
commands = BotCommands()
@events.check_ready([events.BOT_COMMANDSQUERY])
@prerun.prerun('nickname')
@sopel.module.nickname_commands('(.*)')
def query_detection_nick(bot, trigger):
# command issued, check if valid
if not trigger.sb['com'] or not len(trigger.sb['com']):
return
if not trigger.sb['com'][0] == "?":
return
trigger.sb['com'] = trigger.sb['com'][1:]
if not letters_in_string(trigger.sb['com']):
return
commands_list = dict()
for com in commands.dict['commands']['nickname'].keys():
if com not in commands_list.keys():
commands_list[com] = commands.dict['commands']['nickname'][com]
if trigger.sb['com'].endswith("+"):
trigger.sb['com'] = trigger.sb['com'][:-1]
if not trigger.sb['com'] or not len(trigger.sb['com']):
return
if trigger.sb['com'].lower() not in list(commands_list.keys()):
dispmsg = ["Cannot find any alias " + bot.nick + " commands: No valid commands match " + str(trigger.sb['com']) + "."]
closestmatches = similar_list(trigger.sb['com'], list(commands_list.keys()), 10, 'reverse')
if len(closestmatches):
dispmsg.append("The following " + bot.nick + " commands match " + str(trigger.sb['com']) + ": " + spicemanip.main(closestmatches, 'andlist') + ".")
bot.osd(dispmsg, trigger.nick, 'notice')
return
realcom = trigger.sb['com']
if "aliasfor" in commands_list[trigger.sb['com']].keys():
realcom = commands_list[trigger.sb['com']]["aliasfor"]
validcomlist = commands_list[realcom]["validcoms"]
bot.osd("The following " + bot.nick + " commands match " + str(trigger.sb['com']) + ": " + spicemanip.main(validcomlist, 'andlist') + ".", trigger.nick, 'notice')
return
if trigger.sb['com'].endswith("?"):
trigger.sb['com'] = trigger.sb['com'][:-1]
if not trigger.sb['com'] or not len(trigger.sb['com']):
return
closestmatches = similar_list(trigger.sb['com'], list(commands_list.keys()), 10, 'reverse')
if not len(closestmatches):
bot.osd("Cannot find any similar " + bot.nick + " commands for " + str(trigger.sb['com']) + ".", trigger.nick, 'notice')
else:
bot.osd("The following " + bot.nick + " commands may match " + str(trigger.sb['com']) + ": " + spicemanip.main(closestmatches, 'andlist') + ".", trigger.nick, 'notice')
return
commandlist = []
for command in list(commands_list.keys()):
if command.lower().startswith(str(trigger.sb['com']).lower()):
commandlist.append(command)
if not len(commandlist):
bot.osd("No " + bot.nick + " commands start with " + str(trigger.sb['com']) + ".", trigger.nick, 'notice')
else:
bot.osd("The following " + bot.nick + " commands start with " + str(trigger.sb['com']) + ": " + spicemanip.main(commandlist, 'andlist') + ".", trigger.nick, 'notice')
@events.check_ready([events.BOT_COMMANDSQUERY])
@prerun.prerun('query')
@sopel.module.rule('^\?(.*)')
def query_detection(bot, trigger):
# command issued, check if valid
if not trigger.sb['com'] or not len(trigger.sb['com']):
return
if not letters_in_string(trigger.sb['com']):
return
commands_list = dict()
for commandstype in commands.dict['commands'].keys():
if commandstype not in ['rule', 'nickname']:
for com in commands.dict['commands'][commandstype].keys():
if com not in commands_list.keys():
commands_list[com] = commands.dict['commands'][commandstype][com]
if trigger.sb['com'][:-1] == "+":
trigger.sb['com'] = trigger.sb['com'][:-1]
if not trigger.sb['com'] or not len(trigger.sb['com']):
return
if trigger.sb['com'].lower() not in list(commands_list.keys()):
dispmsg = ["Cannot find any alias commands: No valid commands match " + str(trigger.sb['com']) + "."]
closestmatches = similar_list(trigger.sb['com'], list(commands_list.keys()), 10, 'reverse')
if len(closestmatches):
dispmsg.append("The following commands match " + str(trigger.sb['com']) + ": " + spicemanip.main(closestmatches, 'andlist') + ".")
bot.osd(dispmsg, trigger.nick, 'notice')
return
realcom = trigger.sb['com']
if "aliasfor" in commands_list[trigger.sb['com']].keys():
realcom = commands_list[trigger.sb['com']]["aliasfor"]
validcomlist = commands_list[realcom]["validcoms"]
bot.osd("The following commands match " + str(trigger.sb['com']) + ": " + spicemanip.main(validcomlist, 'andlist') + ".", trigger.nick, 'notice')
return
if trigger.sb['com'][:-1] == "?":
trigger.sb['com'] = trigger.sb['com'][:-1]
if not trigger.sb['com'] or not len(trigger.sb['com']):
return
closestmatches = similar_list(trigger.sb['com'], list(commands_list.keys()), 10, 'reverse')
if not len(closestmatches):
bot.osd("Cannot find any similar commands for " + str(trigger.sb['com']) + ".", trigger.nick, 'notice')
else:
bot.osd("The following commands may match " + str(trigger.sb['com']) + ": " + spicemanip.main(closestmatches, 'andlist') + ".", trigger.nick, 'notice')
return
commandlist = []
for command in list(commands_list.keys()):
if command.lower().startswith(str(trigger.sb['com']).lower()):
commandlist.append(command)
if not len(commandlist):
bot.osd("No commands start with " + str(trigger.sb['com']) + ".", trigger.nick, 'notice')
else:
bot.osd("The following commands start with " + str(trigger.sb['com']) + ": " + spicemanip.main(commandlist, 'andlist') + ".", trigger.nick, 'notice')