aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar jesopo2019-11-22 16:23:30 +0000
committerGravatar jesopo2019-11-22 16:23:30 +0000
commit5730d6fe591b63b8d50fbebf7c151504003921bd (patch)
treef6d170e30a8bd82513b52b98771c4f4c7a8fd778
parentupdate CHANGELOG.md (diff)
signature
refactor commands (mostly stdout/stderr) and split typing/reply out
closes #208
-rw-r--r--modules/commands/__init__.py132
-rw-r--r--modules/commands/outs.py101
-rw-r--r--modules/ircv3_msgid.py7
-rw-r--r--modules/ircv3_typing.py27
-rw-r--r--src/ModuleManager.py10
5 files changed, 119 insertions, 158 deletions
diff --git a/modules/commands/__init__.py b/modules/commands/__init__.py
index 433e1097..5b0a52e8 100644
--- a/modules/commands/__init__.py
+++ b/modules/commands/__init__.py
@@ -8,9 +8,10 @@ from . import outs
COMMAND_METHOD = "command-method"
COMMAND_METHODS = ["PRIVMSG", "NOTICE"]
-MESSAGE_TAGS_CAP = utils.irc.Capability("message-tags",
- "draft/message-tags-0.2")
-MSGID_TAG = utils.irc.MessageTag("msgid", "draft/msgid")
+STR_MORE = " (more...)"
+STR_MORE_LEN = len(STR_MORE.encode("utf8"))
+STR_CONTINUED = "(...continued)"
+WORD_BOUNDARIES = [" "]
NON_ALPHANUMERIC = [char for char in string.printable if not char.isalnum()]
@@ -48,8 +49,6 @@ class Module(ModuleManager.BaseModule):
target = event["user"]
else:
target = event["channel"]
- target.last_stdout = None
- target.last_stderr = None
def has_command(self, command):
return command.lower() in self.events.on("received").on(
@@ -62,10 +61,10 @@ class Module(ModuleManager.BaseModule):
if s and s[-1] in [":", ","]:
return server.is_own_nickname(s[:-1])
- def _command_method(self, target, server):
+ def _command_method(self, server, target):
return target.get_setting(COMMAND_METHOD,
server.get_setting(COMMAND_METHOD,
- self.bot.get_setting(COMMAND_METHOD, "PRIVMSG")))
+ self.bot.get_setting(COMMAND_METHOD, "PRIVMSG"))).upper()
def _find_command_hook(self, server, target, is_channel, command, args):
if not self.has_command(command):
@@ -159,31 +158,13 @@ class Module(ModuleManager.BaseModule):
if not is_success:
raise utils.EventError("%s: %s" % (user.nickname, message))
- def _tagmsg(self, target, tags):
- return IRCLine.ParsedLine("TAGMSG", [target], tags=tags)
-
def command(self, server, target, target_str, is_channel, user, command,
- args_split, tags, hook, **kwargs):
- message_tags = server.has_capability(MESSAGE_TAGS_CAP)
- expect_output = hook.get_kwarg("expect_output", True)
-
- module_name = self._get_prefix(hook) or ""
- if not module_name and hasattr(hook.function, "__self__"):
- module_name = hook.function.__self__._name
-
- send_tags = {}
- if message_tags:
- msgid = MSGID_TAG.get_value(tags)
- if msgid:
- send_tags["+draft/reply"] = msgid
+ args_split, line, hook, **kwargs):
+ module_name = (self._get_prefix(hook) or
+ self.bot.modules.from_context(hook.context).title)
- if expect_output:
- line = self._tagmsg(target_str, {"+draft/typing": "active"})
- server.send(line, immediate=True)
-
- stdout = outs.StdOut(server, module_name, target, target_str, send_tags)
- stderr = outs.StdErr(server, module_name, target, target_str, send_tags)
- command_method = self._command_method(target, server)
+ stdout = outs.StdOut(module_name)
+ stderr = outs.StdOut(module_name)
ret = False
has_out = False
@@ -192,9 +173,9 @@ class Module(ModuleManager.BaseModule):
args_split = list(filter(None, args_split))
event_kwargs = {"hook": hook, "user": user, "server": server,
- "target": target, "is_channel": is_channel, "tags": tags,
- "args_split": args_split, "command": command,
- "args": " ".join(args_split), "stdout": stdout,
+ "target": target, "target_str": target_str,
+ "is_channel": is_channel, "line": line, "args_split": args_split,
+ "command": command, "args": " ".join(args_split), "stdout": stdout,
"stderr": stderr}
event_kwargs.update(kwargs)
@@ -203,37 +184,59 @@ class Module(ModuleManager.BaseModule):
event_kwargs["check_assert"] = check_assert
check_success, check_message = self._check("preprocess", event_kwargs)
- if not check_success:
+ if check_success:
+ new_event = self.events.on(hook.event_name).make_event(**event_kwargs)
+ self.log.trace("calling command '%s': %s", [command, new_event.kwargs])
+
+ try:
+ hook.call(new_event)
+ except utils.EventError as e:
+ stderr.write(str(e))
+ else:
if check_message:
- stderr.write("%s: %s" % (user.nickname, check_message)
- ).send(command_method)
- return True
+ stderr.write("%s: %s" % (user.nickname, check_message))
- new_event = self.events.on(hook.event_name).make_event(**event_kwargs)
+ self._check("postprocess", event_kwargs)
+ # postprocess - send stdout/stderr and typing tag
- self.log.trace("calling command '%s': %s", [command, new_event.kwargs])
+ return new_event.eaten
- try:
- hook.call(new_event)
- except utils.EventError as e:
- stderr.write(str(e)).send(command_method)
- return True
+ @utils.hook("postprocess.command")
+ @utils.kwarg("priority", EventManager.PRIORITY_LOW)
+ def postprocess(self, event):
+ color = None
+ obj = None
+ if event["stdout"].has_text():
+ color = utils.consts.GREEN
+ obj = event["stdout"]
+ elif event["stderr"].has_text():
+ color = utils.consts.RED
+ obj = event["stderr"]
+ else:
+ return
- if not hook.get_kwarg("skip_out", False):
- has_out = stdout.has_text() or stderr.has_text()
- if has_out:
- command_method = self._command_method(target, server)
- stdout.send(command_method)
- stderr.send(command_method)
- target.last_stdout = stdout
- target.last_stderr = stderr
- ret = new_event.eaten
+ line_str = "[%s] %s" % (utils.irc.color(obj.prefix, color), obj.pop())
+ method = self._command_method(event["server"], event["target"])
- if expect_output and message_tags and not has_out:
- line = self._tagmsg(target_str, {"+draft/typing": "done"})
- server.send(line, immediate=True)
+ if not method in ["PRIVMSG", "NOTICE"]:
+ raise ValueError("Unknown command-method '%s'" % method)
- return ret
+ line = IRCLine.ParsedLine(method, [event["target_str"], line_str],
+ tags=obj.tags)
+ valid, trunc = line.truncate(event["server"].hostmask(),
+ margin=STR_MORE_LEN)
+
+ if trunc:
+ if not trunc[0] in WORD_BOUNDARIES:
+ for boundary in WORD_BOUNDARIES:
+ left, *right = valid.rsplit(boundary, 1)
+ if right:
+ valid = left
+ trunc = right[0]+trunc
+ obj.insert("%s %s" % (STR_CONTINUED, trunc))
+ valid = valid+STR_MORE
+ line = IRCLine.parse_line(valid)
+ event["server"].send(line)
@utils.hook("preprocess.command")
def _check_min_args(self, event):
@@ -293,7 +296,7 @@ class Module(ModuleManager.BaseModule):
if hook:
self.command(event["server"], event["channel"],
event["target_str"], True, event["user"], command,
- args_split, event["tags"], hook,
+ args_split, event["line"], hook,
command_prefix=command_prefix)
else:
self.events.on("unknown.command").call(server=event["server"],
@@ -313,7 +316,7 @@ class Module(ModuleManager.BaseModule):
command = hook.get_kwarg("command", "")
res = self.command(event["server"], event["channel"],
event["target_str"], True, event["user"], command,
- "", event["tags"], hook, match=match,
+ "", event["line"], hook, match=match,
message=event["message"], command_prefix="",
action=event["action"])
@@ -344,7 +347,7 @@ class Module(ModuleManager.BaseModule):
if hook:
self.command(event["server"], event["user"],
event["user"].nickname, False, event["user"], command,
- args_split, event["tags"], hook, command_prefix="")
+ args_split, event["line"], hook, command_prefix="")
else:
self.events.on("unknown.command").call(server=event["server"],
target=event["user"], user=event["user"], command=command,
@@ -364,15 +367,6 @@ class Module(ModuleManager.BaseModule):
def _get_alias_of(self, hook):
return hook.get_kwarg("alias_of", None)
- @utils.hook("received.command.more", skip_out=True)
- def more(self, event):
- """
- :help: Show more output from the last command
- """
- if event["target"].last_stdout and event["target"].last_stdout.has_text():
- event["target"].last_stdout.send(
- self._command_method(event["target"], event["server"]))
-
@utils.hook("send.stdout")
def send_stdout(self, event):
target = event["target"]
diff --git a/modules/commands/outs.py b/modules/commands/outs.py
index 41528da3..fb3c29ef 100644
--- a/modules/commands/outs.py
+++ b/modules/commands/outs.py
@@ -1,98 +1,29 @@
import re
from src import IRCLine, utils
-STR_MORE = " (more...)"
-STR_MORE_LEN = len(STR_MORE.encode("utf8"))
-STR_CONTINUED = "(...continued) "
-WORD_BOUNDARY = ' '
-
-def _message_factory(command):
- if not command in ["PRIVMSG", "NOTICE"]:
- raise ValueError("Unknown command method '%s'" % method)
-
- def _(target, message, tags):
- return IRCLine.ParsedLine(command, [target, message], tags=tags)
- return _
-
-class Out(object):
- def __init__(self, server, module_name, target, target_str, tags):
- self.server = server
- self._prefix = self._default_prefix(module_name)
- self._hide_prefix = False
- self.target = target
- self._target_str = target_str
- self._text = ""
- self.written = False
- self._tags = tags
+class StdOut(object):
+ def __init__(self, prefix):
+ self.prefix = prefix
+ self._lines = []
+ self.tags = {}
self._assured = False
def assure(self):
self._assured = True
def write(self, text):
- self._text += text
- self.written = True
- return self
- def writeline(self, line):
- self._text += "%s\n" % line
-
- def send(self, method):
- if self.has_text():
- prefix = ""
- if not self._hide_prefix:
- prefix = utils.consts.RESET + "[%s] " % self._prefix
-
- text = self._text[:].replace("\r", "")
- while "\n\n" in text:
- text = text.replace("\n\n", "\n")
-
- full_text = "%s%s" % (prefix, text)
- message_factory = _message_factory(method)
-
- line = message_factory(self._target_str, full_text, tags=self._tags)
- if self._assured:
- line.assure()
-
- valid, truncated = line.truncate(self.server.hostmask(),
- margin=STR_MORE_LEN)
+ self.write_lines(
+ text.replace("\r", "").replace("\n\n", "\n").split("\n"))
+ def write_lines(self, lines):
+ self._lines += list(filter(None, lines))
- if truncated:
- valid, truncated = self._adjust_to_word_boundaries(valid, truncated)
-
- line = IRCLine.parse_line(valid+STR_MORE)
- self._text = "%s%s" % (STR_CONTINUED, truncated)
- else:
- self._text = ""
-
- sent_line = self.server.send(line)
-
- def _adjust_to_word_boundaries(self, left, right):
- if right[0] == WORD_BOUNDARY:
- return left, right
-
- parts = left.rsplit(WORD_BOUNDARY, 1)
-
- if len(parts) != 2:
- return left, right
-
- return parts[0], parts[1] + right
-
- def _default_prefix(self, s: str):
- return s
- def set_prefix(self, prefix):
- self._prefix = self._default_prefix(prefix)
- def append_prefix(self, s: str):
- self._prefix = "%s%s" % (self._prefix, s)
- def hide_prefix(self):
- self._hide_prefix = True
+ def get_all(self):
+ return self._lines.copy()
+ def pop(self):
+ return self._lines.pop(0)
+ def insert(self, text):
+ self._lines.insert(0, text)
def has_text(self):
- return bool(self._text)
-
-class StdOut(Out):
- def _default_prefix(self, s: str):
- return utils.irc.color(s, utils.consts.GREEN)
-class StdErr(Out):
- def _default_prefix(self, s: str):
- return utils.irc.color(s, utils.consts.RED)
+ return bool(self._lines)
diff --git a/modules/ircv3_msgid.py b/modules/ircv3_msgid.py
index d5690286..cbb207dc 100644
--- a/modules/ircv3_msgid.py
+++ b/modules/ircv3_msgid.py
@@ -22,3 +22,10 @@ class Module(ModuleManager.BaseModule):
def ctcp(self, event):
if event["is_channel"]:
self._on_channel(event["target"], event["tags"])
+
+ @utils.hook("postprocess.command")
+ def postprocess_command(self, event):
+ msgid = TAG.get_value(event["line"].tags)
+ if msgid:
+ event["stdout"].tags["+draft/reply"] = msgid
+ event["stderr"].tags["+draft/reply"] = msgid
diff --git a/modules/ircv3_typing.py b/modules/ircv3_typing.py
new file mode 100644
index 00000000..ca2fce2b
--- /dev/null
+++ b/modules/ircv3_typing.py
@@ -0,0 +1,27 @@
+from src import IRCLine, ModuleManager, utils
+
+CAP = utils.irc.Capability("message-tags", "draft/message-tags-0.2")
+
+class Module(ModuleManager.BaseModule):
+ def _tagmsg(self, target, state):
+ return IRCLine.ParsedLine("TAGMSG", [target],
+ tags={"+draft/typing": state})
+ def _has_tags(self, server):
+ return server.has_capability(CAP)
+
+ @utils.hook("preprocess.command")
+ def preprocess(self, event):
+ if (self._has_tags(event["server"]) and
+ event["hook"].get_kwarg("expect_output", True)):
+ event["target"]._typing = True
+ event["server"].send(self._tagmsg(event["target_str"], "active"),
+ immediate=True)
+ else:
+ event["target"]._typing = False
+
+ @utils.hook("postprocess.command")
+ def postprocess(self, event):
+ if (event["target"]._typing and
+ not event["stdout"].has_text() and
+ not event["stderr"].has_text()):
+ event["server"].send(self._tagmsg(event["target_str"], "done"))
diff --git a/src/ModuleManager.py b/src/ModuleManager.py
index 50af3493..5ef57b73 100644
--- a/src/ModuleManager.py
+++ b/src/ModuleManager.py
@@ -88,10 +88,12 @@ class ModuleDefinition(object):
class LoadedModule(object):
def __init__(self,
name: str,
+ title: str,
module: BaseModule,
context: str,
import_name: str):
self.name = name
+ self.title = title
self.module = module
self.context = context
self.import_name = import_name
@@ -233,8 +235,8 @@ class ModuleManager(object):
module_object = module_object_pointer(bot, context_events,
context_exports, context_timers, self.log)
- if not hasattr(module_object, "_name"):
- module_object._name = definition.name.title()
+ module_title = (getattr(module_object, "_name", None) or
+ definition.name.title())
# @utils.hook() magic
for attribute_name in dir(module_object):
@@ -256,8 +258,8 @@ class ModuleManager(object):
raise ModuleNameCollisionException("Module name '%s' "
"attempted to be used twice" % definition.name)
- return LoadedModule(definition.name, module_object, context,
- import_name)
+ return LoadedModule(definition.name, module_title, module_object,
+ context, import_name)
def load_module(self, bot: "IRCBot.Bot", definition: ModuleDefinition
) -> LoadedModule: