prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Problem in (at least) Wikipedia module: possibly Unicode related
  Hi,
  observe the following use case:
      https://en.wikipedia.org/wiki/Hir%C5%8D_Onoda
      @willie_5.4.1 KeyError: u'extract' (file "/usr/local/lib/python2.7/dist-packages/willie-5.4.1-py2.7.egg/willie/modules/wikipedia.py", line 89, in mw_snippet)
  
  
  --- 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 """
  3 wikipedia.py - Sopel Wikipedia Module
  4 Copyright 2013 Edward Powell - embolalia.net
  5 Licensed under the Eiffel Forum License 2.
  6 
  7 http://sopel.chat
  8 """
  9 from __future__ import unicode_literals, absolute_import, print_function, division
  10 from sopel import web, tools
  11 from sopel.config.types import StaticSection, ValidatedAttribute
  12 from sopel.module import NOLIMIT, commands, example, rule
  13 import json
  14 import re
  15 
  16 import sys
  17 if sys.version_info.major < 3:
  18     from urlparse import unquote
  19 else:
  20     from urllib.parse import unquote
  21 
  22 REDIRECT = re.compile(r'^REDIRECT (.*)')
  23 
  24 
  25 class WikipediaSection(StaticSection):
  26     default_lang = ValidatedAttribute('default_lang', default='en')
  27     """The default language to find articles from."""
  28     lang_per_channel = ValidatedAttribute('lang_per_channel')
  29 
  30 
  31 def setup(bot):
  32     bot.config.define_section('wikipedia', WikipediaSection)
  33 
  34     regex = re.compile('([a-z]+).(wikipedia.org/wiki/)([^ ]+)')
  35     if not bot.memory.contains('url_callbacks'):
  36         bot.memory['url_callbacks'] = tools.SopelMemory()
  37     bot.memory['url_callbacks'][regex] = mw_info
  38 
  39 
  40 def configure(config):
  41     config.define_section('wikipedia', WikipediaSection)
  42     config.wikipedia.configure_setting(
  43         'default_lang',
  44         "Enter the default language to find articles from."
  45     )
  46 
  47 
  48 def mw_search(server, query, num):
  49     """
  50     Searches the specified MediaWiki server for the given query, and returns
  51     the specified number of results.
  52     """
  53     search_url = ('http://%s/w/api.php?format=json&action=query'
  54                   '&list=search&srlimit=%d&srprop=timestamp&srwhat=text'
  55                   '&srsearch=') % (server, num)
  56     search_url += query
  57     query = json.loads(web.get(search_url))
  58     if 'query' in query:
  59         query = query['query']['search']
  60         return [r['title'] for r in query]
  61     else:
  62         return None
  63 
  64 
  65 def say_snippet(bot, server, query, show_url=True):
  66     page_name = query.replace('_', ' ')
  67     query = query.replace(' ', '_')
  68     snippet = mw_snippet(server, query)
  69     msg = '[WIKIPEDIA] {} | "{}"'.format(page_name, snippet)
  70     if show_url:
  71         msg = msg + ' | https://{}/wiki/{}'.format(server, query)
  72     bot.say(msg)
  73 
  74 
  75 def mw_snippet(server, query):
  76     """
  77     Retrives a snippet of the specified length from the given page on the given
  78     server.
  79     """
  80     snippet_url = ('https://' + server + '/w/api.php?format=json'
  81                    '&action=query&prop=extracts&exintro&explaintext'
  82                    '&exchars=300&redirects&titles=')
  83     snippet_url += query
  84     snippet = json.loads(web.get(snippet_url))
  85     snippet = snippet['query']['pages']
  86 
  87     # For some reason, the API gives the page *number* as the key, so we just
  88     # grab the first page number in the results.
  89     snippet = snippet[list(snippet.keys())[0]]
  90 
  91     return snippet['extract']
  92 
  93 
  94 @rule('.*/([a-z]+\.wikipedia.org)/wiki/([^ ]+).*')
  95 def mw_info(bot, trigger, found_match=None):
  96     """
  97     Retrives a snippet of the specified length from the given page on the given
  98     server.
  99     """
  100     match = found_match or trigger
  101     say_snippet(bot, match.group(1), unquote(match.group(2)), show_url=False)
  102 
  103 
  104 @commands('w', 'wiki', 'wik')
  105 @example('.w San Francisco')
  106 def wikipedia(bot, trigger):
  107     lang = bot.config.wikipedia.default_lang
  108 
  109     #change lang if channel has custom language set
  110     if (trigger.sender and not trigger.sender.is_nick() and
  111             bot.config.wikipedia.lang_per_channel):
  112         customlang = re.search('(' + trigger.sender + '):(\w+)',
  113                                bot.config.wikipedia.lang_per_channel)
  114         if customlang is not None:
  115             lang = customlang.group(2)
  116 
  117     if trigger.group(2) is None:
  118         bot.reply("What do you want me to look up?")
  119         return NOLIMIT
  120 
  121     query = trigger.group(2)
  122     args = re.search(r'^-([a-z]{2,12})\s(.*)', query)
  123     if args is not None:
  124         lang = args.group(1)
  125         query = args.group(2)
  126 
  127     if not query:
  128         bot.reply('What do you want me to look up?')
  129         return NOLIMIT
  130     server = lang + '.wikipedia.org'
  131     query = mw_search(server, query, 1)
  132     if not query:
  133         bot.reply("I can't find any results for that.")
  134         return NOLIMIT
  135     else:
  136         query = query[0]
  137     say_snippet(bot, server, query)
  138
  ```
  --- 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
  @@ -15,7 +15,8 @@
   
   import sys
   if sys.version_info.major < 3:
  -    from urlparse import unquote
  +    from urlparse import unquote as _unquote
  +    unquote = lambda s: _unquote(s.encode('utf-8')).decode('utf-8')
   else:
       from urllib.parse import unquote
  
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@@ -15,7 +15,8 @@\n \n import sys\n if sys.version_info.major < 3:\n-    from urlparse import unquote\n+    from urlparse import unquote as _unquote\n+    unquote = lambda s: _unquote(s.encode('utf-8')).decode('utf-8')\n else:\n     from urllib.parse import unquote\n", "issue": "Problem in (at least) Wikipedia module: possibly Unicode related\nHi,\nobserve the following use case:\n    https://en.wikipedia.org/wiki/Hir%C5%8D_Onoda\n    @willie_5.4.1 KeyError: u'extract' (file \"/usr/local/lib/python2.7/dist-packages/willie-5.4.1-py2.7.egg/willie/modules/wikipedia.py\", line 89, in mw_snippet)\n\n", "before_files": [{"content": "# coding=utf-8\n\"\"\"\nwikipedia.py - Sopel Wikipedia Module\nCopyright 2013 Edward Powell - embolalia.net\nLicensed under the Eiffel Forum License 2.\n\nhttp://sopel.chat\n\"\"\"\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\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/([^ ]+).*')\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\"\"\"\nwikipedia.py - Sopel Wikipedia Module\nCopyright 2013 Edward Powell - embolalia.net\nLicensed under the Eiffel Forum License 2.\n\nhttp://sopel.chat\n\"\"\"\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/([^ ]+).*')\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"}]}