notes: add plugin, only taking notes

This commit is contained in:
dece 2020-10-09 23:37:35 +02:00
parent 9ab1812938
commit 70cdcad604
7 changed files with 105 additions and 7 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
config.json config.json
storage.json
resources/* resources/*
!resources/.gitkeep !resources/.gitkeep

View file

@ -43,3 +43,4 @@ Missing features
- [ ] Youtube: parsing for title, requests for channel or video - [ ] Youtube: parsing for title, requests for channel or video
- [ ] Command aliases - [ ] Command aliases
- [ ] Question aliases - [ ] Question aliases
- [ ] Various macros

View file

@ -5,6 +5,7 @@
"alternative_nicks": ["edmon", "edmond"], "alternative_nicks": ["edmon", "edmond"],
"channels": ["#idi0crates"], "channels": ["#idi0crates"],
"speak_delay": 0.5, "speak_delay": 0.5,
"storage_file": "storage.json",
"resources_dir": "resources", "resources_dir": "resources",
"error_message": "An error occured, sorry!", "error_message": "An error occured, sorry!",
"plugins": { "plugins": {
@ -37,6 +38,10 @@
"pissed": "Pissed off..." "pissed": "Pissed off..."
} }
}, },
"notes": {
"commands": ["note down"],
"content_regex": "for (P:<target>\\S+) (P:<note>.+)"
},
"random": { "random": {
"commands": ["choose"], "commands": ["choose"],
"separator": "or" "separator": "or"

View file

@ -1,4 +1,5 @@
import importlib import importlib
import json
import os import os
import time import time
from pathlib import Path from pathlib import Path
@ -17,6 +18,7 @@ class Bot(irc.client.SimpleIRCClient, Logger):
self.logger = logger self.logger = logger
self.plugins = [] self.plugins = []
self.values = {} self.values = {}
self.storage = self._get_storage()
@property @property
def nick(self): def nick(self):
@ -26,6 +28,28 @@ class Bot(irc.client.SimpleIRCClient, Logger):
def names(self): def names(self):
return (self.config["nick"], *self.config["alternative_nicks"]) return (self.config["nick"], *self.config["alternative_nicks"])
def _get_storage(self):
"""Load data from storage."""
try:
with open(self.config["storage_file"], "rt") as storage_file:
storage = json.load(storage_file)
self.log_d("Loaded storage file.")
return storage
except (OSError, json.decoder.JSONDecodeError) as exc:
self.log_e(f"Could not load storage file: {exc}")
self.log_w("If it's not the first time Edm0nd is run, you may lose"
" data when closing the program.")
return {}
def _save_storage(self):
"""Save storage data to disk."""
try:
with open(self.config["storage_file"], "wt") as storage_file:
json.dump(self.storage, storage_file, indent=2, sort_keys=True)
self.log_d("Saved storage file.")
except (OSError, json.decoder.JSONEncodeError) as exc:
self.log_e(f"Could not save storage file: {exc}")
def on_welcome(self, connection, event): def on_welcome(self, connection, event):
self.log_i(f"Connected to server {event.source}.") self.log_i(f"Connected to server {event.source}.")
self.run_plugin_callbacks(event) self.run_plugin_callbacks(event)
@ -63,6 +87,7 @@ class Bot(irc.client.SimpleIRCClient, Logger):
self.start() self.start()
except KeyboardInterrupt: except KeyboardInterrupt:
self.log_i("Stopping Edmond.") self.log_i("Stopping Edmond.")
self._save_storage()
def load_plugins(self): def load_plugins(self):
"""Load all installed plugins.""" """Load all installed plugins."""
@ -97,6 +122,7 @@ class Bot(irc.client.SimpleIRCClient, Logger):
self.connection.privmsg(target, message) self.connection.privmsg(target, message)
def run_plugin_callbacks(self, event): def run_plugin_callbacks(self, event):
"""Run appropriate callbacks for each plugin."""
etype = event.type etype = event.type
for plugin in filter(lambda p: p.is_ready, self.plugins): for plugin in filter(lambda p: p.is_ready, self.plugins):
callbacks = plugin.callbacks callbacks = plugin.callbacks

View file

@ -47,7 +47,11 @@ class Plugin:
return not missing return not missing
def get_runtime_value(self, key, ns=None): def get_runtime_value(self, key, ns=None):
"""Get a value from the plugin runtime dict.""" """Get a value from the plugin runtime dict.
This will get the value from the plugin namespace, but it is possible to
get runtime values from other plugins using their name as `ns`.
"""
if ns is None: if ns is None:
ns = self.name ns = self.name
return self.bot.values[ns].get(key) return self.bot.values[ns].get(key)
@ -56,6 +60,26 @@ class Plugin:
"""Set a value in the plugin runtime dict.""" """Set a value in the plugin runtime dict."""
self.bot.values[self.name][key] = value self.bot.values[self.name][key] = value
def get_storage_value(self, key):
"""Get a value from the plugin persistent storage."""
return self.bot.storage.get(self.name, {}).get(key)
def set_storage_value(self, key, value):
"""Set a value in the plugin persistent storage."""
if self.name not in self.bot.storage:
self.bot.storage[self.name] = {key: value}
else:
self.bot.storage[self.name][key] = value
def append_storage_list_value(self, key, value):
"""Append a value to a list in the plugin persistent storage."""
if self.name not in self.bot.storage:
self.bot.storage[self.name] = {key: [value]}
elif key not in self.bot.storage[self.name]:
self.bot.storage[self.name][key] = [value]
else:
self.bot.storage[self.name][key].append(value)
def should_answer_question(self, message): def should_answer_question(self, message):
"""Store Question in object and return True if it should answer it.""" """Store Question in object and return True if it should answer it."""
words = message.split() words = message.split()
@ -74,7 +98,10 @@ class Plugin:
"""Store Command in object and return True if it should handle it.""" """Store Command in object and return True if it should handle it."""
command = self.parse_command(message, no_content=no_content) command = self.parse_command(message, no_content=no_content)
commands = self.config.get("commands", []) commands = self.config.get("commands", [])
if command and any(c == command.ident for c in commands): if (
any(command.ident == c for c in commands) or
(no_content and any(command.ident.startswith(c) for c in commands))
):
self.command = command self.command = command
self.bot.log_d(f"Processing command from plugin {self.name}.") self.bot.log_d(f"Processing command from plugin {self.name}.")
return True return True

42
edmond/plugins/notes.py Normal file
View file

@ -0,0 +1,42 @@
import re
from edmond.plugin import Plugin
class NotesPlugin(Plugin):
REQUIRED_CONFIGS = ["commands", "content_regex"]
def __init__(self, bot):
super().__init__(bot)
self._content_re = None
@property
def content_re(self):
if self._content_re is None:
self._content_re = re.compile(self.config["content_regex"])
return self._content_re
def on_pubmsg(self, event):
if not self.should_handle_command(event.arguments[0], no_content=True):
return False
# "note down" command.
command0 = self.config["commands"][0]
if self.command.ident.startswith(command0):
content = self.command.ident[len(command0):].strip()
match = self.content_re.match(content)
if not match:
return False
groups = match.groupdict()
if any(k not in groups for k in ("target", "note")):
return False
target = groups["target"]
message = groups["note"]
self.bot.log_d(f"Noting for {target}: {message}")
note = {
"sender": event.source.nick,
"dest": target,
"message": message
}
self.append_storage_list_value("notes", note)

View file

@ -13,14 +13,10 @@ class RandomPlugin(Plugin):
def on_pubmsg(self, event): def on_pubmsg(self, event):
if not self.should_handle_command(event.arguments[0]): if not self.should_handle_command(event.arguments[0]):
return False return False
if self.command.ident == "choose":
self.pick_random(event.target)
def pick_random(self, target):
separator = self.config["separator"] separator = self.config["separator"]
choices = self.command.content.split(f" {separator} ") choices = self.command.content.split(f" {separator} ")
self.bot.log_d(f"Choices: {choices}") self.bot.log_d(f"Choices: {choices}")
if len(choices): if len(choices):
choice = random.choice(choices) choice = random.choice(choices)
if choice: if choice:
self.bot.say(target, choice) self.bot.say(event.target, choice)