From cddc3f97cdc4715eb048f045a37a8bfd0f958f1b Mon Sep 17 00:00:00 2001 From: Max Gurela Date: Sun, 1 May 2016 19:30:00 -0700 Subject: [PATCH 1/8] irc, bot, module: Basic echo-message simulation --- sopel/bot.py | 3 +++ sopel/irc.py | 10 ++++++++++ sopel/module.py | 17 +++++++++++++++++ test/test_module.py | 7 +++++++ 4 files changed, 37 insertions(+) diff --git a/sopel/bot.py b/sopel/bot.py index 28be3539b3..dbefc7f4e4 100644 --- a/sopel/bot.py +++ b/sopel/bot.py @@ -530,6 +530,9 @@ def dispatch(self, pretrigger): match = True if not match: continue + if (not (hasattr(func, 'echo') and func.echo is True) and + trigger.nick.lower() == self.nick.lower()): + continue if func.thread: targs = (func, wrapper, trigger) t = threading.Thread(target=self.call, args=targs) diff --git a/sopel/irc.py b/sopel/irc.py index 7b259ab08d..23ff923cbb 100644 --- a/sopel/irc.py +++ b/sopel/irc.py @@ -149,6 +149,16 @@ def write(self, args, text=None): self.send(temp.encode('utf-8')) finally: self.writing_lock.release() + # Simulate echo-message + if 'echo-message' not in self.enabled_capabilities: + # Since we have no way of knowing the hostmask the IRC server uses + # for us, we'll just use something reasonable + host = 'localhost' + if self.config.core.bind_host: + host = self.config.core.bind_host + pretrigger = PreTrigger(self.nick, ':{0}!{1}@{2} {3}' + .format(self.nick, self.user, host, temp)) + self.dispatch(pretrigger) def run(self, host, port=6667): try: diff --git a/sopel/module.py b/sopel/module.py index 7d3f93fc5a..02613c2f66 100644 --- a/sopel/module.py +++ b/sopel/module.py @@ -116,6 +116,23 @@ def add_attribute(function): return add_attribute +def echo(value=True): + """Decorate a function to specify if it should receive echo messages. + + This decorator can be used to listen in on the messages that Sopel is + sending and react accordingly. + + Args: + value: Either True or False. If True the function will receive echo + messages, and if False only messages received from other users. + + """ + def add_attribute(function): + function.echo = value + return function + return add_attribute + + def commands(*command_list): """Decorate a function to set one or more commands to trigger it. diff --git a/test/test_module.py b/test/test_module.py index 3f42d950ed..8b2126e0c5 100644 --- a/test/test_module.py +++ b/test/test_module.py @@ -82,6 +82,13 @@ def mock(bot, trigger, match): assert mock.thread is True +def test_echo(): + @module.echo(False) + def mock(bot, trigger, match): + return True + assert mock.echo is False + + def test_commands(): @module.commands('sopel') def mock(bot, trigger, match): From 383afc8c9ed8bc92af7963e59a26365798ad0e08 Mon Sep 17 00:00:00 2001 From: Max Gurela Date: Sun, 1 May 2016 22:44:16 -0700 Subject: [PATCH 2/8] module: Removed @echo default so the code is less confusing --- sopel/module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sopel/module.py b/sopel/module.py index 02613c2f66..ce2cb0483f 100644 --- a/sopel/module.py +++ b/sopel/module.py @@ -116,7 +116,7 @@ def add_attribute(function): return add_attribute -def echo(value=True): +def echo(value): """Decorate a function to specify if it should receive echo messages. This decorator can be used to listen in on the messages that Sopel is From 570ae92ca0483323ce099c8144bcc0b734564a48 Mon Sep 17 00:00:00 2001 From: dgw Date: Wed, 3 Apr 2019 18:53:24 -0500 Subject: [PATCH 3/8] irc, module: tweaks to echo-message behavior - Remove argument from module.echo() decorator and just always set the attribute to `True` on decorated functions - Only simulate `echo-message` for PRIVMSG and NOTICE lines --- sopel/irc.py | 3 ++- sopel/module.py | 13 ++++++------- test/test_module.py | 8 ++++++-- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/sopel/irc.py b/sopel/irc.py index 23ff923cbb..269c7e4669 100644 --- a/sopel/irc.py +++ b/sopel/irc.py @@ -150,7 +150,8 @@ def write(self, args, text=None): finally: self.writing_lock.release() # Simulate echo-message - if 'echo-message' not in self.enabled_capabilities: + if ('echo-message' not in self.enabled_capabilities and + args[0].upper() in ['PRIVMSG', 'NOTICE']): # Since we have no way of knowing the hostmask the IRC server uses # for us, we'll just use something reasonable host = 'localhost' diff --git a/sopel/module.py b/sopel/module.py index ce2cb0483f..c35729aedf 100644 --- a/sopel/module.py +++ b/sopel/module.py @@ -116,20 +116,19 @@ def add_attribute(function): return add_attribute -def echo(value): +def echo(function=None): """Decorate a function to specify if it should receive echo messages. This decorator can be used to listen in on the messages that Sopel is sending and react accordingly. - - Args: - value: Either True or False. If True the function will receive echo - messages, and if False only messages received from other users. - """ def add_attribute(function): - function.echo = value + function.echo = True return function + + # hack to allow both @echo and @echo() to work + if callable(function): + return add_attribute(function) return add_attribute diff --git a/test/test_module.py b/test/test_module.py index 8b2126e0c5..76b88e3e0d 100644 --- a/test/test_module.py +++ b/test/test_module.py @@ -83,10 +83,14 @@ def mock(bot, trigger, match): def test_echo(): - @module.echo(False) + @module.echo() def mock(bot, trigger, match): return True - assert mock.echo is False + assert mock.echo is True + + def mock(bot, trigger, match): + return True + assert not hasattr(mock, 'echo') def test_commands(): From e900420550a3a9f92c5583bcb9a2332a059579e2 Mon Sep 17 00:00:00 2001 From: dgw Date: Wed, 3 Apr 2019 23:37:54 -0500 Subject: [PATCH 4/8] loader: set default value for func.echo If a function is not decorated with @sopel.module.echo, it should not throw an exception if code somewhere tries to check `func.echo`'s value. Simplified check in `bot.dispatch()` with this: since `func.echo` will always at least exist, there's no need to do any `hasattr()` first. --- sopel/bot.py | 4 ++-- sopel/loader.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/sopel/bot.py b/sopel/bot.py index dbefc7f4e4..73c54e290d 100644 --- a/sopel/bot.py +++ b/sopel/bot.py @@ -530,8 +530,8 @@ def dispatch(self, pretrigger): match = True if not match: continue - if (not (hasattr(func, 'echo') and func.echo is True) and - trigger.nick.lower() == self.nick.lower()): + if (trigger.nick.lower() == self.nick.lower() and + not func.echo): continue if func.thread: targs = (func, wrapper, trigger) diff --git a/sopel/loader.py b/sopel/loader.py index dc0e48e56f..b40027f706 100644 --- a/sopel/loader.py +++ b/sopel/loader.py @@ -144,6 +144,7 @@ def clean_callable(func, config): example = None func.unblockable = getattr(func, 'unblockable', False) + func.echo = getattr(func, 'echo', False) func.priority = getattr(func, 'priority', 'medium') func.thread = getattr(func, 'thread', True) func.rate = getattr(func, 'rate', 0) From 8314424c4473fc0b0eaaa15cfbbf8be489cd32bf Mon Sep 17 00:00:00 2001 From: dgw Date: Thu, 4 Apr 2019 16:46:07 -0500 Subject: [PATCH 5/8] irc: use bot's new hostmask property in echo-message emulation This landed on master, so we can safely use it in the echo-message branch assuming all tests are run on merges (which they are in CI). --- sopel/irc.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/sopel/irc.py b/sopel/irc.py index 269c7e4669..303601ce0b 100644 --- a/sopel/irc.py +++ b/sopel/irc.py @@ -149,14 +149,21 @@ def write(self, args, text=None): self.send(temp.encode('utf-8')) finally: self.writing_lock.release() + # Simulate echo-message if ('echo-message' not in self.enabled_capabilities and args[0].upper() in ['PRIVMSG', 'NOTICE']): - # Since we have no way of knowing the hostmask the IRC server uses - # for us, we'll just use something reasonable + # Use the hostmask we think the IRC server is using for us, + # or something reasonable if that's not available host = 'localhost' if self.config.core.bind_host: host = self.config.core.bind_host + else: + try: + host = self.hostmask + except KeyError: + pass # we tried, and that's good enough + pretrigger = PreTrigger(self.nick, ':{0}!{1}@{2} {3}' .format(self.nick, self.user, host, temp)) self.dispatch(pretrigger) From 92fb78f756a1e2d453480df9464a69c9796826c2 Mon Sep 17 00:00:00 2001 From: dgw Date: Thu, 4 Apr 2019 17:08:15 -0500 Subject: [PATCH 6/8] test_module: more comprehensive, commented echo tests --- test/test_module.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/test_module.py b/test/test_module.py index 76b88e3e0d..ec8ea03bb6 100644 --- a/test/test_module.py +++ b/test/test_module.py @@ -83,13 +83,23 @@ def mock(bot, trigger, match): def test_echo(): + # test decorator with parentheses @module.echo() def mock(bot, trigger, match): return True assert mock.echo is True + # test decorator without parentheses + @module.echo def mock(bot, trigger, match): return True + assert mock.echo is True + + # test without decorator + def mock(bot, trigger, match): + return True + # on undecorated callables, the attr only exists after the loader loads them + # so this cannot `assert mock.echo is False` here assert not hasattr(mock, 'echo') From 7376b83b507f069bd898d6934f9a346d2b8c1652 Mon Sep 17 00:00:00 2001 From: dgw Date: Thu, 4 Apr 2019 17:15:38 -0500 Subject: [PATCH 7/8] coretasks: tweak CAP handling Corrected misspelled method names related to receiving CAP messages, and added `echo-message` to the list of core capabilities Sopel should enable if available from the server (for the module.echo decorator). --- sopel/coretasks.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/sopel/coretasks.py b/sopel/coretasks.py index 0098fedd1c..fe55631fd4 100644 --- a/sopel/coretasks.py +++ b/sopel/coretasks.py @@ -407,11 +407,11 @@ def track_quit(bot, trigger): @sopel.module.thread(False) @sopel.module.priority('high') @sopel.module.unblockable -def recieve_cap_list(bot, trigger): +def receive_cap_list(bot, trigger): cap = trigger.strip('-=~') # Server is listing capabilites if trigger.args[1] == 'LS': - recieve_cap_ls_reply(bot, trigger) + receive_cap_ls_reply(bot, trigger) # Server denied CAP REQ elif trigger.args[1] == 'NAK': entry = bot._cap_reqs.get(cap, None) @@ -455,10 +455,10 @@ def recieve_cap_list(bot, trigger): if req.success: req.success(bot, req.prefix + trigger) if cap == 'sasl': # TODO why is this not done with bot.cap_req? - recieve_cap_ack_sasl(bot) + receive_cap_ack_sasl(bot) -def recieve_cap_ls_reply(bot, trigger): +def receive_cap_ls_reply(bot, trigger): if bot.server_capabilities: # We've already seen the results, so someone sent CAP LS from a module. # We're too late to do SASL, and we don't want to send CAP END before @@ -480,7 +480,13 @@ def recieve_cap_ls_reply(bot, trigger): # If some other module requests it, we don't need to add another request. # If some other module prohibits it, we shouldn't request it. - core_caps = ['multi-prefix', 'away-notify', 'cap-notify', 'server-time'] + core_caps = [ + 'echo-message', + 'multi-prefix', + 'away-notify', + 'cap-notify', + 'server-time', + ] for cap in core_caps: if cap not in bot._cap_reqs: bot._cap_reqs[cap] = [_CapReq('', 'coretasks')] @@ -529,7 +535,7 @@ def acct_warn(bot, cap): bot.write(('CAP', 'END')) -def recieve_cap_ack_sasl(bot): +def receive_cap_ack_sasl(bot): # Presumably we're only here if we said we actually *want* sasl, but still # check anyway. password = bot.config.core.auth_password From 443c303110fb643f371fb21809b6899d9a4244c8 Mon Sep 17 00:00:00 2001 From: dgw Date: Thu, 4 Apr 2019 20:26:24 -0500 Subject: [PATCH 8/8] find: collect own lines as well Use new `@module.echo` decorator to collect Sopel's own lines as well, so cheeky users can say, "Sopel: s/you made/a mistake/" to the bot. Basically, proof that the echo decorator does its job. --- sopel/modules/find.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sopel/modules/find.py b/sopel/modules/find.py index 8c11697b08..34b3a6d439 100644 --- a/sopel/modules/find.py +++ b/sopel/modules/find.py @@ -12,7 +12,7 @@ import re from sopel.tools import Identifier, SopelMemory -from sopel.module import rule, priority +from sopel.module import rule, priority, echo from sopel.formatting import bold @@ -20,6 +20,7 @@ def setup(bot): bot.memory['find_lines'] = SopelMemory() +@echo @rule('.*') @priority('low') def collectlines(bot, trigger):