internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: sopel-irc/sopel

Issue:
bot.db.unalias_nick() behavior does not match docs
> ```
> Raises ValueError if there is not at least one other nick in the group.
> ```

The ValueError is never raised:

```
<dgw> .nickunmerge ThisIsATestNick
<Sopel> Removed ThisIsATestNick from nick group 1497.
<dgw> that's not supposed to work
<ThisIsATestNick> blerg
<dgw> .nickunmerge ThisIsATestNick
<Sopel> Removed ThisIsATestNick from nick group 1498.
```

I have traced this issue to a logic error, and will submit a proposed fix when I'm back in front of my laptop later tonight.

If it's more desirable to change the documentation than the behavior, I'd be happy to rewrite that instead—but I believe that the described behavior is correct, and the observed behavior is a bug.



Discussion:

Resolving patch:
diff --git a/sopel/db.py b/sopel/db.py
--- a/sopel/db.py
+++ b/sopel/db.py
@@ -174,7 +174,7 @@ def unalias_nick(self, alias):
         nick_id = self.get_nick_id(alias, False)
         count = self.execute('SELECT COUNT(*) FROM nicknames WHERE nick_id = ?',
                              [nick_id]).fetchone()[0]
-        if count == 0:
+        if count <= 1:
             raise ValueError('Given alias is the only entry in its group.')
         self.execute('DELETE FROM nicknames WHERE slug = ?', [alias.lower()])
 


Test patch:
Repository: sopel-irc/sopel

Issue:
Sopel not aware of topic changes
As far as I can tell Sopel only keeps track of the topic upon entering a channel, if I use either /topic from my IRC client or .topic to make it change the topic its completely unaware of any changes and if I run the command I built to show the current topic (ideally to be run before invoking another command to change the topic) it never detects changed topics even if I force a reload of the bot, the only way it will detect a new topic is if I kill it and restart it or join/part the channel.

Running Sopel 6.3.1 on Centos 7.2.1511 and its been installed and updated via pip

Code in question that I used to test this is here 

https://gist.github.com/dword4/3e0d38421cf81822af18d698adc3e709



Discussion:
There is topic tracking near the end of `coretasks.py` (around line 700 in current master), but it only updates `bot.channels`, not `bot.privileges`.

That is where I pull the topic from in my test code but as far as I can tell its only updated when the bot first joins a channel

@dword4 may I ask what network you're experiencing this on? If I recall correctly there's some ircds which handle topic changes differently and send non-spec reply codes.

InspIRCd-2.0 is what it identifies as when I connect, if necessary I can get the full version if it will make a difference.

InspIRCd sends topic updates using the `TOPIC` prefix (https://github.com/inspircd/inspircd/blob/4a12fc9d4bfb77fc2fc05c1052c66294e3774649/src/channels.cpp#L49) . Should be easy enough to add support for it in the existing code, just another `@event` decorator.


Resolving patch:
diff --git a/sopel/coretasks.py b/sopel/coretasks.py
--- a/sopel/coretasks.py
+++ b/sopel/coretasks.py
@@ -696,12 +696,16 @@ def track_notify(bot, trigger):
 
 
 @sopel.module.rule('.*')
+@sopel.module.event('TOPIC')
 @sopel.module.event(events.RPL_TOPIC)
 @sopel.module.priority('high')
 @sopel.module.thread(False)
 @sopel.module.unblockable
 def track_topic(bot, trigger):
-    channel = trigger.args[1]
+    if trigger.event != 'TOPIC':
+        channel = trigger.args[1]
+    else:
+        channel = trigger.args[0]
     if channel not in bot.channels:
         return
     bot.channels[channel].topic = trigger.args[-1]


Test patch:
Repository: sopel-irc/sopel

Issue:
Fixed request.exceptions.SSLError
## Bot throw SSLerror
## requests.packages.urllib3.exceptions.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:645)

even when 'verify_ssl = False'
The core.verify_ssl was not passed to url.find_title()

Signed-off-by: Sachin Patil psachin@redhat.com



Discussion:

Resolving patch:
diff --git a/sopel/modules/meetbot.py b/sopel/modules/meetbot.py
--- a/sopel/modules/meetbot.py
+++ b/sopel/modules/meetbot.py
@@ -342,7 +342,7 @@ def meetinglink(bot, trigger):
     if not link.startswith("http"):
         link = "http://" + link
     try:
-        title = find_title(link)
+        title = find_title(link, verify=bot.config.core.verify_ssl)
     except:
         title = ''
     logplain('LINK: %s [%s]' % (link, title), trigger.sender)
diff --git a/sopel/modules/url.py b/sopel/modules/url.py
--- a/sopel/modules/url.py
+++ b/sopel/modules/url.py
@@ -158,7 +158,7 @@ def process_urls(bot, trigger, urls):
             if matched:
                 continue
             # Finally, actually show the URL
-            title = find_title(url)
+            title = find_title(url, verify=bot.config.core.verify_ssl)
             if title:
                 results.append((title, get_hostname(url)))
     return results
@@ -182,9 +182,9 @@ def check_callbacks(bot, trigger, url, run=True):
     return matched
 
 
-def find_title(url):
+def find_title(url, verify=True):
     """Return the title for the given URL."""
-    response = requests.get(url, stream=True)
+    response = requests.get(url, stream=True, verify=verify)
     try:
         content = ''
         for byte in response.iter_content(chunk_size=512, decode_unicode=True):


Test patch:
Repository: sopel-irc/sopel

Issue:
[etymology] HTML entities not decoded before output
The `etymology` module does not decode HTML entities in the snippet before sending it to the channel. This results in printing snippets like this:

````
<Sopel> "Old English w&aelig;ter, from Proto-Germanic *watar (source also
        of Old Saxon watar, Old Frisian wetir, Dutch water, Old High German
        wazzar, German Wasser, Old Norse vatn, Gothic wato 'water'), from
        PIE *wod-or, from root *wed- (1) 'water, wet' (source also of
        Hittite [...]" - http://etymonline.com/?term=water
````


Discussion:
Hello,

I added
` s = s.replace('&aelig;', "\u00E6")`
under
` def unescape(s): `

so it now reads: 


```
def unescape(s):
    s = s.replace('&gt;', '>')
    s = s.replace('&lt;', '<')
    s = s.replace('&amp;', '&')
    s = s.replace('&aelig;', "\u00E6")
    return s
```

Giving us the Following output:

> <@VaultsBot>: "Old English wæter, from Proto-Germanic *watar (source also of Old Saxon watar, Old Frisian wetir, Dutch water, Old High German wazzar, German Wasser, Old Norse vatn, Gothic wato 'water'), from PIE *wod-or, from root *wed- (1) 'water, wet' (source also of Hittite watar, [...]" - http://etymonline.com/?term=water

Resolving patch:
diff --git a/sopel/modules/etymology.py b/sopel/modules/etymology.py
--- a/sopel/modules/etymology.py
+++ b/sopel/modules/etymology.py
@@ -8,6 +8,14 @@
 """
 from __future__ import unicode_literals, absolute_import, print_function, division
 
+try:
+    from html import unescape
+except ImportError:
+    from HTMLParser import HTMLParser
+
+    # pep8 dictates a blank line here...
+    def unescape(s):
+        return HTMLParser.unescape.__func__(HTMLParser, s)
 import re
 from sopel import web
 from sopel.module import commands, example, NOLIMIT
@@ -29,13 +37,6 @@
 r_sentence = re.compile(t_sentence % ')(?<!'.join(abbrs))
 
 
-def unescape(s):
-    s = s.replace('&gt;', '>')
-    s = s.replace('&lt;', '<')
-    s = s.replace('&amp;', '&')
-    return s
-
-
 def text(html):
     html = r_tag.sub('', html)
     html = r_whitespace.sub(' ', html)


Test patch:
Repository: sopel-irc/sopel

Issue:
Chanserv auth_method?
Is there an option for the [auth_method](https://sopel.chat/docs/config.html#the-core-configuration-section) that's equivalent to `chanserv`?


Discussion:

Resolving patch:
diff --git a/sopel/config/core_section.py b/sopel/config/core_section.py
--- a/sopel/config/core_section.py
+++ b/sopel/config/core_section.py
@@ -50,10 +50,10 @@ class CoreSection(StaticSection):
     capabilities."""
 
     auth_method = ChoiceAttribute('auth_method', choices=[
-        'nickserv', 'authserv', 'Q', 'sasl', 'server'])
+        'nickserv', 'authserv', 'Q', 'sasl', 'server', 'userserv'])
     """The method to use to authenticate with the server.
 
-    Can be ``nickserv``, ``authserv``, ``Q``, ``sasl``, or ``server``."""
+    Can be ``nickserv``, ``authserv``, ``Q``, ``sasl``, or ``server`` or ``userserv``."""
 
     auth_password = ValidatedAttribute('auth_password')
     """The password to use to authenticate with the server."""
diff --git a/sopel/coretasks.py b/sopel/coretasks.py
--- a/sopel/coretasks.py
+++ b/sopel/coretasks.py
@@ -50,7 +50,7 @@ def auth_after_register(bot):
             'AUTHSERV auth',
             account + ' ' + password
         ))
-    
+
     elif bot.config.core.auth_method == 'Q':
         account = bot.config.core.auth_username
         password = bot.config.core.auth_password
@@ -59,6 +59,13 @@ def auth_after_register(bot):
             account + ' ' + password
         ))
 
+    elif bot.config.core.auth_method == 'userserv':
+        userserv_name = bot.config.core.auth_target or 'UserServ'
+        account = bot.config.core.auth_username
+        password = bot.config.core.auth_password
+        bot.msg(userserv_name, "LOGIN {account} {password}".format(
+                account=account, password=password))
+
 
 @sopel.module.event(events.RPL_WELCOME, events.RPL_LUSERCLIENT)
 @sopel.module.rule('.*')


Test patch:
Repository: sopel-irc/sopel

Issue:
FreeBSD [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:777)
sopel/config/core_section.py
#  python3.6 [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:777)
```
from sys import platform

if 'freebsd' in platform:
    certs = '/etc/ssl/cert.pem'

def _find_certs():
    certs = '/etc/pki/tls/cert.pem'
    if not os.path.isfile(certs):
        certs = '/etc/ssl/certs/ca-certificates.crt'
        if not os.path.isfile(certs):
            return None
    return certs

```
Seems like there is missing a good validation on what platform it is running on and where the certs are stored.
 


Discussion:

Resolving patch:
diff --git a/sopel/config/core_section.py b/sopel/config/core_section.py
--- a/sopel/config/core_section.py
+++ b/sopel/config/core_section.py
@@ -12,12 +12,26 @@
 
 
 def _find_certs():
-    certs = '/etc/pki/tls/cert.pem'
-    if not os.path.isfile(certs):
-        certs = '/etc/ssl/certs/ca-certificates.crt'
-        if not os.path.isfile(certs):
-            return None
-    return certs
+    """
+    Find the TLS root CA store.
+
+    :returns: str (path to file)
+    """
+    # check if the root CA store is at a known location
+    locations = [
+        '/etc/pki/tls/cert.pem',  # best first guess
+        '/etc/ssl/certs/ca-certificates.crt',  # Debian
+        '/etc/ssl/cert.pem',  # FreeBSD base OpenSSL
+        '/usr/local/openssl/cert.pem',  # FreeBSD userland OpenSSL
+        '/etc/pki/tls/certs/ca-bundle.crt',  # RHEL 6 / Fedora
+        '/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem',  # RHEL 7 / CentOS
+        '/etc/pki/tls/cacert.pem',  # OpenELEC
+        '/etc/ssl/ca-bundle.pem',  # OpenSUSE
+        ]
+    for certs in locations:
+        if os.path.isfile(certs):
+            return certs
+    return None
 
 
 def configure(config):


Test patch:
Repository: sopel-irc/sopel

Issue:
Results from currency conversion should be rounded to 2 decimals
At the moment, the results of a currency conversion query is reported as a float:

    < mynick> .cur 100 SEK to USD
    < mybot> 100.0 SEK (Swedish krona) = 12.202017114914426 USD (US dollar)

As far as I know, no currency reports more than 2 decimals, and some even less (JPY comes to mind). Rounding the result to 2 decimals will result in more readable output.

    < mynick> .cur 100 SEK to USD
    < mybot> 100.0 SEK (Swedish krona) = 12.20 USD (US dollar)



Discussion:

Resolving patch:
diff --git a/sopel/modules/currency.py b/sopel/modules/currency.py
--- a/sopel/modules/currency.py
+++ b/sopel/modules/currency.py
@@ -74,7 +74,7 @@ def display(bot, amount, of, to):
         return NOLIMIT
 
     result = amount / of_rate * to_rate
-    bot.say("{} {} ({}) = {} {} ({})".format(amount, of.upper(), of_name,
+    bot.say("{:.2f} {} ({}) = {:.2f} {} ({})".format(amount, of.upper(), of_name,
                                              result, to.upper(), to_name))
 
 


Test patch:
Repository: sopel-irc/sopel

Issue:
[wikipedia] Queries with e.g. & do not work properly
````
<dgw> ;w Miraculous Ladybug
<Kaede> KeyError: u'extract' (file "/usr/local/lib/python2.7/dist-packages/sopel/modules/wikipedia.py",
        line 87, in mw_snippet)
[…]
<dgw> https://en.wikipedia.org/wiki/Miraculous:_Tales_of_Ladybug_%26_Cat_Noir
<Kaede> KeyError: u'extract' (file "/usr/local/lib/python2.7/dist-packages/sopel/modules/wikipedia.py",
        line 87, in mw_snippet)
````

Basically, if a search returns a page title that contains `&`, e.g. "Miraculous: Tales of Ladybug & Cat Noir", the `wikipedia` module winds up trying to retrieve a snippet for, e.g., "Miraculous: Tales of Ladybug " and failing to do so because the page doesn't exist.

I have a fix for this in testing, but wanted to open the issue so anyone searching for the problem would find out that it is already on the radar.
Small fix for URLs with "+" in them



Discussion:
https://github.com/sopel-irc/sopel/pull/1252/commits/17c44c08aa971a1b787964e07792ea36919a0982 in https://github.com/sopel-irc/sopel/pull/1252


Resolving patch:
diff --git a/sopel/modules/wikipedia.py b/sopel/modules/wikipedia.py
--- a/sopel/modules/wikipedia.py
+++ b/sopel/modules/wikipedia.py
@@ -10,10 +10,12 @@
 
 import sys
 if sys.version_info.major < 3:
+    from urllib import quote as _quote
     from urlparse import unquote as _unquote
+    quote = lambda s: _quote(s.encode('utf-8')).decode('utf-8')
     unquote = lambda s: _unquote(s.encode('utf-8')).decode('utf-8')
 else:
-    from urllib.parse import unquote
+    from urllib.parse import quote, unquote
 
 REDIRECT = re.compile(r'^REDIRECT (.*)')
 
@@ -60,7 +62,7 @@ def mw_search(server, query, num):
 
 def say_snippet(bot, server, query, show_url=True):
     page_name = query.replace('_', ' ')
-    query = query.replace(' ', '_')
+    query = quote(query.replace(' ', '_'))
     try:
         snippet = mw_snippet(server, query)
     except KeyError:


Test patch:
Repository: sopel-irc/sopel

Issue:
[wikipedia] Error on nonexistent article link
I productively (? :laughing:) found another issue in the `wikipedia` module while testing my fix for #1255. It turns out that, if someone sends a link to a Wikipedia article that doesn't exist, the bot spits out a nice `KeyError`. Better to spit out a clean error (or, perhaps, nothing).

As with #1255, I'm testing a fix for this on my own Sopel instance. Once I'm happy with it (and the wording of whatever error message I add), there will be a PR for feedback before merging.


Discussion:

Resolving patch:
diff --git a/sopel/modules/wikipedia.py b/sopel/modules/wikipedia.py
--- a/sopel/modules/wikipedia.py
+++ b/sopel/modules/wikipedia.py
@@ -61,7 +61,12 @@ def mw_search(server, query, num):
 def say_snippet(bot, server, query, show_url=True):
     page_name = query.replace('_', ' ')
     query = query.replace(' ', '_')
-    snippet = mw_snippet(server, query)
+    try:
+        snippet = mw_snippet(server, query)
+    except KeyError:
+        if show_url:
+            bot.say("[WIKIPEDIA] Error fetching snippet for \"{}\".".format(page_name))
+        return
     msg = '[WIKIPEDIA] {} | "{}"'.format(page_name, snippet)
     if show_url:
         msg = msg + ' | https://{}/wiki/{}'.format(server, query)


Test patch:
Repository: sopel-irc/sopel

Issue:
[Bugzilla] Error calling shutdown method for module bugzilla:None
Noticed this in my logs. Bugzilla shutdown throwing none. On Sopel 6.5.3, Python 3.5.3.

```
Ping timeout reached after 120 seconds, closing connection
Calling shutdown for 2 modules.
calling reddit.shutdown
calling bugzilla.shutdown
Error calling shutdown method for module bugzilla:None
Closed!
Warning: Disconnected. Reconnecting in 20 seconds...
 
Welcome to Sopel. Loading modules...
```


Discussion:
I noticed this the other day while testing things, too. Haven't looked into it yet, since it seems harmless… Thanks for the report though :smile_cat: 
Actually it looks like a simple fix. The `url_callbacks` SopelMemory is never initialized if the bugzilla module isn't given any domains to look for (`setup()` returns without doing anything `if not bot.config.bugzilla.domains`), then `shutdown()` tries to `del` the key `None` from a nonexistent dict. The message shown in the error is the exception message from a `KeyError` raised by `del`, which returns the name of the key that couldn't be `del`eted. In this case, `None` (the uninitialized value of `regex`).

Self-assigning because I already found the problem. Just have to find a slightly longer window to properly write and test the patch. Maybe Wednesday. Certainly sometime later this week…

Resolving patch:
diff --git a/sopel/modules/bugzilla.py b/sopel/modules/bugzilla.py
--- a/sopel/modules/bugzilla.py
+++ b/sopel/modules/bugzilla.py
@@ -52,7 +52,12 @@ def setup(bot):
 
 
 def shutdown(bot):
-    del bot.memory['url_callbacks'][regex]
+    try:
+        del bot.memory['url_callbacks'][regex]
+    except KeyError:
+        # bot.config.bugzilla.domains was probably just empty on startup
+        # everything's daijoubu
+        pass
 
 
 @rule(r'.*https?://(\S+?)'


Test patch:
Repository: sopel-irc/sopel

Issue:
raw logging defaults to true...necessary?
sopel [by default](https://github.com/sopel-irc/sopel/blob/master/sopel/config/core_section.py#L134) does raw logging.  
You would need to manually add log_raw = False to config file to disable it.
Just wondering if having it on by default is really that necessary?


Discussion:
Ha, no it's not. That's a silly default, good catch.

Resolving patch:
diff --git a/sopel/config/core_section.py b/sopel/config/core_section.py
--- a/sopel/config/core_section.py
+++ b/sopel/config/core_section.py
@@ -131,7 +131,7 @@ def homedir(self):
 
     Regular expression syntax is used"""
 
-    log_raw = ValidatedAttribute('log_raw', bool, default=True)
+    log_raw = ValidatedAttribute('log_raw', bool, default=False)
     """Whether a log of raw lines as sent and received should be kept."""
 
     logdir = FilenameAttribute('logdir', directory=True, default='logs')


Test patch:
Repository: sopel-irc/sopel

Issue:
search: Unicode URLs get mojibake'd under Python 2
Behold, the curse of bad Unicode handling:

````
23:33:27 <~dgw>  ;g grandorder.wiki chulainn alter
23:33:28 <&Kaede>  dgw: https://grandorder.wiki/CÃº_Chulainn_(Alter)
````

Versus the glory of Python 3:

````
01:00:10 <~dgw>  .g grandorder.wiki chulainn alter
01:00:11 <Sopel>  dgw: https://grandorder.wiki/Cú_Chulainn_(Alter)
````

Thanks to @prahulkgp for triggering this error, so I could find it. I've already added a test case using this exact query locally, both to verify the fix and to see that it doesn't break later. (Here's hoping it doesn't start bugging out Travis builds like the existing Bing tests sometimes do.)


Discussion:

Resolving patch:
diff --git a/sopel/modules/search.py b/sopel/modules/search.py
--- a/sopel/modules/search.py
+++ b/sopel/modules/search.py
@@ -12,7 +12,8 @@
 import sys
 
 if sys.version_info.major < 3:
