diff --git a/sopel/config/core_section.py b/sopel/config/core_section.py index 706fe53a03..9e782aaf5a 100644 --- a/sopel/config/core_section.py +++ b/sopel/config/core_section.py @@ -20,6 +20,8 @@ """Default prefix used for commands.""" COMMAND_DEFAULT_HELP_PREFIX = '.' """Default help prefix used in commands' usage messages.""" +URL_DEFAULT_SCHEMES = ['http', 'https', 'ftp'] +"""Default URL schemes allowed for URLs.""" def _find_certs(): @@ -188,7 +190,7 @@ class CoreSection(StaticSection): auto_url_schemes = ListAttribute( 'auto_url_schemes', strip=True, - default=['http', 'https', 'ftp']) + default=URL_DEFAULT_SCHEMES) """List of URL schemes that will trigger URL callbacks. :default: ``['http', 'https', 'ftp']`` diff --git a/sopel/irc/__init__.py b/sopel/irc/__init__.py index faa26b8e81..16762bdf36 100644 --- a/sopel/irc/__init__.py +++ b/sopel/irc/__init__.py @@ -213,7 +213,11 @@ def on_message(self, message): """ self.last_raw_line = message - pretrigger = PreTrigger(self.nick, message) + pretrigger = PreTrigger( + self.nick, + message, + url_schemes=self.settings.core.auto_url_schemes, + ) if all(cap not in self.enabled_capabilities for cap in ['account-tag', 'extended-join']): pretrigger.tags.pop('account', None) @@ -262,7 +266,8 @@ def on_message_sent(self, raw): pretrigger = PreTrigger( self.nick, - ":{0}!{1}@{2} {3}".format(self.nick, self.user, host, raw) + ":{0}!{1}@{2} {3}".format(self.nick, self.user, host, raw), + url_schemes=self.settings.core.auto_url_schemes, ) self.dispatch(pretrigger) diff --git a/sopel/test_tools.py b/sopel/test_tools.py index deb391dcfc..6de00e1c18 100644 --- a/sopel/test_tools.py +++ b/sopel/test_tools.py @@ -166,6 +166,7 @@ def test(configfactory, botfactory, ircfactory): owner=owner, ) settings = configfactory('default.cfg', test_config) + url_schemes = settings.core.auto_url_schemes bot = botfactory(settings) server = ircfactory(bot) server.channel_joined('#Sopel') @@ -184,7 +185,8 @@ def test(configfactory, botfactory, ircfactory): # TODO enable message tags full_message = ':{} PRIVMSG {} :{}'.format(hostmask, sender, msg) - pretrigger = sopel.trigger.PreTrigger(bot.nick, full_message) + pretrigger = sopel.trigger.PreTrigger( + bot.nick, full_message, url_schemes=url_schemes) trigger = sopel.trigger.Trigger(bot.settings, pretrigger, match) pattern = re.compile(r'^%s: ' % re.escape(bot.nick)) @@ -206,7 +208,8 @@ def isnt_ignored(value): tested_func(wrapper, trigger) output_triggers = ( - sopel.trigger.PreTrigger(bot.nick, message.decode('utf-8')) + sopel.trigger.PreTrigger( + bot.nick, message.decode('utf-8'), url_schemes=url_schemes) for message in wrapper.backend.message_sent ) output_texts = ( diff --git a/sopel/tests/factories.py b/sopel/tests/factories.py index 6bfe580451..0e59c850f1 100644 --- a/sopel/tests/factories.py +++ b/sopel/tests/factories.py @@ -93,9 +93,10 @@ def wrapper(self, mockbot, raw, pattern=None): return bot.SopelWrapper(mockbot, trigger) def __call__(self, mockbot, raw, pattern=None): + url_schemes = mockbot.settings.core.auto_url_schemes return trigger.Trigger( mockbot.settings, - trigger.PreTrigger(mockbot.nick, raw), + trigger.PreTrigger(mockbot.nick, raw, url_schemes=url_schemes), re.match(pattern or r'.*', raw)) diff --git a/sopel/trigger.py b/sopel/trigger.py index 3ebbe95159..b15d9c5f5f 100644 --- a/sopel/trigger.py +++ b/sopel/trigger.py @@ -7,6 +7,7 @@ import sys from sopel import tools +from sopel.tools import web __all__ = [ @@ -24,6 +25,7 @@ class PreTrigger(object): :param str own_nick: the bot's own IRC nickname :param str line: the full line from the server + :param tuple url_schemes: allowed schemes for URL detection At the :class:`PreTrigger` stage, the line has not been matched against any rules yet. This is what Sopel uses to perform matching. @@ -80,6 +82,16 @@ class PreTrigger(object): For lines that do *not* contain ``:``, :attr:`text` will be the last argument in :attr:`args` instead. + .. py:attribute:: urls + :type: tuple + + The :class:`tuple` of URLs found in the IRC line's text, for + ``PRIVMSG`` and ``NOTICE`` only. For other messages, this will be an + empty ``tuple``. + + These URLs are found in the text message of the IRC line, usually after + the ` :` section. + .. py:attribute:: time The time when the message was received. @@ -95,9 +107,10 @@ class PreTrigger(object): component_regex = re.compile(r'([^!]*)!?([^@]*)@?(.*)') intent_regex = re.compile('\x01(\\S+) ?(.*)\x01') - def __init__(self, own_nick, line): + def __init__(self, own_nick, line, url_schemes=None): line = line.strip('\r\n') self.line = line + self.urls = tuple() # Break off IRCv3 message tags, if present self.tags = {} @@ -170,6 +183,10 @@ def __init__(self, own_nick, line): self.tags['intent'] = intent self.args[-1] = message or '' + # Search URLs after CTCP parsing + self.urls = tuple( + web.search_urls(self.args[-1], schemes=url_schemes)) + # Populate account from extended-join messages if self.event == 'JOIN' and len(self.args) == 3: # Account is the second arg `...JOIN #Sopel account :realname` @@ -315,6 +332,14 @@ class Trigger(unicode): example, when setting ``mode -m`` on the channel ``#example``, args would be ``('#example', '-m')`` """ + urls = property(lambda self: self._pretrigger.urls) + """A tuple containing all URLs found in the text message. + + :type: tuple + + These are the URLs found in the text message of the IRC line, usually after + the ` :` section. + """ tags = property(lambda self: self._pretrigger.tags) """A map of the IRCv3 message tags on the message.