Add random reply chance to markov bot
Whenever someone says something, there's a chance that markov will interject his opinion. Users can also set the chance between 0.0 and the default value (in the config) if they want to see markov replies less often. Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
@@ -3,6 +3,7 @@ from collections import defaultdict
|
|||||||
import dataclasses
|
import dataclasses
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import math
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import random
|
import random
|
||||||
from typing import Any, List, Mapping, Sequence
|
from typing import Any, List, Mapping, Sequence
|
||||||
@@ -35,8 +36,9 @@ def windows(items: Sequence[Any], size: int):
|
|||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class Chain:
|
class Chain:
|
||||||
def __init__(self, order: int, path: Path):
|
def __init__(self, order: int, chance: float, path: Path):
|
||||||
self.order = order
|
self.order = order
|
||||||
|
self.reply_chance = chance
|
||||||
self.path = path
|
self.path = path
|
||||||
self.__cache = chain_default()
|
self.__cache = chain_default()
|
||||||
self.__last_access = 0.0
|
self.__last_access = 0.0
|
||||||
@@ -85,6 +87,10 @@ class Chain:
|
|||||||
return
|
return
|
||||||
with open(self.path) as fp:
|
with open(self.path) as fp:
|
||||||
log.info("Loading markov chain from %s", self.path)
|
log.info("Loading markov chain from %s", self.path)
|
||||||
|
obj = json.load(fp)
|
||||||
|
|
||||||
|
# Load the save object
|
||||||
|
self.reply_chance = obj["reply_chance"]
|
||||||
self.__cache = defaultdict(
|
self.__cache = defaultdict(
|
||||||
chain_inner_default,
|
chain_inner_default,
|
||||||
{
|
{
|
||||||
@@ -95,7 +101,7 @@ class Chain:
|
|||||||
for word, weight in value.items()
|
for word, weight in value.items()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
for key, value in json.load(fp).items()
|
for key, value in obj["chain"]
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.__dirty = False
|
self.__dirty = False
|
||||||
@@ -106,18 +112,21 @@ class Chain:
|
|||||||
if self.__dirty:
|
if self.__dirty:
|
||||||
log.info("Saving markov chain to %s", self.path)
|
log.info("Saving markov chain to %s", self.path)
|
||||||
self.path.parent.mkdir(parents=True, exist_ok=True)
|
self.path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
with open(self.path, "w") as fp:
|
# Build the save object
|
||||||
json.dump(
|
obj = {
|
||||||
{
|
"reply_chance": self.reply_chance,
|
||||||
|
"chain": {
|
||||||
key: {
|
key: {
|
||||||
("" if word is None else word): weight
|
("" if word is None else word): weight
|
||||||
for word, weight in value.items()
|
for word, weight in value.items()
|
||||||
}
|
}
|
||||||
for key, value in self.__cache.items()
|
for key, value in self.__cache.items()
|
||||||
},
|
},
|
||||||
fp,
|
}
|
||||||
)
|
with open(self.path, "w") as fp:
|
||||||
|
json.dump(obj, fp)
|
||||||
self.__dirty = False
|
self.__dirty = False
|
||||||
|
|
||||||
if not retain:
|
if not retain:
|
||||||
log.debug("Pruning markov chain %s from memory", self.path)
|
log.debug("Pruning markov chain %s from memory", self.path)
|
||||||
self.clear_cache()
|
self.clear_cache()
|
||||||
@@ -162,6 +171,7 @@ class Markov(Plugin):
|
|||||||
self.order = int(self.plugin_config.get("order", 1))
|
self.order = int(self.plugin_config.get("order", 1))
|
||||||
self.data_path = Path(self.plugin_config.get("data_path", "data/markov"))
|
self.data_path = Path(self.plugin_config.get("data_path", "data/markov"))
|
||||||
self.save_every = int(self.plugin_config.get("save_every", 300))
|
self.save_every = int(self.plugin_config.get("save_every", 300))
|
||||||
|
self.reply_chance = float(self.plugin_config.get("reply_chance", 0.01))
|
||||||
self.__chains = {}
|
self.__chains = {}
|
||||||
self.__save_loop_task = None
|
self.__save_loop_task = None
|
||||||
self.__saving = asyncio.Lock()
|
self.__saving = asyncio.Lock()
|
||||||
@@ -180,17 +190,22 @@ class Markov(Plugin):
|
|||||||
elif line[0] != "!":
|
elif line[0] != "!":
|
||||||
# ignore other commands
|
# ignore other commands
|
||||||
self.add(channel, who.nick, line)
|
self.add(channel, who.nick, line)
|
||||||
|
# also, maybe generate a sentence
|
||||||
|
chosen = random.random()
|
||||||
|
chain = self.get_chain(channel, who)
|
||||||
|
if chosen <= chain.reply_chance:
|
||||||
|
pass
|
||||||
|
|
||||||
def get_chain(self, channel: str, who: str) -> Chain:
|
def get_chain(self, channel: str, who: str) -> Chain:
|
||||||
if channel not in self.__chains:
|
if channel not in self.__chains:
|
||||||
self.__chains[channel] = {}
|
self.__chains[channel] = {}
|
||||||
if who not in self.__chains[channel]:
|
if who not in self.__chains[channel]:
|
||||||
path = self.data_path / channel / who
|
path = self.data_path / channel / who
|
||||||
self.__chains[channel][who] = Chain(self.order, path)
|
self.__chains[channel][who] = Chain(self.order, self.reply_chance, path)
|
||||||
return self.__chains[channel][who]
|
return self.__chains[channel][who]
|
||||||
|
|
||||||
def add(self, channel: str, who: str, line: str):
|
def add(self, channel: str, who: str, line: str):
|
||||||
if who == self.server_config.nick == who:
|
if who == self.server_config.nick:
|
||||||
return
|
return
|
||||||
chain = self.get_chain(channel, who)
|
chain = self.get_chain(channel, who)
|
||||||
chain.add(line)
|
chain.add(line)
|
||||||
@@ -208,7 +223,7 @@ class Markov(Plugin):
|
|||||||
if message:
|
if message:
|
||||||
self.send_to(conn, channel, f"{who.nick}: {message}")
|
self.send_to(conn, channel, f"{who.nick}: {message}")
|
||||||
case ["force", nick] | ["emulate", nick]:
|
case ["force", nick] | ["emulate", nick]:
|
||||||
chain = self.get_chain(channel, who.nick)
|
chain = self.get_chain(channel, nick)
|
||||||
if not chain:
|
if not chain:
|
||||||
return
|
return
|
||||||
message = chain.generate()
|
message = chain.generate()
|
||||||
@@ -219,6 +234,13 @@ class Markov(Plugin):
|
|||||||
message = chain.generate()
|
message = chain.generate()
|
||||||
if message:
|
if message:
|
||||||
self.send_to(conn, channel, f"{who.nick}: {message}")
|
self.send_to(conn, channel, f"{who.nick}: {message}")
|
||||||
|
case ["chance", chance]:
|
||||||
|
chain = self.get_chain(channel, who.nick)
|
||||||
|
reply_chance = float(chance)
|
||||||
|
if not math.isnan(reply_chance):
|
||||||
|
chain.reply_chance = min(
|
||||||
|
max(float(reply_chance), 0.0), self.reply_chance
|
||||||
|
)
|
||||||
case _:
|
case _:
|
||||||
# command not recognized
|
# command not recognized
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ async def main():
|
|||||||
for line in lines:
|
for line in lines:
|
||||||
if mat := LINE_RE.search(line):
|
if mat := LINE_RE.search(line):
|
||||||
name = mat["name"]
|
name = mat["name"]
|
||||||
message = mat["message"]
|
message = mat["message"].strip()
|
||||||
|
if name != server_config.nick and message and message[0] != "!":
|
||||||
plugin.add(channel, name, message)
|
plugin.add(channel, name, message)
|
||||||
await plugin.save()
|
await plugin.save()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user