prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN 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.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `sopel/modules/wikipedia.py`
  Content:
  ```
  1 # coding=utf-8
  2 # Copyright 2013 Elsie Powell - embolalia.com
  3 # Licensed under the Eiffel Forum License 2.
  4 from __future__ import unicode_literals, absolute_import, print_function, division
  5 from sopel import web, tools
  6 from sopel.config.types import StaticSection, ValidatedAttribute
  7 from sopel.module import NOLIMIT, commands, example, rule
  8 import json
  9 import re
  10 
  11 import sys
  12 if sys.version_info.major < 3:
  13     from urlparse import unquote as _unquote
  14     unquote = lambda s: _unquote(s.encode('utf-8')).decode('utf-8')
  15 else:
  16     from urllib.parse import unquote
  17 
  18 REDIRECT = re.compile(r'^REDIRECT (.*)')
  19 
  20 
  21 class WikipediaSection(StaticSection):
  22     default_lang = ValidatedAttribute('default_lang', default='en')
  23     """The default language to find articles from."""
  24     lang_per_channel = ValidatedAttribute('lang_per_channel')
  25 
  26 
  27 def setup(bot):
  28     bot.config.define_section('wikipedia', WikipediaSection)
  29 
  30     regex = re.compile('([a-z]+).(wikipedia.org/wiki/)([^ ]+)')
  31     if not bot.memory.contains('url_callbacks'):
  32         bot.memory['url_callbacks'] = tools.SopelMemory()
  33     bot.memory['url_callbacks'][regex] = mw_info
  34 
  35 
  36 def configure(config):
  37     config.define_section('wikipedia', WikipediaSection)
  38     config.wikipedia.configure_setting(
  39         'default_lang',
  40         "Enter the default language to find articles from."
  41     )
  42 
  43 
  44 def mw_search(server, query, num):
  45     """
  46     Searches the specified MediaWiki server for the given query, and returns
  47     the specified number of results.
  48     """
  49     search_url = ('http://%s/w/api.php?format=json&action=query'
  50                   '&list=search&srlimit=%d&srprop=timestamp&srwhat=text'
  51                   '&srsearch=') % (server, num)
  52     search_url += query
  53     query = json.loads(web.get(search_url))
  54     if 'query' in query:
  55         query = query['query']['search']
  56         return [r['title'] for r in query]
  57     else:
  58         return None
  59 
  60 
  61 def say_snippet(bot, server, query, show_url=True):
  62     page_name = query.replace('_', ' ')
  63     query = query.replace(' ', '_')
  64     snippet = mw_snippet(server, query)
  65     msg = '[WIKIPEDIA] {} | "{}"'.format(page_name, snippet)
  66     if show_url:
  67         msg = msg + ' | https://{}/wiki/{}'.format(server, query)
  68     bot.say(msg)
  69 
  70 
  71 def mw_snippet(server, query):
  72     """
  73     Retrives a snippet of the specified length from the given page on the given
  74     server.
  75     """
  76     snippet_url = ('https://' + server + '/w/api.php?format=json'
  77                    '&action=query&prop=extracts&exintro&explaintext'
  78                    '&exchars=300&redirects&titles=')
  79     snippet_url += query
  80     snippet = json.loads(web.get(snippet_url))
  81     snippet = snippet['query']['pages']
  82 
  83     # For some reason, the API gives the page *number* as the key, so we just
  84     # grab the first page number in the results.
  85     snippet = snippet[list(snippet.keys())[0]]
  86 
  87     return snippet['extract']
  88 
  89 
  90 @rule('.*\/([a-z]+\.wikipedia.org)\/wiki\/((?!File\:)[^ ]+).*')
  91 def mw_info(bot, trigger, found_match=None):
  92     """
  93     Retrives a snippet of the specified length from the given page on the given
  94     server.
  95     """
  96     match = found_match or trigger
  97     say_snippet(bot, match.group(1), unquote(match.group(2)), show_url=False)
  98 
  99 
  100 @commands('w', 'wiki', 'wik')
  101 @example('.w San Francisco')
  102 def wikipedia(bot, trigger):
  103     lang = bot.config.wikipedia.default_lang
  104 
  105     #change lang if channel has custom language set
  106     if (trigger.sender and not trigger.sender.is_nick() and
  107             bot.config.wikipedia.lang_per_channel):
  108         customlang = re.search('(' + trigger.sender + '):(\w+)',
  109                                bot.config.wikipedia.lang_per_channel)
  110         if customlang is not None:
  111             lang = customlang.group(2)
  112 
  113     if trigger.group(2) is None:
  114         bot.reply("What do you want me to look up?")
  115         return NOLIMIT
  116 
  117     query = trigger.group(2)
  118     args = re.search(r'^-([a-z]{2,12})\s(.*)', query)
  119     if args is not None:
  120         lang = args.group(1)
  121         query = args.group(2)
  122 
  123     if not query:
  124         bot.reply('What do you want me to look up?')
  125         return NOLIMIT
  126     server = lang + '.wikipedia.org'
  127     query = mw_search(server, query, 1)
  128     if not query:
  129         bot.reply("I can't find any results for that.")
  130         return NOLIMIT
  131     else:
  132         query = query[0]
  133     say_snippet(bot, server, query)
  134
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: 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 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)
  
verification_info: {"golden_diff": "diff --git a/sopel/modules/wikipedia.py b/sopel/modules/wikipedia.py\n--- a/sopel/modules/wikipedia.py\n+++ b/sopel/modules/wikipedia.py\n@@ -61,7 +61,12 @@\n def say_snippet(bot, server, query, show_url=True):\n     page_name = query.replace('_', ' ')\n     query = query.replace(' ', '_')\n-    snippet = mw_snippet(server, query)\n+    try:\n+        snippet = mw_snippet(server, query)\n+    except KeyError:\n+        if show_url:\n+            bot.say(\"[WIKIPEDIA] Error fetching snippet for \\\"{}\\\".\".format(page_name))\n+        return\n     msg = '[WIKIPEDIA] {} | \"{}\"'.format(page_name, snippet)\n     if show_url:\n         msg = msg + ' | https://{}/wiki/{}'.format(server, query)\n", "issue": "[wikipedia] Error on nonexistent article link\nI 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).\r\n\r\nAs 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.\n", "before_files": [{"content": "# coding=utf-8\n# Copyright 2013 Elsie Powell - embolalia.com\n# Licensed under the Eiffel Forum License 2.\nfrom __future__ import unicode_literals, absolute_import, print_function, division\nfrom sopel import web, tools\nfrom sopel.config.types import StaticSection, ValidatedAttribute\nfrom sopel.module import NOLIMIT, commands, example, rule\nimport json\nimport re\n\nimport sys\nif sys.version_info.major < 3:\n    from urlparse import unquote as _unquote\n    unquote = lambda s: _unquote(s.encode('utf-8')).decode('utf-8')\nelse:\n    from urllib.parse import unquote\n\nREDIRECT = re.compile(r'^REDIRECT (.*)')\n\n\nclass WikipediaSection(StaticSection):\n    default_lang = ValidatedAttribute('default_lang', default='en')\n    \"\"\"The default language to find articles from.\"\"\"\n    lang_per_channel = ValidatedAttribute('lang_per_channel')\n\n\ndef setup(bot):\n    bot.config.define_section('wikipedia', WikipediaSection)\n\n    regex = re.compile('([a-z]+).(wikipedia.org/wiki/)([^ ]+)')\n    if not bot.memory.contains('url_callbacks'):\n        bot.memory['url_callbacks'] = tools.SopelMemory()\n    bot.memory['url_callbacks'][regex] = mw_info\n\n\ndef configure(config):\n    config.define_section('wikipedia', WikipediaSection)\n    config.wikipedia.configure_setting(\n        'default_lang',\n        \"Enter the default language to find articles from.\"\n    )\n\n\ndef mw_search(server, query, num):\n    \"\"\"\n    Searches the specified MediaWiki server for the given query, and returns\n    the specified number of results.\n    \"\"\"\n    search_url = ('http://%s/w/api.php?format=json&action=query'\n                  '&list=search&srlimit=%d&srprop=timestamp&srwhat=text'\n                  '&srsearch=') % (server, num)\n    search_url += query\n    query = json.loads(web.get(search_url))\n    if 'query' in query:\n        query = query['query']['search']\n        return [r['title'] for r in query]\n    else:\n        return None\n\n\ndef say_snippet(bot, server, query, show_url=True):\n    page_name = query.replace('_', ' ')\n    query = query.replace(' ', '_')\n    snippet = mw_snippet(server, query)\n    msg = '[WIKIPEDIA] {} | \"{}\"'.format(page_name, snippet)\n    if show_url:\n        msg = msg + ' | https://{}/wiki/{}'.format(server, query)\n    bot.say(msg)\n\n\ndef mw_snippet(server, query):\n    \"\"\"\n    Retrives a snippet of the specified length from the given page on the given\n    server.\n    \"\"\"\n    snippet_url = ('https://' + server + '/w/api.php?format=json'\n                   '&action=query&prop=extracts&exintro&explaintext'\n                   '&exchars=300&redirects&titles=')\n    snippet_url += query\n    snippet = json.loads(web.get(snippet_url))\n    snippet = snippet['query']['pages']\n\n    # For some reason, the API gives the page *number* as the key, so we just\n    # grab the first page number in the results.\n    snippet = snippet[list(snippet.keys())[0]]\n\n    return snippet['extract']\n\n\n@rule('.*\\/([a-z]+\\.wikipedia.org)\\/wiki\\/((?!File\\:)[^ ]+).*')\ndef mw_info(bot, trigger, found_match=None):\n    \"\"\"\n    Retrives a snippet of the specified length from the given page on the given\n    server.\n    \"\"\"\n    match = found_match or trigger\n    say_snippet(bot, match.group(1), unquote(match.group(2)), show_url=False)\n\n\n@commands('w', 'wiki', 'wik')\n@example('.w San Francisco')\ndef wikipedia(bot, trigger):\n    lang = bot.config.wikipedia.default_lang\n\n    #change lang if channel has custom language set\n    if (trigger.sender and not trigger.sender.is_nick() and\n            bot.config.wikipedia.lang_per_channel):\n        customlang = re.search('(' + trigger.sender + '):(\\w+)',\n                               bot.config.wikipedia.lang_per_channel)\n        if customlang is not None:\n            lang = customlang.group(2)\n\n    if trigger.group(2) is None:\n        bot.reply(\"What do you want me to look up?\")\n        return NOLIMIT\n\n    query = trigger.group(2)\n    args = re.search(r'^-([a-z]{2,12})\\s(.*)', query)\n    if args is not None:\n        lang = args.group(1)\n        query = args.group(2)\n\n    if not query:\n        bot.reply('What do you want me to look up?')\n        return NOLIMIT\n    server = lang + '.wikipedia.org'\n    query = mw_search(server, query, 1)\n    if not query:\n        bot.reply(\"I can't find any results for that.\")\n        return NOLIMIT\n    else:\n        query = query[0]\n    say_snippet(bot, server, query)\n", "path": "sopel/modules/wikipedia.py"}], "after_files": [{"content": "# coding=utf-8\n# Copyright 2013 Elsie Powell - embolalia.com\n# Licensed under the Eiffel Forum License 2.\nfrom __future__ import unicode_literals, absolute_import, print_function, division\nfrom sopel import web, tools\nfrom sopel.config.types import StaticSection, ValidatedAttribute\nfrom sopel.module import NOLIMIT, commands, example, rule\nimport json\nimport re\n\nimport sys\nif sys.version_info.major < 3:\n    from urlparse import unquote as _unquote\n    unquote = lambda s: _unquote(s.encode('utf-8')).decode('utf-8')\nelse:\n    from urllib.parse import unquote\n\nREDIRECT = re.compile(r'^REDIRECT (.*)')\n\n\nclass WikipediaSection(StaticSection):\n    default_lang = ValidatedAttribute('default_lang', default='en')\n    \"\"\"The default language to find articles from.\"\"\"\n    lang_per_channel = ValidatedAttribute('lang_per_channel')\n\n\ndef setup(bot):\n    bot.config.define_section('wikipedia', WikipediaSection)\n\n    regex = re.compile('([a-z]+).(wikipedia.org/wiki/)([^ ]+)')\n    if not bot.memory.contains('url_callbacks'):\n        bot.memory['url_callbacks'] = tools.SopelMemory()\n    bot.memory['url_callbacks'][regex] = mw_info\n\n\ndef configure(config):\n    config.define_section('wikipedia', WikipediaSection)\n    config.wikipedia.configure_setting(\n        'default_lang',\n        \"Enter the default language to find articles from.\"\n    )\n\n\ndef mw_search(server, query, num):\n    \"\"\"\n    Searches the specified MediaWiki server for the given query, and returns\n    the specified number of results.\n    \"\"\"\n    search_url = ('http://%s/w/api.php?format=json&action=query'\n                  '&list=search&srlimit=%d&srprop=timestamp&srwhat=text'\n                  '&srsearch=') % (server, num)\n    search_url += query\n    query = json.loads(web.get(search_url))\n    if 'query' in query:\n        query = query['query']['search']\n        return [r['title'] for r in query]\n    else:\n        return None\n\n\ndef say_snippet(bot, server, query, show_url=True):\n    page_name = query.replace('_', ' ')\n    query = query.replace(' ', '_')\n    try:\n        snippet = mw_snippet(server, query)\n    except KeyError:\n        if show_url:\n            bot.say(\"[WIKIPEDIA] Error fetching snippet for \\\"{}\\\".\".format(page_name))\n        return\n    msg = '[WIKIPEDIA] {} | \"{}\"'.format(page_name, snippet)\n    if show_url:\n        msg = msg + ' | https://{}/wiki/{}'.format(server, query)\n    bot.say(msg)\n\n\ndef mw_snippet(server, query):\n    \"\"\"\n    Retrives a snippet of the specified length from the given page on the given\n    server.\n    \"\"\"\n    snippet_url = ('https://' + server + '/w/api.php?format=json'\n                   '&action=query&prop=extracts&exintro&explaintext'\n                   '&exchars=300&redirects&titles=')\n    snippet_url += query\n    snippet = json.loads(web.get(snippet_url))\n    snippet = snippet['query']['pages']\n\n    # For some reason, the API gives the page *number* as the key, so we just\n    # grab the first page number in the results.\n    snippet = snippet[list(snippet.keys())[0]]\n\n    return snippet['extract']\n\n\n@rule('.*\\/([a-z]+\\.wikipedia.org)\\/wiki\\/((?!File\\:)[^ ]+).*')\ndef mw_info(bot, trigger, found_match=None):\n    \"\"\"\n    Retrives a snippet of the specified length from the given page on the given\n    server.\n    \"\"\"\n    match = found_match or trigger\n    say_snippet(bot, match.group(1), unquote(match.group(2)), show_url=False)\n\n\n@commands('w', 'wiki', 'wik')\n@example('.w San Francisco')\ndef wikipedia(bot, trigger):\n    lang = bot.config.wikipedia.default_lang\n\n    #change lang if channel has custom language set\n    if (trigger.sender and not trigger.sender.is_nick() and\n            bot.config.wikipedia.lang_per_channel):\n        customlang = re.search('(' + trigger.sender + '):(\\w+)',\n                               bot.config.wikipedia.lang_per_channel)\n        if customlang is not None:\n            lang = customlang.group(2)\n\n    if trigger.group(2) is None:\n        bot.reply(\"What do you want me to look up?\")\n        return NOLIMIT\n\n    query = trigger.group(2)\n    args = re.search(r'^-([a-z]{2,12})\\s(.*)', query)\n    if args is not None:\n        lang = args.group(1)\n        query = args.group(2)\n\n    if not query:\n        bot.reply('What do you want me to look up?')\n        return NOLIMIT\n    server = lang + '.wikipedia.org'\n    query = mw_search(server, query, 1)\n    if not query:\n        bot.reply(\"I can't find any results for that.\")\n        return NOLIMIT\n    else:\n        query = query[0]\n    say_snippet(bot, server, query)\n", "path": "sopel/modules/wikipedia.py"}]}