We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
announce error on python3
<!-- Before reporting a bug, please search both open *and closed* issues to
see if it has already been reported. If you can, try to reproduce the problem
on an unmodified copy of the `master` branch first, as sometimes bugs are found
and fixed without a report. If the problem is unreported and persists in
`master`, please help us fix it quickly by filling out as much of this
information as you can. Thanks! -->

### Description
.announce results in an error on python 3

### Reproduction steps
1. Setup a instance on python3 (specifically I got the error on v3.7)
2. Try to use .announce
3. there will be a error

### Expected behavior
Works without errors.

### Logs
```
If applicable, add logs to help us figure out what's happening. Raw logs are
super helpful! Logs are usually found in ~/.sopel/logs, depending on your
configuration.
```
Jul 22 17:04:51 bots1.mirahezebots.org sopel[31484]: [2021-07-22 17:04:51,684] sopel.bot            ERROR    - Unexpected error ('dict_keys' object is not subscriptable) from MacFan4000 at 2
Jul 22 17:04:51 bots1.mirahezebots.org sopel[31484]: Traceback (most recent call last):
Jul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:   File "/srv/sopelbots/prodvenv/lib/python3.7/site-packages/sopel/bot.py", line 757, in call_rule
Jul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:     rule.execute(sopel, trigger)
Jul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:   File "/srv/sopelbots/prodvenv/lib/python3.7/site-packages/sopel/plugins/rules.py", line 1057, in execute
Jul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:     exit_code = self._handler(bot, trigger)
Jul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:   File "/srv/sopelbots/prodvenv/lib/python3.7/site-packages/sopel/plugin.py", line 1071, in guarded
Jul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:     return function(bot, trigger, *args, **kwargs)
Jul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:   File "/srv/sopelbots/prodvenv/lib/python3.7/site-packages/sopel/modules/announce.py", line 44, in announce
Jul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:     for cgroup in channels:
Jul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:   File "/srv/sopelbots/prodvenv/lib/python3.7/site-packages/sopel/modules/announce.py", line 24, in _chunks
Jul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:     yield items[delim:delim + size]
Jul 22 17:04:51 bots1.mirahezebots.org sopel[31484]: TypeError: 'dict_keys' object is not subscriptable
### Environment
- Sopel `.version`: [e.g. 7.0.0 or d416e19] 7.1.2
- Sopel installed via: [apt, pip, `setup.py install`, source, ?] pip
- Python version: [e.g. 3.6.9] 3.7
- Operating system: [e.g. Debian 10] Debian Buster
- IRCd `/version`: [e.g. InspIRCd 3.0.1] Libera Chat
- Relevant plugins: [adminchannel, weather, custom\_thing.py, ?] announce

### Notes
I believe https://github.com/sopel-irc/sopel/commit/b7b6b46a84e29e26a6a6b921debf57735661a4c0#diff-a9aa50736c17c299dac1ad9cb5ea1b835fb638c91bbd8c547990ffd9d67daa74 broke it due to .keys() not working the same way on python3 as it does on python2.


--- 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/announce.py`
Content:
```
1 """
2 announce.py - Sopel Announcement Plugin
3 Sends announcements to all channels the bot has joined.
4 Copyright © 2013, Elad Alfassa, <elad@fedoraproject.org>
5 Licensed under the Eiffel Forum License 2.
6 
7 https://sopel.chat
8 """
9 from __future__ import generator_stop
10 
11 from sopel import plugin
12 
13 
14 def _chunks(items, size):
15     """Break a list of items into groups.
16 
17     :param items: the collection of items to chunk
18     :type items: :term:`iterable`
19     :param int size: the size of each chunk
20     """
21     # from https://stackoverflow.com/a/312464/5991 with modified names for readability
22     for delim in range(0, len(items), size):
23         yield items[delim:delim + size]
24 
25 
26 @plugin.command('announce')
27 @plugin.example('.announce Some important message here')
28 @plugin.require_admin('Sorry, I can\'t let you do that', reply=True)
29 @plugin.output_prefix('[ANNOUNCEMENT] ')
30 def announce(bot, trigger):
31     """Send an announcement to all channels the bot is in."""
32     if trigger.group(2) is None:
33         bot.reply('Announce what? I need a message to say.')
34         return
35 
36     size = 1
37     try:
38         size = bot.isupport.TARGMAX.get('PRIVMSG', size)
39     except AttributeError:
40         pass
41 
42     channels = _chunks(bot.channels.keys(), size)
43     for cgroup in channels:
44         bot.say(trigger.group(2), ','.join(cgroup))
45 
46     bot.reply('Announce complete.')
47
```
--- 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()

```

