bitbot-3.11-fork/modules/markov.py

186 lines
7.3 KiB
Python
Raw Normal View History

import random, re, threading
2019-09-20 15:56:00 +00:00
from src import ModuleManager, utils
NO_MARKOV = "Markov chains not enabled in this channel"
@utils.export("channelset", utils.IntRangeSetting(0, 100, "markov-chance",
"0 to 100 percent chance of markov chains being generated at random"))
2019-09-20 15:56:00 +00:00
class Module(ModuleManager.BaseModule):
2019-09-23 09:41:01 +00:00
_load_thread = None
2019-09-20 16:15:21 +00:00
def on_load(self):
2019-09-20 15:57:17 +00:00
if not self.bot.database.has_table("markov"):
self.bot.database.execute("""CREATE TABLE markov
2019-09-20 15:56:00 +00:00
(channel_id INTEGER, first_word TEXT, second_word TEXT,
third_word TEXT, frequency INT,
FOREIGN KEY (channel_id) REFERENCES channels(channel_id),
2019-09-20 15:58:42 +00:00
PRIMARY KEY (channel_id, first_word, second_word))""")
2019-09-20 15:56:00 +00:00
@utils.hook("command.regex")
@utils.kwarg("expect_output", False)
2019-10-29 18:09:57 +00:00
@utils.kwarg("ignore_action", True)
@utils.kwarg("command", "markov-trigger")
@utils.kwarg("pattern", re.compile(".+"))
2019-09-20 15:56:00 +00:00
def channel_message(self, event):
markov_chance = event["target"].get_setting("markov-chance", 0)
if random.randint(0, 99) < markov_chance:
words = event["message"].split()
random.shuffle(words)
for word in words:
out = self._generate(event["target"].id, [word])
if out:
event["stdout"].write(out)
break
if event["target"].get_setting("markov", False):
self._create(event["target"].id, event["match"].group(0))
@utils.hook("received.command.markovlog")
@utils.kwarg("min_args", 1)
@utils.kwarg("permission", "markovlog")
@utils.kwarg("help", "Load a message-only newline-delimited log in to this "
"channel's markov chain")
def load_log(self, event):
if not event["target"].get_setting("markov", False):
raise utils.EventError(NO_MARKOV)
if not self._load_thread == None:
raise utils.EventError("Log loading already in progress")
page = utils.http.request(event["args_split"][0])
if page.code == 200:
event["stdout"].write("Importing...")
self._load_thread = threading.Thread(target=self._load_loop,
args=[event["target"].id, page.decode()])
self._load_thread.daemon = True
self._load_thread.start()
else:
event["stderr"].write("Failed to load log (%d)" % page.code)
def _load_loop(self, channel_id, data):
for line in data.decode("utf8", errors="ignore").split("\n"):
self.bot.trigger(self._create_factory(channel_id, line.strip()))
self._load_thread = None
def _create_factory(self, channel_id, line):
return lambda: self._create(channel_id, line)
def _create(self, channel_id, line):
if utils.http.REGEX_URL.search(line):
return
words = list(filter(None, line.split(" ")))
words = [word.lower() for word in words]
2019-09-20 15:56:00 +00:00
words_n = len(words)
if not words_n > 2:
return
inserts = []
inserts.append([None, None, words[0]])
inserts.append([None, words[0], words[1]])
2019-09-20 15:56:00 +00:00
for i in range(words_n-2):
inserts.append(words[i:i+3])
2019-09-20 15:56:00 +00:00
inserts.append([words[-2], words[-1], None])
2019-09-20 15:56:00 +00:00
for insert in inserts:
frequency = self.bot.database.execute_fetchone("""SELECT
frequency FROM markov WHERE channel_id=? AND first_word=?
AND second_word=? AND third_word=?""",
[channel_id]+insert)
frequency = (frequency or [0])[0]+1
2019-09-20 15:56:00 +00:00
self.bot.database.execute(
"INSERT OR REPLACE INTO markov VALUES (?, ?, ?, ?, ?)",
[channel_id]+insert+[frequency])
2019-09-20 15:56:00 +00:00
def _choose(self, words):
words, frequencies = list(zip(*words))
return random.choices(words, weights=frequencies, k=1)[0]
@utils.hook("received.command.markov")
@utils.kwarg("channel_only", True)
2019-09-20 17:08:22 +00:00
@utils.kwarg("help", "Generate a markov chain for the current channel")
@utils.kwarg("usage", "[first-word]")
2019-09-20 17:08:22 +00:00
def markov(self, event):
self._markov_for(event["target"], event["stdout"], event["stderr"],
2019-09-30 15:27:29 +00:00
first_words=event["args_split"][:])
2019-09-20 17:08:22 +00:00
@utils.hook("received.command.markovfor")
2019-09-20 17:08:22 +00:00
@utils.kwarg("min_args", 1)
@utils.kwarg("help", "Generate a markov chain for a given channel")
@utils.kwarg("usage", "<channel> [first-word]")
2019-09-20 17:08:22 +00:00
def markov_for(self, event):
if event["args_split"][0] in event["server"].channels:
channel = event["server"].channels.get(event["args_split"][0])
if not channel.has_user(event["user"]):
event["check_assert"](utils.Check("permission", "markovfor"))
self._markov_for(channel, event["stdout"], event["stderr"],
2019-09-30 15:27:29 +00:00
first_words=event["args_split"][1:])
2019-09-20 17:08:22 +00:00
else:
event["stderr"].write("Unknown channel")
2019-09-30 15:27:29 +00:00
def _markov_for(self, channel, stdout, stderr, first_words):
2019-09-20 17:08:22 +00:00
if not channel.get_setting("markov", False):
stderr.write(NO_MARKOV)
else:
2019-09-30 15:27:29 +00:00
out = self._generate(channel.id, first_words)
2019-09-20 17:08:22 +00:00
if not out == None:
stdout.write(out)
else:
stderr.write("Failed to generate markov chain")
2019-09-30 15:27:29 +00:00
def _generate(self, channel_id, first_words):
if not first_words:
first_words = self.bot.database.execute_fetchall("""SELECT
third_word, frequency FROM markov WHERE channel_id=? AND
first_word IS NULL AND second_word IS NULL AND third_word
NOT NULL""", [channel_id])
if not first_words:
return None
first_word = self._choose(first_words)
second_words = self.bot.database.execute_fetchall("""SELECT
third_word, frequency FROM markov WHERE channel_id=? AND
first_word IS NULL AND second_word=? AND third_word NOT NULL""",
[channel_id, first_word])
if not second_words:
return None
second_word = self._choose(second_words)
words = [first_word, second_word]
elif len(first_words) == 1:
first_word = first_words[0].lower()
second_two_words = self.bot.database.execute_fetchall("""SELECT
second_word, third_word, frequency FROM markov WHERE
channel_id=? AND first_word=? AND second_word NOT NULL AND
third_word NOT NULL""", [channel_id, first_word])
if not second_two_words:
return None
second_word, third_word = self._choose(
[[[s, t], f] for s, t, f in second_two_words])
words = [first_word, second_word, third_word]
else:
words = [word.lower() for word in first_words]
2019-09-20 15:56:00 +00:00
for i in range(30):
two_words = words[-2:]
2019-09-20 16:12:17 +00:00
third_words = self.bot.database.execute_fetchall("""SELECT
third_word, frequency FROM markov WHERE channel_id=? AND
first_word=? AND second_word=?""", [channel_id]+two_words)
2019-10-04 09:12:52 +00:00
if not third_words:
break
2019-09-20 15:56:00 +00:00
third_word = self._choose(third_words)
if third_word == None:
break
2019-09-20 15:56:00 +00:00
words.append(third_word)
if words == first_words:
return None
2019-09-20 15:56:00 +00:00
return " ".join(words)