-    from urllib import quote_plus, unquote
+    from urllib import quote_plus, unquote as _unquote
+    unquote = lambda s: _unquote(s.encode('utf-8')).decode('utf-8')
 else:
     from urllib.parse import quote_plus, unquote
 
@@ -76,6 +77,9 @@ def duck_api(query):
 
 
 @commands('duck', 'ddg', 'g')
+# test for bad Unicode handling in py2
+@example('.duck grandorder.wiki chulainn alter', 'https://grandorder.wiki/Cú_Chulainn_(Alter)')
+# the last example is what .help displays
 @example('.duck sopel irc bot', r'https?:\/\/sopel\.chat\/?', re=True)
 def duck(bot, trigger):
     """Queries Duck Duck Go for the specified input."""


Test patch:
Repository: sopel-irc/sopel

Issue:
.title command should say an error if no title is found
Example: http://zimage.com/~ant/temp/notitle.html

Sopel should say there was no web page title found.


Discussion:
That page does have a title…

````HTML
<head>
<title>Error 404</title>
</head>
````

I'm not sure there's a whole lot of benefit to spitting out anything if a link has no title. That would also mean spitting out "No title found" (or whatever the message would say) for links that never have titles, like images, video, PDFs, etc.

Granted, Sopel could output the error only if the link actually returns `text/html` or related MIME-type. But mostly, I think the `url` module should simply say nothing if it can't say something useful.

