We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
admin: admins can usurp owners
I believe  `admin` and `owner` are meant to be two separate permission levels for the bot. There are `@require_admin` and `@require_owner` for commands, implying that there are certain functions for an `owner` that should not be available to `admin`s. 

That being said, if the `admin` plugin is enabled, an `admin` can simply:
<details>
<summary>Not a spoiler :sweat_smile: </summary>

<pre>.set core.owner <i>I-Am-gRoot</i></pre> 
</details>
bypassing the <code>@require_owner</code>, and potentially locking them out of their own bot.

--- 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/admin.py`
Content:
```
1 # coding=utf-8
2 """
3 admin.py - Sopel Admin Module
4 Copyright 2010-2011, Sean B. Palmer (inamidst.com) and Michael Yanovich
5 (yanovich.net)
6 Copyright © 2012, Elad Alfassa, <elad@fedoraproject.org>
7 Copyright 2013, Ari Koivula <ari@koivu.la>
8 
9 Licensed under the Eiffel Forum License 2.
10 
11 https://sopel.chat
12 """
13 from __future__ import unicode_literals, absolute_import, print_function, division
14 
15 from sopel.config.types import (
16     StaticSection, ValidatedAttribute, FilenameAttribute
17 )
18 import sopel.module
19 
20 
21 class AdminSection(StaticSection):
22     hold_ground = ValidatedAttribute('hold_ground', bool, default=False)
23     """Auto re-join on kick"""
24     auto_accept_invite = ValidatedAttribute('auto_accept_invite', bool,
25                                             default=True)
26 
27 
28 def configure(config):
29     """
30     | name | example | purpose |
31     | ---- | ------- | ------- |
32     | hold\\_ground | False | Auto-rejoin the channel after being kicked. |
33     | auto\\_accept\\_invite | True | Auto-join channels when invited. |
34     """
35     config.define_section('admin', AdminSection)
36     config.admin.configure_setting('hold_ground',
37                                    "Automatically re-join after being kicked?")
38     config.admin.configure_setting('auto_accept_invite',
39                                    'Automatically join channels when invited?')
40 
41 
42 def setup(bot):
43     bot.config.define_section('admin', AdminSection)
44 
45 
46 @sopel.module.require_privmsg
47 @sopel.module.require_admin
48 @sopel.module.commands('join')
49 @sopel.module.priority('low')
50 @sopel.module.example('.join #example or .join #example key')
51 def join(bot, trigger):
52     """Join the specified channel. This is an admin-only command."""
53     channel, key = trigger.group(3), trigger.group(4)
54     if not channel:
55         return
56     elif not key:
57         bot.join(channel)
58     else:
59         bot.join(channel, key)
60 
61 
62 @sopel.module.require_privmsg
63 @sopel.module.require_admin
64 @sopel.module.commands('part')
65 @sopel.module.priority('low')
66 @sopel.module.example('.part #example')
67 def part(bot, trigger):
68     """Part the specified channel. This is an admin-only command."""
69     channel, _sep, part_msg = trigger.group(2).partition(' ')
70     if part_msg:
71         bot.part(channel, part_msg)
72     else:
73         bot.part(channel)
74 
75 
76 @sopel.module.require_privmsg
77 @sopel.module.require_owner
78 @sopel.module.commands('quit')
79 @sopel.module.priority('low')
80 def quit(bot, trigger):
81     """Quit from the server. This is an owner-only command."""
82     quit_message = trigger.group(2)
83     if not quit_message:
84         quit_message = 'Quitting on command from %s' % trigger.nick
85 
86     bot.quit(quit_message)
87 
88 
89 @sopel.module.require_privmsg
90 @sopel.module.require_admin
91 @sopel.module.commands('msg')
92 @sopel.module.priority('low')
93 @sopel.module.example('.msg #YourPants Does anyone else smell neurotoxin?')
94 def msg(bot, trigger):
95     """
96     Send a message to a given channel or nick. Can only be done in privmsg by
97     an admin.
98     """
99     if trigger.group(2) is None:
100         return
101 
102     channel, _sep, message = trigger.group(2).partition(' ')
103     message = message.strip()
104     if not channel or not message:
105         return
106 
107     bot.msg(channel, message)
108 
109 
110 @sopel.module.require_privmsg
111 @sopel.module.require_admin
112 @sopel.module.commands('me')
113 @sopel.module.priority('low')
114 def me(bot, trigger):
115     """
116     Send an ACTION (/me) to a given channel or nick. Can only be done in
117     privmsg by an admin.
118     """
119     if trigger.group(2) is None:
120         return
121 
122     channel, _sep, action = trigger.group(2).partition(' ')
123     action = action.strip()
124     if not channel or not action:
125         return
126 
127     msg = '\x01ACTION %s\x01' % action
128     bot.msg(channel, msg)
129 
130 
131 @sopel.module.event('INVITE')
132 @sopel.module.rule('.*')
133 @sopel.module.priority('low')
134 def invite_join(bot, trigger):
135     """
136     Join a channel Sopel is invited to, if the inviter is an admin.
137     """
138     if trigger.admin or bot.config.admin.auto_accept_invite:
139         bot.join(trigger.args[1])
140         return
141 
142 
143 @sopel.module.event('KICK')
144 @sopel.module.rule(r'.*')
145 @sopel.module.priority('low')
146 def hold_ground(bot, trigger):
147     """
148     This function monitors all kicks across all channels Sopel is in. If it
149     detects that it is the one kicked it'll automatically join that channel.
150 
151     WARNING: This may not be needed and could cause problems if Sopel becomes
152     annoying. Please use this with caution.
153     """
154     if bot.config.admin.hold_ground:
155         channel = trigger.sender
156         if trigger.args[1] == bot.nick:
157             bot.join(channel)
158 
159 
160 @sopel.module.require_privmsg
161 @sopel.module.require_admin
162 @sopel.module.commands('mode')
163 @sopel.module.priority('low')
164 def mode(bot, trigger):
165     """Set a user mode on Sopel. Can only be done in privmsg by an admin."""
166     mode = trigger.group(3)
167     bot.write(('MODE', bot.nick + ' ' + mode))
168 
169 
170 @sopel.module.require_privmsg("This command only works as a private message.")
171 @sopel.module.require_admin("This command requires admin privileges.")
172 @sopel.module.commands('set')
173 @sopel.module.example('.set core.owner Me')
174 def set_config(bot, trigger):
175     """See and modify values of Sopel's config object.
176 
177     Trigger args:
178         arg1 - section and option, in the form "section.option"
179         arg2 - value
180 
181     If there is no section, section will default to "core".
182     If value is None, the option will be deleted.
183     """
184     # Get section and option from first argument.
185     match = trigger.group(3)
186     if match is None:
187         bot.reply("Usage: .set section.option value")
188         return
189     arg1 = match.split('.')
190     if len(arg1) == 1:
191         section_name, option = "core", arg1[0]
192     elif len(arg1) == 2:
193         section_name, option = arg1
194     else:
195         bot.reply("Usage: .set section.option value")
196         return
197     section = getattr(bot.config, section_name)
198     static_sec = isinstance(section, StaticSection)
199 
200     if static_sec and not hasattr(section, option):
201         bot.say('[{}] section has no option {}.'.format(section_name, option))
202         return
203 
204     delim = trigger.group(2).find(' ')
205     # Skip preceding whitespaces, if any.
206     while delim > 0 and delim < len(trigger.group(2)) and trigger.group(2)[delim] == ' ':
207         delim = delim + 1
208 
209     # Display current value if no value is given.
210     if delim == -1 or delim == len(trigger.group(2)):
211         if not static_sec and bot.config.parser.has_option(section, option):
212             bot.reply("Option %s.%s does not exist." % (section_name, option))
213             return
214         # Except if the option looks like a password. Censor those to stop them
215         # from being put on log files.
216         if option.endswith("password") or option.endswith("pass"):
217             value = "(password censored)"
218         else:
219             value = getattr(section, option)
220         bot.reply("%s.%s = %s" % (section_name, option, value))
221         return
222 
223     # Otherwise, set the value to one given as argument 2.
224     value = trigger.group(2)[delim:]
225     if static_sec:
226         descriptor = getattr(section.__class__, option)
227         try:
228             if isinstance(descriptor, FilenameAttribute):
229                 value = descriptor.parse(bot.config, descriptor, value)
230             else:
231                 value = descriptor.parse(value)
232         except ValueError as exc:
233             bot.say("Can't set attribute: " + str(exc))
234             return
235     setattr(section, option, value)
236 
237 
238 @sopel.module.require_privmsg
239 @sopel.module.require_admin
240 @sopel.module.commands('save')
241 @sopel.module.example('.save')
242 def save_config(bot, trigger):
243     """Save state of Sopel's config object to the configuration file."""
244     bot.config.save()
245
```
--- 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/admin.py b/sopel/modules/admin.py
--- a/sopel/modules/admin.py
+++ b/sopel/modules/admin.py
@@ -220,6 +220,13 @@
         bot.reply("%s.%s = %s" % (section_name, option, value))
         return
 
+    # 'core.owner' cannot be set by the bot. Any changes to this setting must be
+    # made manually in the config file.
+    if section_name == 'core' and option == 'owner':
+        bot.say('\'core.owner\' cannot be set by the bot. '
+                'This setting must be changed manually in the configuration file.')
+        return
+
     # Otherwise, set the value to one given as argument 2.
     value = trigger.group(2)[delim:]
     if static_sec:

{"golden_diff": "diff --git a/sopel/modules/admin.py b/sopel/modules/admin.py\n--- a/sopel/modules/admin.py\n+++ b/sopel/modules/admin.py\n@@ -220,6 +220,13 @@\n         bot.reply(\"%s.%s = %s\" % (section_name, option, value))\n         return\n \n+    # 'core.owner' cannot be set by the bot. Any changes to this setting must be\n+    # made manually in the config file.\n+    if section_name == 'core' and option == 'owner':\n+        bot.say('\\'core.owner\\' cannot be set by the bot. '\n+                'This setting must be changed manually in the configuration file.')\n+        return\n+\n     # Otherwise, set the value to one given as argument 2.\n     value = trigger.group(2)[delim:]\n     if static_sec:\n", "issue": "admin: admins can usurp owners\nI believe  `admin` and `owner` are meant to be two separate permission levels for the bot. There are `@require_admin` and `@require_owner` for commands, implying that there are certain functions for an `owner` that should not be available to `admin`s. \r\n\r\nThat being said, if the `admin` plugin is enabled, an `admin` can simply:\r\n<details>\r\n<summary>Not a spoiler :sweat_smile: </summary>\r\n\r\n<pre>.set core.owner <i>I-Am-gRoot</i></pre> \r\n</details>\r\nbypassing the <code>@require_owner</code>, and potentially locking them out of their own bot.\n", "before_files": [{"content": "# coding=utf-8\n\"\"\"\nadmin.py - Sopel Admin Module\nCopyright 2010-2011, Sean B. Palmer (inamidst.com) and Michael Yanovich\n(yanovich.net)\nCopyright \u00a9 2012, Elad Alfassa, <elad@fedoraproject.org>\nCopyright 2013, Ari Koivula <ari@koivu.la>\n\nLicensed under the Eiffel Forum License 2.\n\nhttps://sopel.chat\n\"\"\"\nfrom __future__ import unicode_literals, absolute_import, print_function, division\n\nfrom sopel.config.types import (\n    StaticSection, ValidatedAttribute, FilenameAttribute\n)\nimport sopel.module\n\n\nclass AdminSection(StaticSection):\n    hold_ground = ValidatedAttribute('hold_ground', bool, default=False)\n    \"\"\"Auto re-join on kick\"\"\"\n    auto_accept_invite = ValidatedAttribute('auto_accept_invite', bool,\n                                            default=True)\n\n\ndef configure(config):\n    \"\"\"\n    | name | example | purpose |\n    | ---- | ------- | ------- |\n    | hold\\\\_ground | False | Auto-rejoin the channel after being kicked. |\n    | auto\\\\_accept\\\\_invite | True | Auto-join channels when invited. |\n    \"\"\"\n    config.define_section('admin', AdminSection)\n    config.admin.configure_setting('hold_ground',\n                                   \"Automatically re-join after being kicked?\")\n    config.admin.configure_setting('auto_accept_invite',\n                                   'Automatically join channels when invited?')\n\n\ndef setup(bot):\n    bot.config.define_section('admin', AdminSection)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_admin\n@sopel.module.commands('join')\n@sopel.module.priority('low')\n@sopel.module.example('.join #example or .join #example key')\ndef join(bot, trigger):\n    \"\"\"Join the specified channel. This is an admin-only command.\"\"\"\n    channel, key = trigger.group(3), trigger.group(4)\n    if not channel:\n        return\n    elif not key:\n        bot.join(channel)\n    else:\n        bot.join(channel, key)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_admin\n@sopel.module.commands('part')\n@sopel.module.priority('low')\n@sopel.module.example('.part #example')\ndef part(bot, trigger):\n    \"\"\"Part the specified channel. This is an admin-only command.\"\"\"\n    channel, _sep, part_msg = trigger.group(2).partition(' ')\n    if part_msg:\n        bot.part(channel, part_msg)\n    else:\n        bot.part(channel)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_owner\n@sopel.module.commands('quit')\n@sopel.module.priority('low')\ndef quit(bot, trigger):\n    \"\"\"Quit from the server. This is an owner-only command.\"\"\"\n    quit_message = trigger.group(2)\n    if not quit_message:\n        quit_message = 'Quitting on command from %s' % trigger.nick\n\n    bot.quit(quit_message)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_admin\n@sopel.module.commands('msg')\n@sopel.module.priority('low')\n@sopel.module.example('.msg #YourPants Does anyone else smell neurotoxin?')\ndef msg(bot, trigger):\n    \"\"\"\n    Send a message to a given channel or nick. Can only be done in privmsg by\n    an admin.\n    \"\"\"\n    if trigger.group(2) is None:\n        return\n\n    channel, _sep, message = trigger.group(2).partition(' ')\n    message = message.strip()\n    if not channel or not message:\n        return\n\n    bot.msg(channel, message)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_admin\n@sopel.module.commands('me')\n@sopel.module.priority('low')\ndef me(bot, trigger):\n    \"\"\"\n    Send an ACTION (/me) to a given channel or nick. Can only be done in\n    privmsg by an admin.\n    \"\"\"\n    if trigger.group(2) is None:\n        return\n\n    channel, _sep, action = trigger.group(2).partition(' ')\n    action = action.strip()\n    if not channel or not action:\n        return\n\n    msg = '\\x01ACTION %s\\x01' % action\n    bot.msg(channel, msg)\n\n\n@sopel.module.event('INVITE')\n@sopel.module.rule('.*')\n@sopel.module.priority('low')\ndef invite_join(bot, trigger):\n    \"\"\"\n    Join a channel Sopel is invited to, if the inviter is an admin.\n    \"\"\"\n    if trigger.admin or bot.config.admin.auto_accept_invite:\n        bot.join(trigger.args[1])\n        return\n\n\n@sopel.module.event('KICK')\n@sopel.module.rule(r'.*')\n@sopel.module.priority('low')\ndef hold_ground(bot, trigger):\n    \"\"\"\n    This function monitors all kicks across all channels Sopel is in. If it\n    detects that it is the one kicked it'll automatically join that channel.\n\n    WARNING: This may not be needed and could cause problems if Sopel becomes\n    annoying. Please use this with caution.\n    \"\"\"\n    if bot.config.admin.hold_ground:\n        channel = trigger.sender\n        if trigger.args[1] == bot.nick:\n            bot.join(channel)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_admin\n@sopel.module.commands('mode')\n@sopel.module.priority('low')\ndef mode(bot, trigger):\n    \"\"\"Set a user mode on Sopel. Can only be done in privmsg by an admin.\"\"\"\n    mode = trigger.group(3)\n    bot.write(('MODE', bot.nick + ' ' + mode))\n\n\n@sopel.module.require_privmsg(\"This command only works as a private message.\")\n@sopel.module.require_admin(\"This command requires admin privileges.\")\n@sopel.module.commands('set')\n@sopel.module.example('.set core.owner Me')\ndef set_config(bot, trigger):\n    \"\"\"See and modify values of Sopel's config object.\n\n    Trigger args:\n        arg1 - section and option, in the form \"section.option\"\n        arg2 - value\n\n    If there is no section, section will default to \"core\".\n    If value is None, the option will be deleted.\n    \"\"\"\n    # Get section and option from first argument.\n    match = trigger.group(3)\n    if match is None:\n        bot.reply(\"Usage: .set section.option value\")\n        return\n    arg1 = match.split('.')\n    if len(arg1) == 1:\n        section_name, option = \"core\", arg1[0]\n    elif len(arg1) == 2:\n        section_name, option = arg1\n    else:\n        bot.reply(\"Usage: .set section.option value\")\n        return\n    section = getattr(bot.config, section_name)\n    static_sec = isinstance(section, StaticSection)\n\n    if static_sec and not hasattr(section, option):\n        bot.say('[{}] section has no option {}.'.format(section_name, option))\n        return\n\n    delim = trigger.group(2).find(' ')\n    # Skip preceding whitespaces, if any.\n    while delim > 0 and delim < len(trigger.group(2)) and trigger.group(2)[delim] == ' ':\n        delim = delim + 1\n\n    # Display current value if no value is given.\n    if delim == -1 or delim == len(trigger.group(2)):\n        if not static_sec and bot.config.parser.has_option(section, option):\n            bot.reply(\"Option %s.%s does not exist.\" % (section_name, option))\n            return\n        # Except if the option looks like a password. Censor those to stop them\n        # from being put on log files.\n        if option.endswith(\"password\") or option.endswith(\"pass\"):\n            value = \"(password censored)\"\n        else:\n            value = getattr(section, option)\n        bot.reply(\"%s.%s = %s\" % (section_name, option, value))\n        return\n\n    # Otherwise, set the value to one given as argument 2.\n    value = trigger.group(2)[delim:]\n    if static_sec:\n        descriptor = getattr(section.__class__, option)\n        try:\n            if isinstance(descriptor, FilenameAttribute):\n                value = descriptor.parse(bot.config, descriptor, value)\n            else:\n                value = descriptor.parse(value)\n        except ValueError as exc:\n            bot.say(\"Can't set attribute: \" + str(exc))\n            return\n    setattr(section, option, value)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_admin\n@sopel.module.commands('save')\n@sopel.module.example('.save')\ndef save_config(bot, trigger):\n    \"\"\"Save state of Sopel's config object to the configuration file.\"\"\"\n    bot.config.save()\n", "path": "sopel/modules/admin.py"}], "after_files": [{"content": "# coding=utf-8\n\"\"\"\nadmin.py - Sopel Admin Module\nCopyright 2010-2011, Sean B. Palmer (inamidst.com) and Michael Yanovich\n(yanovich.net)\nCopyright \u00a9 2012, Elad Alfassa, <elad@fedoraproject.org>\nCopyright 2013, Ari Koivula <ari@koivu.la>\n\nLicensed under the Eiffel Forum License 2.\n\nhttps://sopel.chat\n\"\"\"\nfrom __future__ import unicode_literals, absolute_import, print_function, division\n\nfrom sopel.config.types import (\n    StaticSection, ValidatedAttribute, FilenameAttribute\n)\nimport sopel.module\n\n\nclass AdminSection(StaticSection):\n    hold_ground = ValidatedAttribute('hold_ground', bool, default=False)\n    \"\"\"Auto re-join on kick\"\"\"\n    auto_accept_invite = ValidatedAttribute('auto_accept_invite', bool,\n                                            default=True)\n\n\ndef configure(config):\n    \"\"\"\n    | name | example | purpose |\n    | ---- | ------- | ------- |\n    | hold\\\\_ground | False | Auto-rejoin the channel after being kicked. |\n    | auto\\\\_accept\\\\_invite | True | Auto-join channels when invited. |\n    \"\"\"\n    config.define_section('admin', AdminSection)\n    config.admin.configure_setting('hold_ground',\n                                   \"Automatically re-join after being kicked?\")\n    config.admin.configure_setting('auto_accept_invite',\n                                   'Automatically join channels when invited?')\n\n\ndef setup(bot):\n    bot.config.define_section('admin', AdminSection)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_admin\n@sopel.module.commands('join')\n@sopel.module.priority('low')\n@sopel.module.example('.join #example or .join #example key')\ndef join(bot, trigger):\n    \"\"\"Join the specified channel. This is an admin-only command.\"\"\"\n    channel, key = trigger.group(3), trigger.group(4)\n    if not channel:\n        return\n    elif not key:\n        bot.join(channel)\n    else:\n        bot.join(channel, key)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_admin\n@sopel.module.commands('part')\n@sopel.module.priority('low')\n@sopel.module.example('.part #example')\ndef part(bot, trigger):\n    \"\"\"Part the specified channel. This is an admin-only command.\"\"\"\n    channel, _sep, part_msg = trigger.group(2).partition(' ')\n    if part_msg:\n        bot.part(channel, part_msg)\n    else:\n        bot.part(channel)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_owner\n@sopel.module.commands('quit')\n@sopel.module.priority('low')\ndef quit(bot, trigger):\n    \"\"\"Quit from the server. This is an owner-only command.\"\"\"\n    quit_message = trigger.group(2)\n    if not quit_message:\n        quit_message = 'Quitting on command from %s' % trigger.nick\n\n    bot.quit(quit_message)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_admin\n@sopel.module.commands('msg')\n@sopel.module.priority('low')\n@sopel.module.example('.msg #YourPants Does anyone else smell neurotoxin?')\ndef msg(bot, trigger):\n    \"\"\"\n    Send a message to a given channel or nick. Can only be done in privmsg by\n    an admin.\n    \"\"\"\n    if trigger.group(2) is None:\n        return\n\n    channel, _sep, message = trigger.group(2).partition(' ')\n    message = message.strip()\n    if not channel or not message:\n        return\n\n    bot.msg(channel, message)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_admin\n@sopel.module.commands('me')\n@sopel.module.priority('low')\ndef me(bot, trigger):\n    \"\"\"\n    Send an ACTION (/me) to a given channel or nick. Can only be done in\n    privmsg by an admin.\n    \"\"\"\n    if trigger.group(2) is None:\n        return\n\n    channel, _sep, action = trigger.group(2).partition(' ')\n    action = action.strip()\n    if not channel or not action:\n        return\n\n    msg = '\\x01ACTION %s\\x01' % action\n    bot.msg(channel, msg)\n\n\n@sopel.module.event('INVITE')\n@sopel.module.rule('.*')\n@sopel.module.priority('low')\ndef invite_join(bot, trigger):\n    \"\"\"\n    Join a channel Sopel is invited to, if the inviter is an admin.\n    \"\"\"\n    if trigger.admin or bot.config.admin.auto_accept_invite:\n        bot.join(trigger.args[1])\n        return\n\n\n@sopel.module.event('KICK')\n@sopel.module.rule(r'.*')\n@sopel.module.priority('low')\ndef hold_ground(bot, trigger):\n    \"\"\"\n    This function monitors all kicks across all channels Sopel is in. If it\n    detects that it is the one kicked it'll automatically join that channel.\n\n    WARNING: This may not be needed and could cause problems if Sopel becomes\n    annoying. Please use this with caution.\n    \"\"\"\n    if bot.config.admin.hold_ground:\n        channel = trigger.sender\n        if trigger.args[1] == bot.nick:\n            bot.join(channel)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_admin\n@sopel.module.commands('mode')\n@sopel.module.priority('low')\ndef mode(bot, trigger):\n    \"\"\"Set a user mode on Sopel. Can only be done in privmsg by an admin.\"\"\"\n    mode = trigger.group(3)\n    bot.write(('MODE', bot.nick + ' ' + mode))\n\n\n@sopel.module.require_privmsg(\"This command only works as a private message.\")\n@sopel.module.require_admin(\"This command requires admin privileges.\")\n@sopel.module.commands('set')\n@sopel.module.example('.set core.owner Me')\ndef set_config(bot, trigger):\n    \"\"\"See and modify values of Sopel's config object.\n\n    Trigger args:\n        arg1 - section and option, in the form \"section.option\"\n        arg2 - value\n\n    If there is no section, section will default to \"core\".\n    If value is None, the option will be deleted.\n    \"\"\"\n    # Get section and option from first argument.\n    match = trigger.group(3)\n    if match is None:\n        bot.reply(\"Usage: .set section.option value\")\n        return\n    arg1 = match.split('.')\n    if len(arg1) == 1:\n        section_name, option = \"core\", arg1[0]\n    elif len(arg1) == 2:\n        section_name, option = arg1\n    else:\n        bot.reply(\"Usage: .set section.option value\")\n        return\n    section = getattr(bot.config, section_name)\n    static_sec = isinstance(section, StaticSection)\n\n    if static_sec and not hasattr(section, option):\n        bot.say('[{}] section has no option {}.'.format(section_name, option))\n        return\n\n    delim = trigger.group(2).find(' ')\n    # Skip preceding whitespaces, if any.\n    while delim > 0 and delim < len(trigger.group(2)) and trigger.group(2)[delim] == ' ':\n        delim = delim + 1\n\n    # Display current value if no value is given.\n    if delim == -1 or delim == len(trigger.group(2)):\n        if not static_sec and bot.config.parser.has_option(section, option):\n            bot.reply(\"Option %s.%s does not exist.\" % (section_name, option))\n            return\n        # Except if the option looks like a password. Censor those to stop them\n        # from being put on log files.\n        if option.endswith(\"password\") or option.endswith(\"pass\"):\n            value = \"(password censored)\"\n        else:\n            value = getattr(section, option)\n        bot.reply(\"%s.%s = %s\" % (section_name, option, value))\n        return\n\n    # 'core.owner' cannot be set by the bot. Any changes to this setting must be\n    # made manually in the config file.\n    if section_name == 'core' and option == 'owner':\n        bot.say('\\'core.owner\\' cannot be set by the bot. '\n                'This setting must be changed manually in the configuration file.')\n        return\n\n    # Otherwise, set the value to one given as argument 2.\n    value = trigger.group(2)[delim:]\n    if static_sec:\n        descriptor = getattr(section.__class__, option)\n        try:\n            if isinstance(descriptor, FilenameAttribute):\n                value = descriptor.parse(bot.config, descriptor, value)\n            else:\n                value = descriptor.parse(value)\n        except ValueError as exc:\n            bot.say(\"Can't set attribute: \" + str(exc))\n            return\n    setattr(section, option, value)\n\n\n@sopel.module.require_privmsg\n@sopel.module.require_admin\n@sopel.module.commands('save')\n@sopel.module.example('.save')\ndef save_config(bot, trigger):\n    \"\"\"Save state of Sopel's config object to the configuration file.\"\"\"\n    bot.config.save()\n", "path": "sopel/modules/admin.py"}]}