def execute(self, message): """ :type message: IrcMessage """ #Immediately check if there's any parameters, to prevent useless work if message.messagePartsLength == 0: message.reply("Please provide a term to search for. See '{}help {}' for an explanation how to use this command".format(message.bot.commandPrefix, message.trigger), "say") return searchType = message.messageParts[0].lower() addExtendedInfo = message.trigger == 'netrunner' #Check for update command before file existence, to prevent message that card file is missing after update, which doesn't make much sense if searchType == 'update' or searchType == 'forceupdate': if self.areCardfilesBeingUpdated: replytext = "I'm already updating!" elif not message.bot.isUserAdmin(message.user, message.userNickname, message.userAddress): replytext = "Sorry, only admins can use my update function" elif not searchType == 'forceupdate' and not self.shouldUpdate(): replytext = "The last update check was done pretty recently, there's no need to check again so soon" else: replytext = self.updateCardFile()[1] #Since we're checking now, set the automatic check to start counting from now on self.resetScheduledFunctionGreenlet() message.reply(replytext, "say") return #Check if the data file even exists elif not os.path.exists(os.path.join(GlobalStore.scriptfolder, 'data', 'NetrunnerCards.json')): if self.areCardfilesBeingUpdated: message.reply("I don't have my card database, but I'm solving that problem as we speak! Try again in, oh, 10, 15 seconds") else: message.reply("Sorry, I don't appear to have my card database. I'll try to retrieve it though! Give me 20 seconds, tops") gevent.spawn(self.updateCardFile) self.resetScheduledFunctionGreenlet() return #If we reached here, we're gonna search through the card store searchDict = {} # If there is an actual search (with colon key-value separator OR a random card is requested with specific search requirements if (searchType == 'search' and ':' in message.message) or (searchType == 'random' and message.messagePartsLength > 1): #Advanced search! if message.messagePartsLength <= 1: message.reply("Please provide an advanced search query too, in JSON format, so 'key1: value1, key2: value2'") return #Turn the search string (not the argument) into a usable dictionary, case-insensitive, searchDict = StringUtil.stringToDict(" ".join(message.messageParts[1:]).lower(), True) if len(searchDict) == 0: message.reply("That is not a valid search query. It should be entered like JSON, so 'name: Wall of Thorns, type: ICE,...'. ") return #If the searchtype is just 'random', don't set a 'name' field so we don't go through all the cards first # Otherwise, set the whole message as the 'name' search, since that's the default search elif not searchType.startswith('random'): searchDict['title'] = message.message.lower() #Correct some values, to make searching easier (so a search for 'set' or 'sets' both work) searchTermsToCorrect = {'setname': ['set', 'sets'], 'flavor': ['flavour'], 'title': ['name']} for correctTerm, listOfWrongterms in searchTermsToCorrect.iteritems(): for wrongTerm in listOfWrongterms: if wrongTerm in searchDict: if correctTerm not in searchDict: searchDict[correctTerm] = searchDict[wrongTerm] searchDict.pop(wrongTerm) #Turn the search strings into actual regexes regexDict = {} errors = [] for attrib, query in searchDict.iteritems(): try: #Since the query is a string, and the card data is unicode, convert the query to unicode before turning it into a regex regex = re.compile(unicode(query, encoding='utf8'), re.IGNORECASE) except (re.error, SyntaxError) as e: self.logError("[Netrunner] Regex error when trying to parse '{}': {}".format(query, e)) errors.append(attrib) except UnicodeDecodeError as e: self.logError("[Netrunner] Unicode error in key '{}': {}".format(attrib, e)) errors.append(attrib) else: regexDict[attrib] = regex #If there were errors parsing the regular expressions, don't continue, to prevent errors further down if len(errors) > 0: #If there was only one search element to begin with, there's no need to specify if len(searchDict) == 1: message.reply("An error occurred when trying to parse your search query. Please check if it is a valid regular expression, and that there are no non-UTF8 characters") #If there were more elements but only one error, specify elif len(errors) == 1: message.reply("An error occurred while trying to parse the query for the '{}' field. Please check if it is a valid regular expression without non-UTF8 characters".format(errors[0])) #Multiple errors, list them all else: message.reply("Errors occurred while parsing attributes: {}. Please check your search query for errors".format(", ".join(errors))) return #All entered data is valid, look through the stored cards with open(os.path.join(GlobalStore.scriptfolder, 'data', 'NetrunnerCards.json'), 'r') as jsonfile: cardstore = json.load(jsonfile) for index in xrange(0, len(cardstore)): carddata = cardstore.pop(0) #Then check if the rest of the attributes match for attrib in regexDict: if attrib not in carddata or not regexDict[attrib].search(carddata[attrib]): #If the wanted attribute is either not in the card, or it doesn't match, throw it out break #The else-block of a for-loop is executed when a for-loop isn't broken out of. So if everything matches, we get here else: cardstore.append(carddata) numberOfCardsFound = len(cardstore) #Pick a random card if needed and possible if searchType.startswith('random') and numberOfCardsFound > 0: cardstore = [random.choice(cardstore)] numberOfCardsFound = 1 if numberOfCardsFound == 0: replytext = "Sorry, no card matching your query was found" elif numberOfCardsFound == 1: replytext = self.getFormattedCardInfo(cardstore[0], addExtendedInfo) else: nameMatchedCardFound = False replytext = "" #If there was a name search, check if the literal name is in the resulting cards if 'title' in searchDict: titleMatchIndex = None for index, card in enumerate(cardstore): if card['title'].lower() == searchDict['title']: titleMatchIndex = index break if titleMatchIndex: replytext = self.getFormattedCardInfo(cardstore[titleMatchIndex], addExtendedInfo) cardstore.pop(titleMatchIndex) numberOfCardsFound -= 1 nameMatchedCardFound = True #Pick some cards to show maxCardsToList = 15 if numberOfCardsFound > maxCardsToList: cardstore = random.sample(cardstore, maxCardsToList) cardnameText = "" for card in cardstore: cardnameText += card['title'].encode('utf-8') + "; " cardnameText = cardnameText[:-2] if nameMatchedCardFound: replytext += " ({:,} more match{} found: ".format(numberOfCardsFound, 'es' if numberOfCardsFound > 1 else '') else: replytext += "Your search returned {:,} cards: ".format(numberOfCardsFound) replytext += cardnameText if numberOfCardsFound > maxCardsToList: replytext += " and {:,} more".format(numberOfCardsFound - maxCardsToList) #Since the extra results list is bracketed when a literal match was also found, it needs a closing bracket if nameMatchedCardFound: replytext += ")" re.purge() #Clear the stored regexes, since we don't need them anymore message.reply(replytext)