But I won't close this immediately, for visibility. Tagging for 6.6.0, and a final decision will be made closer to release of that version based on any further comments made between now and then.
dgw: Sorry, I had to change the original link.  I replaced it with http://zimage.com/~ant/temp/notitle.html as an example. You were too fast, or maybe I am too slow. :)
Why not both? :)

Either way, I still don't think `url` should say anything unless it's functionally useful. Arguably (and this is where feedback from other users comes in), "No title found" is not functionally useful and could be considered just channel noise.
Makes sense if you use the ".title" command but not for links that appear by themselves or as a part of a message in the channel.
OK, I'm going to update the issue title because I agree with @kwaaak. `.title` should return an error of some kind if there is no title found, but URLs just fetched from chat shouldn't output anything in that case.

Resolving patch:
diff --git a/sopel/modules/url.py b/sopel/modules/url.py
--- a/sopel/modules/url.py
+++ b/sopel/modules/url.py
@@ -104,6 +104,13 @@ def title_command(bot, trigger):
     for title, domain in results[:4]:
         bot.reply('[ %s ] - %s' % (title, domain))
 
+    # Nice to have different failure messages for one-and-only requested URL
+    # failed vs. one-of-many failed.
+    if len(urls) == 1 and not results:
+        bot.reply('Sorry, fetching that title failed. Make sure the site is working.')
+    elif len(urls) > len(results):
+        bot.reply('I couldn\'t get all of the titles, but I fetched what I could!')
+
 
 @rule('(?u).*(https?://\S+).*')
 def title_auto(bot, trigger):


