diff --git a/Chatbot.py b/Chatbot.py index cdaa32e..6bedd7f 100644 --- a/Chatbot.py +++ b/Chatbot.py @@ -16,7 +16,7 @@ class Chatbot(object): np.load('markov_dict.npy') except FileNotFoundError: #If there's no initial file, make a new one: - initFile = {'pairs':{self.START_OF_MESSAGE:{'Hoi':1},'Hoi':{self.END_OF_MESSAGE:1}}, 'trairs':{}} + initFile = {'pairs':{self.START_OF_MESSAGE:{'Hoi':1},'Hoi':{self.END_OF_MESSAGE:1}}, 'trairs':{}, 'responsePairs':{}} np.save("markov_dict.npy", initFile) except: print("ERROR! - Could not open or create Markov chat file") @@ -39,9 +39,9 @@ class Chatbot(object): #Learn from a human message #Returns an updated dict - def learnFrom(self, wordDict, message: str): - newPairDict = wordDict['pairs'] - newTrairDict = wordDict['trairs'] + def learnFrom(self, wordDictPairs, wordDictTrairs, message: str): + newPairDict = wordDictPairs + newTrairDict = wordDictTrairs message = self.START_OF_MESSAGE + " " + message + " " + self.END_OF_MESSAGE corpus = message.split() @@ -67,8 +67,43 @@ class Chatbot(object): else: newTrairDict[word_1] = {word_3 : 1} - return {'pairs':newPairDict, 'trairs':newTrairDict}; - + return newPairDict, newTrairDict; + + #Learn to respond to messages + #Returns ??? + def learnResponse(self, wordDictResp, message1: str, message2: str): + STOP_WORDS = ["aan", "achter", "al", "ben", "dan", "dat", "de", "die", "dit", "een", "en", "er", "gehad", "had", "heb", + "hen", "het", "hun", "in", "is", "kon", "met", "na", "of", "om", "ook", "op", "tot", "van", "was", "wat", "zo", + "a", "an", "the", "am", "was", "were", "be", "not", "at"] + + newRespPairDict = wordDictResp + + #Split messages into words and remove stopwords: + message1List = [] + message2List = [] + for word in message1.split(): + if word not in STOP_WORDS: + message1List.append(word) + for word in message2.split(): + if word not in STOP_WORDS: + message2List.append(word) + + #link words of response to words of initial message + for word_1 in message1List: + for word_2 in message2List: + if word_1 in newRespPairDict.keys(): + if word_2 in newRespPairDict[word_1].keys(): + newRespPairDict[word_1][word_2] += 1 + else: + newRespPairDict[word_1][word_2] = 1 + else: + newRespPairDict[word_1] = {word_2 : 1} + + return newRespPairDict + + + + @commands.command(pass_context=True, hidden=True) @asyncio.coroutine def trainmarkovchat(self, ctx, channelId=409751773595566081, nrOfMessages=50): @@ -76,36 +111,61 @@ class Chatbot(object): param channelId: use this channel to train param nrOfMessages: use the x most recent messages """ - + channel=self.bot.get_channel(channelId) #Open current chain: wordDict = np.load('markov_dict.npy').item() #Iterate through the last x messages from the channel: - iterator = channel.history(limit=nrOfMessages, before=None, after=None, reverse=False, around=None) + messageIterator = channel.history(limit=nrOfMessages, before=None, after=None, reverse=False, around=None) + allMessages = [] for i in range(nrOfMessages): - msg = yield from iterator.next() + msg = yield from messageIterator.next() + allMessages.append(msg) + + i = 0 + nextMessageIndex = -10 + #Note: the loop goes from new to old + for msg in allMessages: messageContent = msg.content.replace('(', '').replace(')', '').replace('"', '') messageContent = messageContent.strip() + if messageContent == "": - pass #print("-- Bericht is leeg. Negeer.") + pass # Bericht is leeg. Negeer. elif msg.author.bot: - pass #print("-- Dit is een bot chat. Negeer.") + pass # Dit is een bot chat. Negeer. elif messageContent[0] == "!": - pass #print("-- Dit is een commando. Negeer.") + pass # Dit is een commando. Negeer. elif "```" in messageContent: - pass #print("-- Daar zit een codeblok in. Negeer.") + pass # Daar zit een codeblok in. Negeer. + elif messageContent[0:7] == "http://" or messageContent[0:8] == "https://": + pass # Een link. Negeer. TODO: post af en toe een random link die geen repost is else: - wordDict = self.learnFrom(wordDict, messageContent) + # Usable message! + wordDict['pairs'], wordDict['trairs'] = self.learnFrom(wordDict['pairs'], wordDict['trairs'], messageContent) + + if (i-nextMessageIndex) == 1 and msg.author != nextMessage.author: #the previous usable messages was the one right after this one and was not from the same author + timeDelta = nextMessage.created_at - msg.created_at + #TODO check for different author + if timeDelta.days == 0 and timeDelta.seconds < 3600: #less than an hour difference + #this is counted as a response. learn from it + wordDict['responsePairs'] = self.learnResponse(wordDict['responsePairs'], messageContent, nextMessageContent) + + #save for next iteration: + nextMessage = msg + nextMessageContent = messageContent + nextMessageIndex = i + i += 1 np.save("markov_dict.npy", wordDict) + yield from ctx.channel.send("Ik heb weer wat nieuwe woordjes geleerd. :slight_smile:") @commands.command(pass_context=True, hidden=False) @asyncio.coroutine def reageer(self, ctx): - """UNDER CONSTRUCTION. Laat Stroop iets willekeurigs zeggen. + """UNDER CONSTRUCTION. Laat Stroop ergens op reageren. Stroop leert van onze gesprekken. Hij kan zich nu voordoen als een van ons. Soort van. """ @@ -115,19 +175,30 @@ class Chatbot(object): wordDict = np.load('markov_dict.npy').item() wordPairDict = wordDict['pairs'] wordTrairDict = wordDict['trairs'] + responsePairDict = wordDict['responsePairs'] #TODO store in global variable to minimise IO? + + #Get previous message to respond to + messageIterator = ctx.channel.history(limit=3, before=None, after=None, reverse=False, around=None) + messageToRespondTo = yield from messageIterator.next() #this will be the !reageer command. skip + messageToRespondTo = yield from messageIterator.next() + if messageToRespondTo.content.strip()[0] == '!' or messageToRespondTo.author.bot: #another chance + messageToRespondTo = yield from messageIterator.next() #but it's not a huge problem if he responds to himself + if messageToRespondTo.content.strip()[0] == '!': + wordsToRespondTo = [] + else: + wordsToRespondTo = messageToRespondTo.content.strip().split() + #print("respond to: ", wordsToRespondTo) #Delete command try: yield from ctx.message.delete() except: - print("No permission to delete message") + pass - #print(wordDict) - #Pick a random first word (Which is not an end-of-message) #Usually, we start with a regular , but sometimes we don't - a = random.randint(1,10) + a = random.randint(1,15) if a == 1: first_word = np.random.choice(list(wordPairDict.keys())) while first_word == self.END_OF_MESSAGE: @@ -135,8 +206,6 @@ class Chatbot(object): else: first_word = self.START_OF_MESSAGE chain = [first_word] - #TODO pick a word which responds to the previous message in the channel - #Go through the chain: for i in range(1, MAX_N_WORDS): @@ -145,6 +214,14 @@ class Chatbot(object): #Find the possibilities: pairPossibilities = wordPairDict[chain[-1]].copy() + + #Increase score if it would be a good response: + for userWord in wordsToRespondTo: + if userWord in responsePairDict: + for goodResponseWord in responsePairDict[userWord]: + if goodResponseWord in pairPossibilities.keys(): + pairPossibilities[goodResponseWord] += responsePairDict[userWord][goodResponseWord] + #Increase score for trairs: if i > 1: #we can only compare trairs if we have at least two words already if chain[-2] in wordTrairDict: trairPossibilities = wordTrairDict[chain[-2]].copy() @@ -152,6 +229,7 @@ class Chatbot(object): for word2 in pairPossibilities.keys(): if word2 in trairPossibilities.keys(): pairPossibilities[word2] += trairPossibilities[word2] + #Calculate probabilities and pick a next word: #https://stackoverflow.com/questions/835092/python-dictionary-are-keys-and-values-always-the-same-order possible_words = list(pairPossibilities.keys())