diff --git a/sopel/modules/announce.py b/sopel/modules/announce.py
--- a/sopel/modules/announce.py
+++ b/sopel/modules/announce.py
@@ -8,6 +8,8 @@
 """
 from __future__ import generator_stop
 
+import itertools
+
 from sopel import plugin
 
 
@@ -17,10 +19,18 @@
     :param items: the collection of items to chunk
     :type items: :term:`iterable`
     :param int size: the size of each chunk
+    :return: a :term:`generator` of chunks
+    :rtype: :term:`generator` of :class:`tuple`
     """
-    # from https://stackoverflow.com/a/312464/5991 with modified names for readability
-    for delim in range(0, len(items), size):
-        yield items[delim:delim + size]
+    # This approach is safer than slicing with non-subscriptable types,
+    # for example `dict_keys` objects
+    iterator = iter(items)
+    # TODO: Simplify to assignment expression (`while cond := expr`)
+    # when dropping Python 3.7
+    chunk = tuple(itertools.islice(iterator, size))
+    while chunk:
+        yield chunk
+        chunk = tuple(itertools.islice(iterator, size))
 
 
 @plugin.command('announce')

{"golden_diff": "diff --git a/sopel/modules/announce.py b/sopel/modules/announce.py\n--- a/sopel/modules/announce.py\n+++ b/sopel/modules/announce.py\n@@ -8,6 +8,8 @@\n \"\"\"\n from __future__ import generator_stop\n \n+import itertools\n+\n from sopel import plugin\n \n \n@@ -17,10 +19,18 @@\n     :param items: the collection of items to chunk\n     :type items: :term:`iterable`\n     :param int size: the size of each chunk\n+    :return: a :term:`generator` of chunks\n+    :rtype: :term:`generator` of :class:`tuple`\n     \"\"\"\n-    # from https://stackoverflow.com/a/312464/5991 with modified names for readability\n-    for delim in range(0, len(items), size):\n-        yield items[delim:delim + size]\n+    # This approach is safer than slicing with non-subscriptable types,\n+    # for example `dict_keys` objects\n+    iterator = iter(items)\n+    # TODO: Simplify to assignment expression (`while cond := expr`)\n+    # when dropping Python 3.7\n+    chunk = tuple(itertools.islice(iterator, size))\n+    while chunk:\n+        yield chunk\n+        chunk = tuple(itertools.islice(iterator, size))\n \n \n @plugin.command('announce')\n", "issue": "announce error on python3\n<!-- Before reporting a bug, please search both open *and closed* issues to\r\nsee if it has already been reported. If you can, try to reproduce the problem\r\non an unmodified copy of the `master` branch first, as sometimes bugs are found\r\nand fixed without a report. If the problem is unreported and persists in\r\n`master`, please help us fix it quickly by filling out as much of this\r\ninformation as you can. Thanks! -->\r\n\r\n### Description\r\n.announce results in an error on python 3\r\n\r\n### Reproduction steps\r\n1. Setup a instance on python3 (specifically I got the error on v3.7)\r\n2. Try to use .announce\r\n3. there will be a error\r\n\r\n### Expected behavior\r\nWorks without errors.\r\n\r\n### Logs\r\n```\r\nIf applicable, add logs to help us figure out what's happening. Raw logs are\r\nsuper helpful! Logs are usually found in ~/.sopel/logs, depending on your\r\nconfiguration.\r\n```\r\nJul 22 17:04:51 bots1.mirahezebots.org sopel[31484]: [2021-07-22 17:04:51,684] sopel.bot            ERROR    - Unexpected error ('dict_keys' object is not subscriptable) from MacFan4000 at 2\r\nJul 22 17:04:51 bots1.mirahezebots.org sopel[31484]: Traceback (most recent call last):\r\nJul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:   File \"/srv/sopelbots/prodvenv/lib/python3.7/site-packages/sopel/bot.py\", line 757, in call_rule\r\nJul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:     rule.execute(sopel, trigger)\r\nJul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:   File \"/srv/sopelbots/prodvenv/lib/python3.7/site-packages/sopel/plugins/rules.py\", line 1057, in execute\r\nJul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:     exit_code = self._handler(bot, trigger)\r\nJul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:   File \"/srv/sopelbots/prodvenv/lib/python3.7/site-packages/sopel/plugin.py\", line 1071, in guarded\r\nJul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:     return function(bot, trigger, *args, **kwargs)\r\nJul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:   File \"/srv/sopelbots/prodvenv/lib/python3.7/site-packages/sopel/modules/announce.py\", line 44, in announce\r\nJul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:     for cgroup in channels:\r\nJul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:   File \"/srv/sopelbots/prodvenv/lib/python3.7/site-packages/sopel/modules/announce.py\", line 24, in _chunks\r\nJul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:     yield items[delim:delim + size]\r\nJul 22 17:04:51 bots1.mirahezebots.org sopel[31484]: TypeError: 'dict_keys' object is not subscriptable\r\n### Environment\r\n- Sopel `.version`: [e.g. 7.0.0 or d416e19] 7.1.2\r\n- Sopel installed via: [apt, pip, `setup.py install`, source, ?] pip\r\n- Python version: [e.g. 3.6.9] 3.7\r\n- Operating system: [e.g. Debian 10] Debian Buster\r\n- IRCd `/version`: [e.g. InspIRCd 3.0.1] Libera Chat\r\n- Relevant plugins: [adminchannel, weather, custom\\_thing.py, ?] announce\r\n\r\n### Notes\r\nI believe https://github.com/sopel-irc/sopel/commit/b7b6b46a84e29e26a6a6b921debf57735661a4c0#diff-a9aa50736c17c299dac1ad9cb5ea1b835fb638c91bbd8c547990ffd9d67daa74 broke it due to .keys() not working the same way on python3 as it does on python2.\r\n\n", "before_files": [{"content": "\"\"\"\nannounce.py - Sopel Announcement Plugin\nSends announcements to all channels the bot has joined.\nCopyright \u00a9 2013, Elad Alfassa, <elad@fedoraproject.org>\nLicensed under the Eiffel Forum License 2.\n\nhttps://sopel.chat\n\"\"\"\nfrom __future__ import generator_stop\n\nfrom sopel import plugin\n\n\ndef _chunks(items, size):\n    \"\"\"Break a list of items into groups.\n\n    :param items: the collection of items to chunk\n    :type items: :term:`iterable`\n    :param int size: the size of each chunk\n    \"\"\"\n    # from https://stackoverflow.com/a/312464/5991 with modified names for readability\n    for delim in range(0, len(items), size):\n        yield items[delim:delim + size]\n\n\n@plugin.command('announce')\n@plugin.example('.announce Some important message here')\n@plugin.require_admin('Sorry, I can\\'t let you do that', reply=True)\n@plugin.output_prefix('[ANNOUNCEMENT] ')\ndef announce(bot, trigger):\n    \"\"\"Send an announcement to all channels the bot is in.\"\"\"\n    if trigger.group(2) is None:\n        bot.reply('Announce what? I need a message to say.')\n        return\n\n    size = 1\n    try:\n        size = bot.isupport.TARGMAX.get('PRIVMSG', size)\n    except AttributeError:\n        pass\n\n    channels = _chunks(bot.channels.keys(), size)\n    for cgroup in channels:\n        bot.say(trigger.group(2), ','.join(cgroup))\n\n    bot.reply('Announce complete.')\n", "path": "sopel/modules/announce.py"}], "after_files": [{"content": "\"\"\"\nannounce.py - Sopel Announcement Plugin\nSends announcements to all channels the bot has joined.\nCopyright \u00a9 2013, Elad Alfassa, <elad@fedoraproject.org>\nLicensed under the Eiffel Forum License 2.\n\nhttps://sopel.chat\n\"\"\"\nfrom __future__ import generator_stop\n\nimport itertools\n\nfrom sopel import plugin\n\n\ndef _chunks(items, size):\n    \"\"\"Break a list of items into groups.\n\n    :param items: the collection of items to chunk\n    :type items: :term:`iterable`\n    :param int size: the size of each chunk\n    :return: a :term:`generator` of chunks\n    :rtype: :term:`generator` of :class:`tuple`\n    \"\"\"\n    # This approach is safer than slicing with non-subscriptable types,\n    # for example `dict_keys` objects\n    iterator = iter(items)\n    # TODO: Simplify to assignment expression (`while cond := expr`)\n    # when dropping Python 3.7\n    chunk = tuple(itertools.islice(iterator, size))\n    while chunk:\n        yield chunk\n        chunk = tuple(itertools.islice(iterator, size))\n\n\n@plugin.command('announce')\n@plugin.example('.announce Some important message here')\n@plugin.require_admin('Sorry, I can\\'t let you do that', reply=True)\n@plugin.output_prefix('[ANNOUNCEMENT] ')\ndef announce(bot, trigger):\n    \"\"\"Send an announcement to all channels the bot is in.\"\"\"\n    if trigger.group(2) is None:\n        bot.reply('Announce what? I need a message to say.')\n        return\n\n    size = 1\n    try:\n        size = bot.isupport.TARGMAX.get('PRIVMSG', size)\n    except AttributeError:\n        pass\n\n    channels = _chunks(bot.channels.keys(), size)\n    for cgroup in channels:\n        bot.say(trigger.group(2), ','.join(cgroup))\n\n    bot.reply('Announce complete.')\n", "path": "sopel/modules/announce.py"}]}