Test patch:
Repository: sopel-irc/sopel

Issue:
Ctrl-C doesn't run shutdown routines
When pressing <kbd>Ctrl</kbd>-<kbd>C</kbd> to interrupt a Sopel instance running in the foreground of an active shell, it simply prints `KeyboardInterrupt` and exits seemingly without calling any of the shutdown routines.

Pressing <kbd>Ctrl</kbd>-<kbd>C</kbd> should behave more or less the same as `sopel --quit`.

Discovered while testing for #1369.


Discussion:

Resolving patch:
diff --git a/sopel/__init__.py b/sopel/__init__.py
--- a/sopel/__init__.py
+++ b/sopel/__init__.py
@@ -68,7 +68,7 @@ def run(config, pid_file, daemon=False):
                'work properly.')
 
     def signal_handler(sig, frame):
-        if sig == signal.SIGUSR1 or sig == signal.SIGTERM:
+        if sig == signal.SIGUSR1 or sig == signal.SIGTERM or sig == signal.SIGINT:
             stderr('Got quit signal, shutting down.')
             p.quit('Closing')
     while True:
@@ -78,6 +78,8 @@ def signal_handler(sig, frame):
                 signal.signal(signal.SIGUSR1, signal_handler)
             if hasattr(signal, 'SIGTERM'):
                 signal.signal(signal.SIGTERM, signal_handler)
+            if hasattr(signal, 'SIGINT'):
+                signal.signal(signal.SIGINT, signal_handler)
             sopel.logger.setup_logging(p)
             p.run(config.core.host, int(config.core.port))
         except KeyboardInterrupt:


Test patch: