From c2b7cf733847342993a8cb4cb3a5332384eb6bdb Mon Sep 17 00:00:00 2001 From: JP Date: Sun, 1 Jul 2018 18:15:45 +0200 Subject: [PATCH] Chatbot v1 --- .gitignore | 3 +- Chatbot.py | 126 +++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 3 +- Stroopwafel.py | 2 + 4 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 Chatbot.py diff --git a/.gitignore b/.gitignore index 383adc3..0496188 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ tijd/ test.png password.txt stroopwafel.token -UpgradeLog\.htm +UpgradeLog\.htm +markov_dict.npy diff --git a/Chatbot.py b/Chatbot.py new file mode 100644 index 0000000..e24115a --- /dev/null +++ b/Chatbot.py @@ -0,0 +1,126 @@ +import asyncio +import discord +from discord.ext import commands +import numpy as np + +class Chatbot(object): + """Een heuse chatbot. Under construction...""" + + def __init__(self, bot): + self.bot = bot + self.voice_states = {} + self.END_OF_MESSAGE = "" + try: + np.load('markov_dict.npy') + except FileNotFoundError: + #If there's no initial file, make a new one: + initFile = {'Hoi':{self.END_OF_MESSAGE : 1}} + np.save("markov_dict.npy", initFile) + except: + print("ERROR! - Could not open or create Markov chat file") + def __check(self, ctx): + return True + + #TODO laat Stroop reageren op persoonlijke berichten + + #Make word pairs: + def makePairs(self, corpus): + for i in range(len(corpus)-1): + yield (corpus[i], corpus[i+1]) + + #Learn from a human message + #Returns an updated dict + def learnFrom(self, initialWordDict, message: str): + newWordDict = initialWordDict + + message = message + " " + self.END_OF_MESSAGE + corpus = message.split() + pairs = self.makePairs(corpus) + + #Add wordcounts to dict + for word_1, word_2 in pairs: + if word_1 in newWordDict.keys(): + if word_2 in newWordDict[word_1].keys(): + newWordDict[word_1][word_2] += 1 + else: + newWordDict[word_1][word_2] = 1 + else: + newWordDict[word_1] = {word_2 : 1} + return newWordDict; + + @commands.command(pass_context=True, hidden=True) + @asyncio.coroutine + def trainmarkovchat(self, ctx, channelId=409751773595566081, nrOfMessages=50): + """Train that chain, jermaine + 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) + for i in range(nrOfMessages): + msg = yield from iterator.next() + messageContent = msg.content.strip() + if messageContent == "": + pass #print("-- Bericht is leeg. Negeer.") + elif msg.author.bot: + pass #print("-- Dit is een bot chat. Negeer.") + elif messageContent[0] == "!": + pass #print("-- Dit is een commando. Negeer.") + elif "```" in messageContent: + pass #print("-- Daar zit een codeblok in. Negeer.") + else: + wordDict = self.learnFrom(wordDict, messageContent) + + 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. + Stroop leert van onze gesprekken. Hij kan zich nu voordoen als een van ons. Soort van. + """ + + MAX_N_WORDS = 100 #Don't use more words than this + + #Get dictionary/Markov chain + wordDict = np.load('markov_dict.npy').item() + #TODO store in global variable to minimise IO? + + #Delete command + try: + yield from ctx.message.delete() + except: + print("No permission to delete message") + + #print(wordDict) + + #Pick a random first word (Which is not an end-of-message) + #TODO pick a word which responds to the previous message in the channel + first_word = np.random.choice(list(wordDict.keys())) + while first_word == self.END_OF_MESSAGE: #or first_word.islower() ??? + first_word = np.random.choice(list(wordDict.keys())) + chain = [first_word] + + #Go through the chain: + for i in range(MAX_N_WORDS): + if chain[-1] == self.END_OF_MESSAGE: + break + #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(wordDict[chain[-1]].keys()) + weights = np.array(list(wordDict[chain[-1]].values())) + probs = weights / weights.sum() + chain.append(np.random.choice(possible_words, p=probs)) + + chain = chain[:-1] #remove + + #print(' '.join(chain)) + yield from ctx.channel.send(' '.join(chain)) + \ No newline at end of file diff --git a/README.md b/README.md index 48645c2..9a28dc2 100644 --- a/README.md +++ b/README.md @@ -24,4 +24,5 @@ Verder zoek je het maar lekker zelf uit. Of installeer deze requirements: * `praw` * `oauth2` * `sympy` -* `asyncio` \ No newline at end of file +* `asyncio` +* `numpy` \ No newline at end of file diff --git a/Stroopwafel.py b/Stroopwafel.py index 1dcebd7..d94c156 100644 --- a/Stroopwafel.py +++ b/Stroopwafel.py @@ -5,6 +5,7 @@ import Porno import Shitpost import General import Cryptocoin +import Chatbot import logging import importlib @@ -19,6 +20,7 @@ bot.add_cog(Shitpost.Shitpost(bot)) bot.add_cog(Porno.Porno(bot)) bot.add_cog(General.General(bot)) bot.add_cog(Cryptocoin.Cryptocoin(bot)) +bot.add_cog(Chatbot.Chatbot(bot)) tokenfile = open("stroopwafel.token","r") token = tokenfile.readline()