We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
adminchannel: Restore op/voice commands
@kwaaak made a very good point the other day in PM on IRC: `adminchannel`'s commands for managing ops and voices were removed (902e715) for seemingly no particular reason.

I haven't decided when to do it (for 6.x or wait for Sopel 7), but I do think those commands should come back. They'll need a bit of tweaking, but nothing major. Milestone 6.6.0 for now, I guess, to be punted if I can think of a reason it should be delayed.

--- 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/adminchannel.py`
Content:
```
1 # coding=utf-8
2 """
3 adminchannel.py - Sopel Channel Admin Module
4 Copyright 2010-2011, Michael Yanovich, Alek Rollyson, and Elsie Powell
5 Copyright © 2012, Elad Alfassa <elad@fedoraproject.org>
6 Licensed under the Eiffel Forum License 2.
7 
8 https://sopel.chat
9 """
10 from __future__ import unicode_literals, absolute_import, print_function, division
11 
12 import re
13 
14 from sopel import formatting
15 from sopel.module import (
16     commands, example, priority, OP, HALFOP, require_privilege, require_chanmsg
17 )
18 from sopel.tools import Identifier
19 
20 
21 def default_mask(trigger):
22     welcome = formatting.color('Welcome to:', formatting.colors.PURPLE)
23     chan = formatting.color(trigger.sender, formatting.colors.TEAL)
24     topic_ = formatting.bold('Topic:')
25     topic_ = formatting.color('| ' + topic_, formatting.colors.PURPLE)
26     arg = formatting.color('{}', formatting.colors.GREEN)
27     return '{} {} {} {}'.format(welcome, chan, topic_, arg)
28 
29 
30 @require_chanmsg
31 @require_privilege(OP, 'You are not a channel operator.')
32 @commands('kick')
33 @priority('high')
34 def kick(bot, trigger):
35     """Kick a user from the channel."""
36     if bot.channels[trigger.sender].privileges[bot.nick] < HALFOP:
37         return bot.reply("I'm not a channel operator!")
38     text = trigger.group().split()
39     argc = len(text)
40     if argc < 2:
41         return
42     opt = Identifier(text[1])
43     nick = opt
44     channel = trigger.sender
45     reasonidx = 2
46     if not opt.is_nick():
47         if argc < 3:
48             return
49         nick = text[2]
50         channel = opt
51         reasonidx = 3
52     reason = ' '.join(text[reasonidx:])
53     if nick != bot.config.core.nick:
54         bot.write(['KICK', channel, nick], reason)
55 
56 
57 def configureHostMask(mask):
58     if mask == '*!*@*':
59         return mask
60     if re.match('^[^.@!/]+$', mask) is not None:
61         return '%s!*@*' % mask
62     if re.match('^[^@!]+$', mask) is not None:
63         return '*!*@%s' % mask
64 
65     m = re.match('^([^!@]+)@$', mask)
66     if m is not None:
67         return '*!%s@*' % m.group(1)
68 
69     m = re.match('^([^!@]+)@([^@!]+)$', mask)
70     if m is not None:
71         return '*!%s@%s' % (m.group(1), m.group(2))
72 
73     m = re.match('^([^!@]+)!(^[!@]+)@?$', mask)
74     if m is not None:
75         return '%s!%s@*' % (m.group(1), m.group(2))
76     return ''
77 
78 
79 @require_chanmsg
80 @require_privilege(OP, 'You are not a channel operator.')
81 @commands('ban')
82 @priority('high')
83 def ban(bot, trigger):
84     """Ban a user from the channel
85 
86     The bot must be a channel operator for this command to work.
87     """
88     if bot.channels[trigger.sender].privileges[bot.nick] < HALFOP:
89         return bot.reply("I'm not a channel operator!")
90     text = trigger.group().split()
91     argc = len(text)
92     if argc < 2:
93         return
94     opt = Identifier(text[1])
95     banmask = opt
96     channel = trigger.sender
97     if not opt.is_nick():
98         if argc < 3:
99             return
100         channel = opt
101         banmask = text[2]
102     banmask = configureHostMask(banmask)
103     if banmask == '':
104         return
105     bot.write(['MODE', channel, '+b', banmask])
106 
107 
108 @require_chanmsg
109 @require_privilege(OP, 'You are not a channel operator.')
110 @commands('unban')
111 def unban(bot, trigger):
112     """Unban a user from the channel
113 
114     The bot must be a channel operator for this command to work.
115     """
116     if bot.channels[trigger.sender].privileges[bot.nick] < HALFOP:
117         return bot.reply("I'm not a channel operator!")
118     text = trigger.group().split()
119     argc = len(text)
120     if argc < 2:
121         return
122     opt = Identifier(text[1])
123     banmask = opt
124     channel = trigger.sender
125     if not opt.is_nick():
126         if argc < 3:
127             return
128         channel = opt
129         banmask = text[2]
130     banmask = configureHostMask(banmask)
131     if banmask == '':
132         return
133     bot.write(['MODE', channel, '-b', banmask])
134 
135 
136 @require_chanmsg
137 @require_privilege(OP, 'You are not a channel operator.')
138 @commands('quiet')
139 def quiet(bot, trigger):
140     """Quiet a user
141 
142     The bot must be a channel operator for this command to work.
143     """
144     if bot.channels[trigger.sender].privileges[bot.nick] < OP:
145         return bot.reply("I'm not a channel operator!")
146     text = trigger.group().split()
147     argc = len(text)
148     if argc < 2:
149         return
150     opt = Identifier(text[1])
151     quietmask = opt
152     channel = trigger.sender
153     if not opt.is_nick():
154         if argc < 3:
155             return
156         quietmask = text[2]
157         channel = opt
158     quietmask = configureHostMask(quietmask)
159     if quietmask == '':
160         return
161     bot.write(['MODE', channel, '+q', quietmask])
162 
163 
164 @require_chanmsg
165 @require_privilege(OP, 'You are not a channel operator.')
166 @commands('unquiet')
167 def unquiet(bot, trigger):
168     """Unquiet a user
169 
170     The bot must be a channel operator for this command to work.
171     """
172     if bot.channels[trigger.sender].privileges[bot.nick] < OP:
173         return bot.reply("I'm not a channel operator!")
174     text = trigger.group().split()
175     argc = len(text)
176     if argc < 2:
177         return
178     opt = Identifier(text[1])
179     quietmask = opt
180     channel = trigger.sender
181     if not opt.is_nick():
182         if argc < 3:
183             return
184         quietmask = text[2]
185         channel = opt
186     quietmask = configureHostMask(quietmask)
187     if quietmask == '':
188         return
189     bot.write(['MODE', channel, '-q', quietmask])
190 
191 
192 @require_chanmsg
193 @require_privilege(OP, 'You are not a channel operator.')
194 @commands('kickban', 'kb')
195 @example('.kickban [#chan] user1 user!*@* get out of here')
196 @priority('high')
197 def kickban(bot, trigger):
198     """Kick and ban a user from the channel
199 
200     The bot must be a channel operator for this command to work.
201     """
202     if bot.channels[trigger.sender].privileges[bot.nick] < HALFOP:
203         return bot.reply("I'm not a channel operator!")
204     text = trigger.group().split()
205     argc = len(text)
206     if argc < 4:
207         return
208     opt = Identifier(text[1])
209     nick = opt
210     mask = text[2]
211     channel = trigger.sender
212     reasonidx = 3
213     if not opt.is_nick():
214         if argc < 5:
215             return
216         channel = opt
217         nick = text[2]
218         mask = text[3]
219         reasonidx = 4
220     reason = ' '.join(text[reasonidx:])
221     mask = configureHostMask(mask)
222     if mask == '':
223         return
224     bot.write(['MODE', channel, '+b', mask])
225     bot.write(['KICK', channel, nick], reason)
226 
227 
228 @require_chanmsg
229 @require_privilege(OP, 'You are not a channel operator.')
230 @commands('topic')
231 def topic(bot, trigger):
232     """Change the channel topic
233 
234     The bot must be a channel operator for this command to work.
235     """
236     if bot.channels[trigger.sender].privileges[bot.nick] < HALFOP:
237         return bot.reply("I'm not a channel operator!")
238     if not trigger.group(2):
239         return
240     channel = trigger.sender.lower()
241 
242     narg = 1
243     mask = None
244     mask = bot.db.get_channel_value(channel, 'topic_mask')
245     mask = mask or default_mask(trigger)
246     mask = mask.replace('%s', '{}')
247     narg = len(re.findall('{}', mask))
248 
249     top = trigger.group(2)
250     args = []
251     if top:
252         args = top.split('~', narg)
253 
254     if len(args) != narg:
255         message = "Not enough arguments. You gave {}, it requires {}.".format(
256             len(args), narg)
257         return bot.say(message)
258     topic = mask.format(*args)
259 
260     bot.write(('TOPIC', channel + ' :' + topic))
261 
262 
263 @require_chanmsg
264 @require_privilege(OP, 'You are not a channel operator.')
265 @commands('tmask')
266 def set_mask(bot, trigger):
267     """Set the topic mask to use for the current channel
268 
269     Within the topic mask, {} is used to allow substituting in chunks of text.
270 
271     This mask is used when running the 'topic' command.
272     """
273     bot.db.set_channel_value(trigger.sender, 'topic_mask', trigger.group(2))
274     bot.say("Gotcha, " + trigger.nick)
275 
276 
277 @require_chanmsg
278 @require_privilege(OP, 'You are not a channel operator.')
279 @commands('showmask')
280 def show_mask(bot, trigger):
281     """Show the topic mask for the current channel."""
282     mask = bot.db.get_channel_value(trigger.sender, 'topic_mask')
283     mask = mask or default_mask(trigger)
284     bot.say(mask)
285
```
--- 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/adminchannel.py b/sopel/modules/adminchannel.py
--- a/sopel/modules/adminchannel.py
+++ b/sopel/modules/adminchannel.py
@@ -19,6 +19,74 @@
     return '{} {} {} {}'.format(welcome, chan, topic_, arg)
 
 
+@require_chanmsg
+@require_privilege(OP, 'You are not a channel operator.')
+@commands('op')
+def op(bot, trigger):
+    """
+    Command to op users in a room. If no nick is given,
+    Sopel will op the nick who sent the command
+    """
+    if bot.channels[trigger.sender].privileges[bot.nick] < OP:
+        return bot.reply("I'm not a channel operator!")
+    nick = trigger.group(2)
+    channel = trigger.sender
+    if not nick:
+        nick = trigger.nick
+    bot.write(['MODE', channel, "+o", nick])
+
+
+@require_chanmsg
+@require_privilege(OP, 'You are not a channel operator.')
+@commands('deop')
+def deop(bot, trigger):
+    """
+    Command to deop users in a room. If no nick is given,
+    Sopel will deop the nick who sent the command
+    """
+    if bot.channels[trigger.sender].privileges[bot.nick] < OP:
+        return bot.reply("I'm not a channel operator!")
+    nick = trigger.group(2)
+    channel = trigger.sender
+    if not nick:
+        nick = trigger.nick
+    bot.write(['MODE', channel, "-o", nick])
+
+
+@require_chanmsg
+@require_privilege(OP, 'You are not a channel operator.')
+@commands('voice')
+def voice(bot, trigger):
+    """
+    Command to voice users in a room. If no nick is given,
+    Sopel will voice the nick who sent the command
+    """
+    if bot.channels[trigger.sender].privileges[bot.nick] < HALFOP:
+        return bot.reply("I'm not a channel operator!")
+    nick = trigger.group(2)
+    channel = trigger.sender
+    if not nick:
+        nick = trigger.nick
+    bot.write(['MODE', channel, "+v", nick])
+
+
+@require_chanmsg
+@require_privilege(OP, 'You are not a channel operator.')
+@commands('devoice')
+def devoice(bot, trigger):
+    """
+    Command to devoice users in a room. If no nick is given,
+    Sopel will devoice the nick who sent the command
+    """
+    if bot.channels[trigger.sender].privileges[bot.nick] < HALFOP:
+        return bot.reply("I'm not a channel operator!")
+    nick = trigger.group(2)
+    channel = trigger.sender
+    if not nick:
+        nick = trigger.nick
+    bot.write(['MODE', channel, "-v", nick])
+
+
 @require_chanmsg
 @require_privilege(OP, 'You are not a channel operator.')
 @commands('kick')

{"golden_diff": "diff --git a/sopel/modules/adminchannel.py b/sopel/modules/adminchannel.py\n--- a/sopel/modules/adminchannel.py\n+++ b/sopel/modules/adminchannel.py\n@@ -19,6 +19,74 @@\n     return '{} {} {} {}'.format(welcome, chan, topic_, arg)\n \n \n+@require_chanmsg\n+@require_privilege(OP, 'You are not a channel operator.')\n+@commands('op')\n+def op(bot, trigger):\n+    \"\"\"\n+    Command to op users in a room. If no nick is given,\n+    Sopel will op the nick who sent the command\n+    \"\"\"\n+    if bot.channels[trigger.sender].privileges[bot.nick] < OP:\n+        return bot.reply(\"I'm not a channel operator!\")\n+    nick = trigger.group(2)\n+    channel = trigger.sender\n+    if not nick:\n+        nick = trigger.nick\n+    bot.write(['MODE', channel, \"+o\", nick])\n+\n+\n+@require_chanmsg\n+@require_privilege(OP, 'You are not a channel operator.')\n+@commands('deop')\n+def deop(bot, trigger):\n+    \"\"\"\n+    Command to deop users in a room. If no nick is given,\n+    Sopel will deop the nick who sent the command\n+    \"\"\"\n+    if bot.channels[trigger.sender].privileges[bot.nick] < OP:\n+        return bot.reply(\"I'm not a channel operator!\")\n+    nick = trigger.group(2)\n+    channel = trigger.sender\n+    if not nick:\n+        nick = trigger.nick\n+    bot.write(['MODE', channel, \"-o\", nick])\n+\n+\n+@require_chanmsg\n+@require_privilege(OP, 'You are not a channel operator.')\n+@commands('voice')\n+def voice(bot, trigger):\n+    \"\"\"\n+    Command to voice users in a room. If no nick is given,\n+    Sopel will voice the nick who sent the command\n+    \"\"\"\n+    if bot.channels[trigger.sender].privileges[bot.nick] < HALFOP:\n+        return bot.reply(\"I'm not a channel operator!\")\n+    nick = trigger.group(2)\n+    channel = trigger.sender\n+    if not nick:\n+        nick = trigger.nick\n+    bot.write(['MODE', channel, \"+v\", nick])\n+\n+\n+@require_chanmsg\n+@require_privilege(OP, 'You are not a channel operator.')\n+@commands('devoice')\n+def devoice(bot, trigger):\n+    \"\"\"\n+    Command to devoice users in a room. If no nick is given,\n+    Sopel will devoice the nick who sent the command\n+    \"\"\"\n+    if bot.channels[trigger.sender].privileges[bot.nick] < HALFOP:\n+        return bot.reply(\"I'm not a channel operator!\")\n+    nick = trigger.group(2)\n+    channel = trigger.sender\n+    if not nick:\n+        nick = trigger.nick\n+    bot.write(['MODE', channel, \"-v\", nick])\n+\n+\n @require_chanmsg\n @require_privilege(OP, 'You are not a channel operator.')\n @commands('kick')\n", "issue": "adminchannel: Restore op/voice commands\n@kwaaak made a very good point the other day in PM on IRC: `adminchannel`'s commands for managing ops and voices were removed (902e715) for seemingly no particular reason.\r\n\r\nI haven't decided when to do it (for 6.x or wait for Sopel 7), but I do think those commands should come back. They'll need a bit of tweaking, but nothing major. Milestone 6.6.0 for now, I guess, to be punted if I can think of a reason it should be delayed.\n", "before_files": [{"content": "# coding=utf-8\n\"\"\"\nadminchannel.py - Sopel Channel Admin Module\nCopyright 2010-2011, Michael Yanovich, Alek Rollyson, and Elsie Powell\nCopyright \u00a9 2012, Elad Alfassa <elad@fedoraproject.org>\nLicensed under the Eiffel Forum License 2.\n\nhttps://sopel.chat\n\"\"\"\nfrom __future__ import unicode_literals, absolute_import, print_function, division\n\nimport re\n\nfrom sopel import formatting\nfrom sopel.module import (\n    commands, example, priority, OP, HALFOP, require_privilege, require_chanmsg\n)\nfrom sopel.tools import Identifier\n\n\ndef default_mask(trigger):\n    welcome = formatting.color('Welcome to:', formatting.colors.PURPLE)\n    chan = formatting.color(trigger.sender, formatting.colors.TEAL)\n    topic_ = formatting.bold('Topic:')\n    topic_ = formatting.color('| ' + topic_, formatting.colors.PURPLE)\n    arg = formatting.color('{}', formatting.colors.GREEN)\n    return '{} {} {} {}'.format(welcome, chan, topic_, arg)\n\n\n@require_chanmsg\n@require_privilege(OP, 'You are not a channel operator.')\n@commands('kick')\n@priority('high')\ndef kick(bot, trigger):\n    \"\"\"Kick a user from the channel.\"\"\"\n    if bot.channels[trigger.sender].privileges[bot.nick] < HALFOP:\n        return bot.reply(\"I'm not a channel operator!\")\n    text = trigger.group().split()\n    argc = len(text)\n    if argc < 2:\n        return\n    opt = Identifier(text[1])\n    nick = opt\n    channel = trigger.sender\n    reasonidx = 2\n    if not opt.is_nick():\n        if argc < 3:\n            return\n        nick = text[2]\n        channel = opt\n        reasonidx = 3\n    reason = ' '.join(text[reasonidx:])\n    if nick != bot.config.core.nick:\n        bot.write(['KICK', channel, nick], reason)\n\n\ndef configureHostMask(mask):\n    if mask == '*!*@*':\n        return mask\n    if re.match('^[^.@!/]+$', mask) is not None:\n        return '%s!*@*' % mask\n    if re.match('^[^@!]+$', mask) is not None:\n        return '*!*@%s' % mask\n\n    m = re.match('^([^!@]+)@$', mask)\n    if m is not None:\n        return '*!%s@*' % m.group(1)\n\n    m = re.match('^([^!@]+)@([^@!]+)$', mask)\n    if m is not None:\n        return '*!%s@%s' % (m.group(1), m.group(2))\n\n    m = re.match('^([^!@]+)!(^[!@]+)@?$', mask)\n    if m is not None:\n        return '%s!%s@*' % (m.group(1), m.group(2))\n    return ''\n\n\n@require_chanmsg\n@require_privilege(OP, 'You are not a channel operator.')\n@commands('ban')\n@priority('high')\ndef ban(bot, trigger):\n    \"\"\"Ban a user from the channel\n\n    The bot must be a channel operator for this command to work.\n    \"\"\"\n    if bot.channels[trigger.sender].privileges[bot.nick] < HALFOP:\n        return bot.reply(\"I'm not a channel operator!\")\n    text = trigger.group().split()\n    argc = len(text)\n    if argc < 2:\n        return\n    opt = Identifier(text[1])\n    banmask = opt\n    channel = trigger.sender\n    if not opt.is_nick():\n        if argc < 3:\n            return\n        channel = opt\n        banmask = text[2]\n    banmask = configureHostMask(banmask)\n    if banmask == '':\n        return\n    bot.write(['MODE', channel, '+b', banmask])\n\n\n@require_chanmsg\n@require_privilege(OP, 'You are not a channel operator.')\n@commands('unban')\ndef unban(bot, trigger):\n    \"\"\"Unban a user from the channel\n\n    The bot must be a channel operator for this command to work.\n    \"\"\"\n    if bot.channels[trigger.sender].privileges[bot.nick] < HALFOP:\n        return bot.reply(\"I'm not a channel operator!\")\n    text = trigger.group().split()\n    argc = len(text)\n    if argc < 2:\n        return\n    opt = Identifier(text[1])\n    banmask = opt\n    channel = trigger.sender\n    if not opt.is_nick():\n        if argc < 3:\n            return\n        channel = opt\n        banmask = text[2]\n    banmask = configureHostMask(banmask)\n    if banmask == '':\n        return\n    bot.write(['MODE', channel, '-b', banmask])\n\n\n@require_chanmsg\n@require_privilege(OP, 'You are not a channel operator.')\n@commands('quiet')\ndef quiet(bot, trigger):\n    \"\"\"Quiet a user\n\n    The bot must be a channel operator for this command to work.\n    \"\"\"\n    if bot.channels[trigger.sender].privileges[bot.nick] < OP:\n        return bot.reply(\"I'm not a channel operator!\")\n    text = trigger.group().split()\n    argc = len(text)\n    if argc < 2:\n        return\n    opt = Identifier(text[1])\n    quietmask = opt\n    channel = trigger.sender\n    if not opt.is_nick():\n        if argc < 3:\n            return\n        quietmask = text[2]\n        channel = opt\n    quietmask = configureHostMask(quietmask)\n    if quietmask == '':\n        return\n    bot.write(['MODE', channel, '+q', quietmask])\n\n\n@require_chanmsg\n@require_privilege(OP, 'You are not a channel operator.')\n@commands('unquiet')\ndef unquiet(bot, trigger):\n    \"\"\"Unquiet a user\n\n    The bot must be a channel operator for this command to work.\n    \"\"\"\n    if bot.channels[trigger.sender].privileges[bot.nick] < OP:\n        return bot.reply(\"I'm not a channel operator!\")\n    text = trigger.group().split()\n    argc = len(text)\n    if argc < 2:\n        return\n    opt = Identifier(text[1])\n    quietmask = opt\n    channel = trigger.sender\n    if not opt.is_nick():\n        if argc < 3:\n            return\n        quietmask = text[2]\n        channel = opt\n    quietmask = configureHostMask(quietmask)\n    if quietmask == '':\n        return\n    bot.write(['MODE', channel, '-q', quietmask])\n\n\n@require_chanmsg\n@require_privilege(OP, 'You are not a channel operator.')\n@commands('kickban', 'kb')\n@example('.kickban [#chan] user1 user!*@* get out of here')\n@priority('high')\ndef kickban(bot, trigger):\n    \"\"\"Kick and ban a user from the channel\n\n    The bot must be a channel operator for this command to work.\n    \"\"\"\n    if bot.channels[trigger.sender].privileges[bot.nick] < HALFOP:\n        return bot.reply(\"I'm not a channel operator!\")\n    text = trigger.group().split()\n    argc = len(text)\n    if argc < 4:\n        return\n    opt = Identifier(text[1])\n    nick = opt\n    mask = text[2]\n    channel = trigger.sender\n    reasonidx = 3\n    if not opt.is_nick():\n        if argc < 5:\n            return\n        channel = opt\n        nick = text[2]\n        mask = text[3]\n        reasonidx = 4\n    reason = ' '.join(text[reasonidx:])\n    mask = configureHostMask(mask)\n    if mask == '':\n        return\n    bot.write(['MODE', channel, '+b', mask])\n    bot.write(['KICK', channel, nick], reason)\n\n\n@require_chanmsg\n@require_privilege(OP, 'You are not a channel operator.')\n@commands('topic')\ndef topic(bot, trigger):\n    \"\"\"Change the channel topic\n\n    The bot must be a channel operator for this command to work.\n    \"\"\"\n    if bot.channels[trigger.sender].privileges[bot.nick] < HALFOP:\n        return bot.reply(\"I'm not a channel operator!\")\n    if not trigger.group(2):\n        return\n    channel = trigger.sender.lower()\n\n    narg = 1\n    mask = None\n    mask = bot.db.get_channel_value(channel, 'topic_mask')\n    mask = mask or default_mask(trigger)\n    mask = mask.replace('%s', '{}')\n    narg = len(re.findall('{}', mask))\n\n    top = trigger.group(2)\n    args = []\n    if top:\n        args = top.split('~', narg)\n\n    if len(args) != narg:\n        message = \"Not enough arguments. You gave {}, it requires {}.\".format(\n            len(args), narg)\n        return bot.say(message)\n    topic = mask.format(*args)\n\n    bot.write(('TOPIC', channel + ' :' + topic))\n\n\n@require_chanmsg\n@require_privilege(OP, 'You are not a channel operator.')\n@commands('tmask')\ndef set_mask(bot, trigger):\n    \"\"\"Set the topic mask to use for the current channel\n\n    Within the topic mask, {} is used to allow substituting in chunks of text.\n\n    This mask is used when running the 'topic' command.\n    \"\"\"\n    bot.db.set_channel_value(trigger.sender, 'topic_mask', trigger.group(2))\n    bot.say(\"Gotcha, \" + trigger.nick)\n\n\n@require_chanmsg\n@require_privilege(OP, 'You are not a channel operator.')\n@commands('showmask')\ndef show_mask(bot, trigger):\n    \"\"\"Show the topic mask for the current channel.\"\"\"\n    mask = bot.db.get_channel_value(trigger.sender, 'topic_mask')\n    mask = mask or default_mask(trigger)\n    bot.say(mask)\n", "path": "sopel/modules/adminchannel.py"}], "after_files": [{"content": "# coding=utf-8\n# Copyright 2010-2011, Michael Yanovich, Alek Rollyson, and Elsie Powell\n# Copyright \u00a9 2012, Elad Alfassa <elad@fedoraproject.org>\n# Licensed under the Eiffel Forum License 2.\nfrom __future__ import unicode_literals, absolute_import, print_function, division\n\nimport re\nfrom sopel import formatting\nfrom sopel.module import commands, priority, OP, HALFOP, require_privilege, require_chanmsg\nfrom sopel.tools import Identifier\n\n\ndef default_mask(trigger):\n    welcome = formatting.color('Welcome to:', formatting.colors.PURPLE)\n    chan = formatting.color(trigger.sender, formatting.colors.TEAL)\n    topic_ = formatting.bold('Topic:')\n    topic_ = formatting.color('| ' + topic_, formatting.colors.PURPLE)\n    arg = formatting.color('{}', formatting.colors.GREEN)\n    return '{} {} {} {}'.format(welcome, chan, topic_, arg)\n\n\n@require_chanmsg\n@require_privilege(OP, 'You are not a channel operator.')\n@commands('op')\ndef op(bot, trigger):\n    \"\"\"\n    Command to op users in a room. If no nick is given,\n    Sopel will op the nick who sent the command\n    \"\"\"\n    if bot.channels[trigger.sender].privileges[bot.nick] < OP:\n        return bot.reply(\"I'm not a channel operator!\")\n    nick = trigger.group(2)\n    channel = trigger.sender\n    if not nick:\n        nick = trigger.nick\n    bot.write(['MODE', channel, \"+o\", nick])\n\n\n@require_chanmsg\n@require_privilege(OP, 'You are not a channel operator.')\n@commands('deop')\ndef deop(bot, trigger):\n    \"\"\"\n    Command to deop users in a room. If no nick is given,\n    Sopel will deop the nick who sent the command\n    \"\"\"\n    if bot.channels[trigger.sender].privileges[bot.nick] < OP:\n        return bot.reply(\"I'm not a channel operator!\")\n    nick = trigger.group(2)\n    channel = trigger.sender\n    if not nick:\n        nick = trigger.nick\n    bot.write(['MODE', channel, \"-o\", nick])\n\n\n@require_chanmsg\n@require_privilege(OP, 'You are not a channel operator.')\n@commands('voice')\ndef voice(bot, trigger):\n    \"\"\"\n    Command to voice users in a room. If no nick is given,\n    Sopel will voice the nick who sent the command\n    \"\"\"\n    if bot.channels[trigger.sender].privileges[bot.nick] < HALFOP:\n        return bot.reply(\"I'm not a channel operator!\")\n    nick = trigger.group(2)\n    channel = trigger.sender\n    if not nick:\n        nick = trigger.nick\n    bot.write(['MODE', channel, \"+v\", nick])\n\n\n@require_chanmsg\n@require_privilege(OP, 'You are not a channel operator.')\n@commands('devoice')\ndef devoice(bot, trigger):\n    \"\"\"\n    Command to devoice users in a room. If no nick is given,\n    Sopel will devoice the nick who sent the command\n    \"\"\"\n    if bot.channels[trigger.sender].privileges[bot.nick] < HALFOP:\n        return bot.reply(\"I'm not a channel operator!\")\n    nick = trigger.group(2)\n    channel = trigger.sender\n    if not nick:\n        nick = trigger.nick\n    bot.write(['MODE', channel, \"-v\", nick])\n\n\n@require_chanmsg\n@require_privilege(OP, 'You are not a channel operator.')\n@commands('kick')\n@priority('high')\ndef kick(bot, trigger):\n    \"\"\"\n    Kick a user from the channel.\n    \"\"\"\n    if bot.channels[trigger.sender].privileges[bot.nick] < HALFOP:\n        return bot.reply(\"I'm not a channel operator!\")\n    text = trigger.group().split()\n    argc = len(text)\n    if argc < 2:\n        return\n    opt = Identifier(text[1])\n    nick = opt\n    channel = trigger.sender\n    reasonidx = 2\n    if not opt.is_nick():\n        if argc < 3:\n            return\n        nick = text[2]\n        channel = opt\n        reasonidx = 3\n    reason = ' '.join(text[reasonidx:])\n    if nick != bot.config.core.nick:\n        bot.write(['KICK', channel, nick], reason)\n\n\ndef configureHostMask(mask):\n    if mask == '*!*@*':\n        return mask\n    if re.match('^[^.@!/]+$', mask) is not None:\n        return '%s!*@*' % mask\n    if re.match('^[^@!]+$', mask) is not None:\n        return '*!*@%s' % mask\n\n    m = re.match('^([^!@]+)@$', mask)\n    if m is not None:\n        return '*!%s@*' % m.group(1)\n\n    m = re.match('^([^!@]+)@([^@!]+)$', mask)\n    if m is not None:\n        return '*!%s@%s' % (m.group(1), m.group(2))\n\n    m = re.match('^([^!@]+)!(^[!@]+)@?$', mask)\n    if m is not None:\n        return '%s!%s@*' % (m.group(1), m.group(2))\n    return ''\n\n\n@require_chanmsg\n@require_privilege(OP, 'You are not a channel operator.')\n@commands('ban')\n@priority('high')\ndef ban(bot, trigger):\n    \"\"\"\n    This give admins the ability to ban a user.\n    The bot must be a Channel Operator for this command to work.\n    \"\"\"\n    if bot.channels[trigger.sender].privileges[bot.nick] < HALFOP:\n        return bot.reply(\"I'm not a channel operator!\")\n    text = trigger.group().split()\n    argc = len(text)\n    if argc < 2:\n        return\n    opt = Identifier(text[1])\n    banmask = opt\n    channel = trigger.sender\n    if not opt.is_nick():\n        if argc < 3:\n            return\n        channel = opt\n        banmask = text[2]\n    banmask = configureHostMask(banmask)\n    if banmask == '':\n        return\n    bot.write(['MODE', channel, '+b', banmask])\n\n\n@require_chanmsg\n@require_privilege(OP, 'You are not a channel operator.')\n@commands('unban')\ndef unban(bot, trigger):\n    \"\"\"\n    This give admins the ability to unban a user.\n    The bot must be a Channel Operator for this command to work.\n    \"\"\"\n    if bot.channels[trigger.sender].privileges[bot.nick] < HALFOP:\n        return bot.reply(\"I'm not a channel operator!\")\n    text = trigger.group().split()\n    argc = len(text)\n    if argc < 2:\n        return\n    opt = Identifier(text[1])\n    banmask = opt\n    channel = trigger.sender\n    if not opt.is_nick():\n        if argc < 3:\n            return\n        channel = opt\n        banmask = text[2]\n    banmask = configureHostMask(banmask)\n    if banmask == '':\n        return\n    bot.write(['MODE', channel, '-b', banmask])\n\n\n@require_chanmsg\n@require_privilege(OP, 'You are not a channel operator.')\n@commands('quiet')\ndef quiet(bot, trigger):\n    \"\"\"\n    This gives admins the ability to quiet a user.\n    The bot must be a Channel Operator for this command to work.\n    \"\"\"\n    if bot.channels[trigger.sender].privileges[bot.nick] < OP:\n        return bot.reply(\"I'm not a channel operator!\")\n    text = trigger.group().split()\n    argc = len(text)\n    if argc < 2:\n        return\n    opt = Identifier(text[1])\n    quietmask = opt\n    channel = trigger.sender\n    if not opt.is_nick():\n        if argc < 3:\n            return\n        quietmask = text[2]\n        channel = opt\n    quietmask = configureHostMask(quietmask)\n    if quietmask == '':\n        return\n    bot.write(['MODE', channel, '+q', quietmask])\n\n\n@require_chanmsg\n@require_privilege(OP, 'You are not a channel operator.')\n@commands('unquiet')\ndef unquiet(bot, trigger):\n    \"\"\"\n    This gives admins the ability to unquiet a user.\n    The bot must be a Channel Operator for this command to work.\n    \"\"\"\n    if bot.channels[trigger.sender].privileges[bot.nick] < OP:\n        return bot.reply(\"I'm not a channel operator!\")\n    text = trigger.group().split()\n    argc = len(text)\n    if argc < 2:\n        return\n    opt = Identifier(text[1])\n    quietmask = opt\n    channel = trigger.sender\n    if not opt.is_nick():\n        if argc < 3:\n            return\n        quietmask = text[2]\n        channel = opt\n    quietmask = configureHostMask(quietmask)\n    if quietmask == '':\n        return\n    bot.write(['MODE', channel, '-q', quietmask])\n\n\n@require_chanmsg\n@require_privilege(OP, 'You are not a channel operator.')\n@commands('kickban', 'kb')\n@priority('high')\ndef kickban(bot, trigger):\n    \"\"\"\n    This gives admins the ability to kickban a user.\n    The bot must be a Channel Operator for this command to work.\n    .kickban [#chan] user1 user!*@* get out of here\n    \"\"\"\n    if bot.channels[trigger.sender].privileges[bot.nick] < HALFOP:\n        return bot.reply(\"I'm not a channel operator!\")\n    text = trigger.group().split()\n    argc = len(text)\n    if argc < 4:\n        return\n    opt = Identifier(text[1])\n    nick = opt\n    mask = text[2]\n    channel = trigger.sender\n    reasonidx = 3\n    if not opt.is_nick():\n        if argc < 5:\n            return\n        channel = opt\n        nick = text[2]\n        mask = text[3]\n        reasonidx = 4\n    reason = ' '.join(text[reasonidx:])\n    mask = configureHostMask(mask)\n    if mask == '':\n        return\n    bot.write(['MODE', channel, '+b', mask])\n    bot.write(['KICK', channel, nick], reason)\n\n\n@require_chanmsg\n@require_privilege(OP, 'You are not a channel operator.')\n@commands('topic')\ndef topic(bot, trigger):\n    \"\"\"\n    This gives ops the ability to change the topic.\n    The bot must be a Channel Operator for this command to work.\n    \"\"\"\n    if bot.channels[trigger.sender].privileges[bot.nick] < HALFOP:\n        return bot.reply(\"I'm not a channel operator!\")\n    if not trigger.group(2):\n        return\n    channel = trigger.sender.lower()\n\n    narg = 1\n    mask = None\n    mask = bot.db.get_channel_value(channel, 'topic_mask')\n    mask = mask or default_mask(trigger)\n    mask = mask.replace('%s', '{}')\n    narg = len(re.findall('{}', mask))\n\n    top = trigger.group(2)\n    args = []\n    if top:\n        args = top.split('~', narg)\n\n    if len(args) != narg:\n        message = \"Not enough arguments. You gave {}, it requires {}.\".format(\n            len(args), narg)\n        return bot.say(message)\n    topic = mask.format(*args)\n\n    bot.write(('TOPIC', channel + ' :' + topic))\n\n\n@require_chanmsg\n@require_privilege(OP, 'You are not a channel operator.')\n@commands('tmask')\ndef set_mask(bot, trigger):\n    \"\"\"\n    Set the mask to use for .topic in the current channel. {} is used to allow\n    substituting in chunks of text.\n    \"\"\"\n    bot.db.set_channel_value(trigger.sender, 'topic_mask', trigger.group(2))\n    bot.say(\"Gotcha, \" + trigger.nick)\n\n\n@require_chanmsg\n@require_privilege(OP, 'You are not a channel operator.')\n@commands('showmask')\ndef show_mask(bot, trigger):\n    \"\"\"Show the topic mask for the current channel.\"\"\"\n    mask = bot.db.get_channel_value(trigger.sender, 'topic_mask')\n    mask = mask or default_mask(trigger)\n    bot.say(mask)\n", "path": "sopel/modules/adminchannel.py"}]}