Repository: sopel-irc/sopel

Issue:
version: broken CTCP replies
Sending CTCP messages specified in ```version.py``` to sopel results in no responses, except the VERSION reply that was hardcoded.
```
<<1515127081.06 :Alan!Alan@aleph.null PRIVMSG hotbot-upstream :\x01version\x01
<<1515127085.19 :Alan!Alan@aleph.null PRIVMSG hotbot-upstream :\x01VERSION\x01
>>1515127085.19 NOTICE Alan :\x01VERSION Sopel IRC Bot version 6.5.1\x01
<<1515127088.76 :Alan!Alan@aleph.null PRIVMSG hotbot-upstream :\x01TIME\x01
<<1515127091.97 :Alan!Alan@aleph.null PRIVMSG hotbot-upstream :\x01time\x01
<<1515127167.17 :Alan!Alan@aleph.null PRIVMSG hotbot-upstream :\x01PING 448766.683000\x01
<<1515127169.71 :Alan!Alan@aleph.null PRIVMSG hotbot-upstream :\x01ping 448769.291000\x01
```
Version: ```Sopel v. 6.5.1 at commit:``` d9cfb417a1fdacbfd450d1e8c64027c6026f8f1e
OS: ```CentOS 7.4.1708```, kernel ```3.10.0-514.26.2.el7.x86_64```

I've written a fix that seems to fix this issue.
After the fix:
```
<<1515125762.5  :Alan!Alan@aleph.null PRIVMSG hotbot-upstream :\x01PING 447362.70000\x01
>>1515125762.51 NOTICE Alan :\x01PING 447362.70000\x01
<<1515125765.61 :Alan!Alan@aleph.null PRIVMSG hotbot-upstream :\x01TIME\x01
>>1515125765.61 NOTICE Alan :\x01TIME Thursday, 04. January 2018 10:16PM\x01
<<1515125767.25 :Alan!Alan@aleph.null PRIVMSG hotbot-upstream :\x01time\x01
>>1515125767.25 NOTICE Alan :\x01TIME Thursday, 04. January 2018 10:16PM\x01
<<1515125770.19 :Alan!Alan@aleph.null PRIVMSG hotbot-upstream :\x01version\x01
>>1515125770.19 NOTICE Alan :\x01VERSION Sopel IRC Bot version 6.5.1\x01
<<1515125814.15 :Alan!Alan@aleph.null PRIVMSG hotbot-upstream :\x01source\x01
>>1515125814.15 NOTICE Alan :\x01SOURCE https://github.com/sopel-irc/sopel/\x01
<<1515127190.19 :Alan!Alan@aleph.null PRIVMSG hotbot-upstream :\x01ping 448789.684000\x01
>>1515127190.19 NOTICE Alan :\x01PING 448789.684000\x01
```
All output is from ```raw.log```.


Discussion:

Resolving patch:
diff --git a/sopel/bot.py b/sopel/bot.py
--- a/sopel/bot.py
+++ b/sopel/bot.py
@@ -224,8 +224,11 @@ def unregister(self, obj):
     def register(self, callables, jobs, shutdowns, urls):
         self.shutdown_methods = shutdowns
         for callbl in callables:
-            for rule in callbl.rule:
-                self._callables[callbl.priority][rule].append(callbl)
+            if hasattr(callbl, 'rule'):
+                for rule in callbl.rule:
+                    self._callables[callbl.priority][rule].append(callbl)
+            else:
+                self._callables[callbl.priority][re.compile('.*')].append(callbl)
             if hasattr(callbl, 'commands'):
                 module_name = callbl.__module__.rsplit('.', 1)[-1]
                 # TODO doc and make decorator for this. Not sure if this is how
@@ -513,9 +516,15 @@ def dispatch(self, pretrigger):
 
                     if event not in func.event:
                         continue
-                    if (hasattr(func, 'intents') and
-                            trigger.tags.get('intent') not in func.intents):
-                        continue
+                    if hasattr(func, 'intents'):
+                        if not trigger.tags.get('intent'):
+                            continue
+                        match = False
+                        for intent in func.intents:
+                            if intent.match(trigger.tags.get('intent')):
+                                match = True
+                        if not match:
+                            continue
                     if func.thread:
                         targs = (func, wrapper, trigger)
                         t = threading.Thread(target=self.call, args=targs)
diff --git a/sopel/loader.py b/sopel/loader.py
--- a/sopel/loader.py
+++ b/sopel/loader.py
@@ -189,6 +189,8 @@ def clean_callable(func, config):
             for command in func.commands:
                 func._docs[command] = (doc, example)
 
+    if hasattr(func, 'intents'):
+        func.intents = [re.compile(intent, re.IGNORECASE) for intent in func.intents]
 
 def load_module(name, path, type_):
     """Load a module, and sort out the callables and shutdowns"""
@@ -203,7 +205,7 @@ def load_module(name, path, type_):
 
 
 def is_triggerable(obj):
-    return any(hasattr(obj, attr) for attr in ('rule', 'intent', 'commands'))
+    return any(hasattr(obj, attr) for attr in ('rule', 'intents', 'commands'))
 
 
 def clean_module(module, config):
diff --git a/sopel/modules/version.py b/sopel/modules/version.py
--- a/sopel/modules/version.py
+++ b/sopel/modules/version.py
@@ -10,7 +10,8 @@
 from __future__ import unicode_literals, absolute_import, print_function, division
 
 from datetime import datetime
-import sopel
+from sopel import __version__ as release
+from sopel.module import commands, intent, rate
 import re
 from os import path
 
@@ -31,10 +32,9 @@ def git_info():
                     return sha
 
 
-@sopel.module.commands('version')
+@commands('version')
 def version(bot, trigger):
     """Display the latest commit version, if Sopel is running in a git repo."""
-    release = sopel.__version__
     sha = git_info()
     if not sha:
         msg = 'Sopel v. ' + release
@@ -43,27 +43,25 @@ def version(bot, trigger):
         bot.reply(msg)
         return
 
-    bot.reply("Sopel v. {} at commit: {}".format(sopel.__version__, sha))
+    bot.reply("Sopel v. {} at commit: {}".format(release, sha))
 
 
-@sopel.module.intent('VERSION')
-@sopel.module.rate(20)
-@sopel.module.rule('.*')
+@intent('VERSION')
+@rate(20)
 def ctcp_version(bot, trigger):
-    print('wat')
     bot.write(('NOTICE', trigger.nick),
-              '\x01VERSION Sopel IRC Bot version %s\x01' % sopel.__version__)
+              '\x01VERSION Sopel IRC Bot version %s\x01' % release)
 
 
-@sopel.module.rule('\x01SOURCE\x01')
-@sopel.module.rate(20)
+@intent('SOURCE')
+@rate(20)
 def ctcp_source(bot, trigger):
     bot.write(('NOTICE', trigger.nick),
               '\x01SOURCE https://github.com/sopel-irc/sopel/\x01')
 
 
-@sopel.module.rule('\x01PING\s(.*)\x01')
-@sopel.module.rate(10)
+@intent('PING')
+@rate(10)
 def ctcp_ping(bot, trigger):
     text = trigger.group()
     text = text.replace("PING ", "")
@@ -72,8 +70,8 @@ def ctcp_ping(bot, trigger):
               '\x01PING {0}\x01'.format(text))
 
 
-@sopel.module.rule('\x01TIME\x01')
-@sopel.module.rate(20)
+@intent('TIME')
+@rate(20)
 def ctcp_time(bot, trigger):
     dt = datetime.now()
     current_time = dt.strftime("%A, %d. %B %Y %I:%M%p")


Test patch: