plugin: answer to privmsg mostly like pubmsg
This commit is contained in:
parent
a8b1b811c5
commit
8020de4edf
|
@ -95,7 +95,7 @@ class Bot(irc.client.SimpleIRCClient, Logger):
|
||||||
self.run_plugin_callbacks(event)
|
self.run_plugin_callbacks(event)
|
||||||
|
|
||||||
def on_privmsg(self, connection, event):
|
def on_privmsg(self, connection, event):
|
||||||
"""Handle a message received privately."""
|
"""Handle a message received privately, usually like a channel msg."""
|
||||||
nick = NickMask(event.source).nick
|
nick = NickMask(event.source).nick
|
||||||
target = event.target
|
target = event.target
|
||||||
message = event.arguments[0]
|
message = event.arguments[0]
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from irc.client import NickMask
|
||||||
|
|
||||||
|
|
||||||
class Plugin:
|
class Plugin:
|
||||||
"""Base class for the bot plugins.
|
"""Base class for the bot plugins.
|
||||||
|
@ -17,15 +19,15 @@ class Plugin:
|
||||||
available after a restart.
|
available after a restart.
|
||||||
|
|
||||||
Initalisation should be very fast, no network connections or anything. They
|
Initalisation should be very fast, no network connections or anything. They
|
||||||
are initialised before connecting to the server, so their `is_ready` flag is
|
are initialised before connecting to the server, so their `is_ready` flag
|
||||||
set at that point. The loading order is more or less random, so a plugin
|
is set at that point. The loading order is more or less random, so a plugin
|
||||||
cannot assume another has been loaded during initialisation. If it wants to
|
cannot assume another has been loaded during initialisation. If it wants to
|
||||||
interact with another plugin, the earliest point to do that is in the
|
interact with another plugin, the earliest point to do that is in the
|
||||||
on_welcome callback which is called after connecting to a server, and can
|
on_welcome callback which is called after connecting to a server, and can
|
||||||
disable itself by setting its own `is_ready` flag to false.
|
disable itself by setting its own `is_ready` flag to false.
|
||||||
|
|
||||||
A plugin can access its config once the base `__init__` has been called. The
|
A plugin can access its config once the base `__init__` has been called.
|
||||||
configuration is valid only is `is_ready` is True, else accessing its
|
The configuration is valid only is `is_ready` is True, else accessing its
|
||||||
content is undefined behaviour.
|
content is undefined behaviour.
|
||||||
|
|
||||||
Plugins can have priorities and calling their callbacks will respect it.
|
Plugins can have priorities and calling their callbacks will respect it.
|
||||||
|
@ -57,19 +59,19 @@ class Plugin:
|
||||||
}
|
}
|
||||||
|
|
||||||
def __get_config(self):
|
def __get_config(self):
|
||||||
"""Return the plugin section from the bot config, plus common values."""
|
"""Return the plugin section from the bot config plus common values."""
|
||||||
plugins_configs = self.bot.config["plugins"]
|
plugins_configs = self.bot.config["plugins"]
|
||||||
config = plugins_configs["common"].copy()
|
config = plugins_configs["common"].copy()
|
||||||
config.update(plugins_configs.get(self.name, {}))
|
config.update(plugins_configs.get(self.name, {}))
|
||||||
# Load resources as config values.
|
# Load resources as config values.
|
||||||
resources_dir = self.bot.config["resources_dir"]
|
res_dir = self.bot.config["resources_dir"]
|
||||||
for key, resource_path in config.get("resources", {}).items():
|
for key, res_path in config.get("resources", {}).items():
|
||||||
resource_path = Path(resources_dir) / resource_path
|
res_path = Path(res_dir) / res_path
|
||||||
try:
|
try:
|
||||||
with open(resource_path, "rt") as resource_file:
|
with open(res_path, "rt") as res_file:
|
||||||
config[key] = [l.strip() for l in resource_file.readlines()]
|
config[key] = [l.strip() for l in res_file.readlines()]
|
||||||
except OSError:
|
except OSError:
|
||||||
self.bot.log_e(f"Could not load resource at {resource_path}.")
|
self.bot.log_e(f"Could not load resource at {res_path}.")
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def __check_config(self):
|
def __check_config(self):
|
||||||
|
@ -77,15 +79,15 @@ class Plugin:
|
||||||
missing = False
|
missing = False
|
||||||
for key in self.REQUIRED_CONFIGS:
|
for key in self.REQUIRED_CONFIGS:
|
||||||
if key not in self.config:
|
if key not in self.config:
|
||||||
self.bot.log_e(f"Missing '{key}' in {self.name} configuration.")
|
self.bot.log_e(f"Missing '{key}' in {self.name} config.")
|
||||||
missing = True
|
missing = True
|
||||||
return not missing
|
return not missing
|
||||||
|
|
||||||
def get_runtime_value(self, key, default=None, ns=None):
|
def get_runtime_value(self, key, default=None, 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
|
This will get the value from the plugin namespace, but it is possible
|
||||||
get runtime values from other plugins using their name as `ns`.
|
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
|
||||||
|
@ -98,8 +100,8 @@ class Plugin:
|
||||||
def get_storage_value(self, key, default=None, ns=None):
|
def get_storage_value(self, key, default=None, ns=None):
|
||||||
"""Get a value from the plugin persistent storage.
|
"""Get a value from the plugin persistent storage.
|
||||||
|
|
||||||
This will get the value from the plugin namespace, but it is possible to
|
This will get the value from the plugin namespace, but it is possible
|
||||||
get storage values from other plugins using their name as `ns`.
|
to get storage values from other plugins using their name as `ns`.
|
||||||
"""
|
"""
|
||||||
if ns is None:
|
if ns is None:
|
||||||
ns = self.name
|
ns = self.name
|
||||||
|
@ -124,7 +126,10 @@ class Plugin:
|
||||||
|
|
||||||
def remove_storage_list_value(self, key, value):
|
def remove_storage_list_value(self, key, value):
|
||||||
"""Remove a value from a persistent storage list."""
|
"""Remove a value from a persistent storage list."""
|
||||||
if self.name in self.bot.storage and key in self.bot.storage[self.name]:
|
if (
|
||||||
|
self.name in self.bot.storage
|
||||||
|
and key in self.bot.storage[self.name]
|
||||||
|
):
|
||||||
self.bot.storage[self.name][key].remove(value)
|
self.bot.storage[self.name][key].remove(value)
|
||||||
|
|
||||||
def should_read_message(self, message):
|
def should_read_message(self, message):
|
||||||
|
@ -136,8 +141,8 @@ class Plugin:
|
||||||
|
|
||||||
Note that you do not need to use this method for command/question
|
Note that you do not need to use this method for command/question
|
||||||
plugins, the next methods already check for this and do more precise
|
plugins, the next methods already check for this and do more precise
|
||||||
parsing of the message. This is rather for plugins reacting to a message
|
parsing of the message. This is rather for plugins reacting to a
|
||||||
addressed to the bot that are neither commands or questions.
|
message addressed to the bot that are neither commands or questions.
|
||||||
"""
|
"""
|
||||||
first_word_and_rest = message.split(maxsplit=1)
|
first_word_and_rest = message.split(maxsplit=1)
|
||||||
num_parts = len(first_word_and_rest)
|
num_parts = len(first_word_and_rest)
|
||||||
|
@ -158,11 +163,11 @@ class Plugin:
|
||||||
def should_answer_question(self, message):
|
def should_answer_question(self, message):
|
||||||
"""Store Question in object and return True if I should answer it.
|
"""Store Question in object and return True if I should answer it.
|
||||||
|
|
||||||
To answer a question, the message must start with one of the bot's names
|
To answer a question, the message must start with one of the bot's
|
||||||
and optionally end with one or more question marks (they are discarded).
|
names and optionally end with one or more question marks (they are
|
||||||
The bot checks that answering conditions are respected, and they cannot
|
discarded). The bot checks that answering conditions are respected, and
|
||||||
be bypassed as with commands. A Question created is checked from the
|
they cannot be bypassed as with commands. A Question created is checked
|
||||||
plugin's registered questions and stored in the instance.
|
from the plugin's registered questions and stored in the instance.
|
||||||
"""
|
"""
|
||||||
words = message.split()
|
words = message.split()
|
||||||
# Is the message addressed to me?
|
# Is the message addressed to me?
|
||||||
|
@ -200,9 +205,9 @@ class Plugin:
|
||||||
|
|
||||||
If no_content is True, the command does not parse command contents and
|
If no_content is True, the command does not parse command contents and
|
||||||
put all the command message (without suffix) to the command identifier.
|
put all the command message (without suffix) to the command identifier.
|
||||||
This is useful for commands that have multiple words in their identifier
|
This is useful for commands that have multiple words in their
|
||||||
and do not have any content, or when the content parsing should be done
|
identifier and do not have any content, or when the content parsing
|
||||||
by the plugin itself.
|
should be done by the plugin itself.
|
||||||
|
|
||||||
If exclude_conditions is a collection of plugin namespaces, the
|
If exclude_conditions is a collection of plugin namespaces, the
|
||||||
conditions of these plugins are not tested. A basic scenario is the
|
conditions of these plugins are not tested. A basic scenario is the
|
||||||
|
@ -250,7 +255,7 @@ class Plugin:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def __parse_command(self, message, no_content=False):
|
def __parse_command(self, message, no_content=False):
|
||||||
"""Return a parsed Command if this message is a command, None otherwise.
|
"""Return a parsed Command if this message is a command, else None.
|
||||||
|
|
||||||
The command raw field is always set. The ident and content fields are
|
The command raw field is always set. The ident and content fields are
|
||||||
empty when no_content is True. The match field is never set by this
|
empty when no_content is True. The match field is never set by this
|
||||||
|
@ -273,7 +278,7 @@ class Plugin:
|
||||||
def __save_command(self, command):
|
def __save_command(self, command):
|
||||||
"""Save command in instance for further processing by the plugin."""
|
"""Save command in instance for further processing by the plugin."""
|
||||||
self.command = command
|
self.command = command
|
||||||
self.bot.log_i(f"Processing command from plugin {self.name}: {command}")
|
self.bot.log_i(f"Processing command from p. {self.name}: {command}")
|
||||||
|
|
||||||
def respects_handling_conditions(self, exclude_conditions=None):
|
def respects_handling_conditions(self, exclude_conditions=None):
|
||||||
"""Check if handling conditions are valid.
|
"""Check if handling conditions are valid.
|
||||||
|
@ -297,6 +302,19 @@ class Plugin:
|
||||||
"""Signal a plugin failure to target."""
|
"""Signal a plugin failure to target."""
|
||||||
self.bot.say(target, self.bot.config["error_message"])
|
self.bot.say(target, self.bot.config["error_message"])
|
||||||
|
|
||||||
|
def on_privmsg(self, event):
|
||||||
|
"""Default behaviour on privmsg is to answer like a pubmsg.
|
||||||
|
|
||||||
|
Because most plugins assume messages are sent publicly, they send their
|
||||||
|
replies to `event.target` so we replace it with the event source nick.
|
||||||
|
If it causes an issue with a plugin requiring the original target even
|
||||||
|
on private message, override this method.
|
||||||
|
"""
|
||||||
|
if (on_pubmsg := getattr(self, "on_pubmsg", None)):
|
||||||
|
event.target = NickMask(event.source).nick
|
||||||
|
return on_pubmsg(event)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Question:
|
class Question:
|
||||||
|
|
Loading…
Reference in a new issue