-
Notifications
You must be signed in to change notification settings - Fork 44
/
moderation.py
477 lines (419 loc) · 20.2 KB
/
moderation.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
import asyncio, discord, functools, re
from discord.ext.commands import BadArgument, has_permissions, bot_has_permissions, RoleConverter
from .. import db
from ._utils import *
from ..utils import clean
class SafeRoleConverter(RoleConverter):
async def convert(self, ctx, arg):
try:
return await super().convert(ctx, arg)
except BadArgument:
if arg.casefold() in ('everyone', '@everyone', '@/everyone', '@.everyone', '@ everyone', '@\N{ZERO WIDTH SPACE}everyone'):
return ctx.guild.default_role
else:
raise
class Moderation(Cog):
async def modlogger(self, ctx, action, target, reason):
modlogmessage = "{} has {} {} because {}".format(ctx.author, action, target, reason)
modlogmessage = clean(ctx=ctx, text=modlogmessage)
with db.Session() as session:
modlogchannel = session.query(Guildmodlog).filter_by(id=ctx.guild.id).one_or_none()
await ctx.send(modlogmessage)
if modlogchannel is not None:
channel = ctx.guild.get_channel(modlogchannel.modlog_channel)
await channel.send(modlogmessage)
else:
await ctx.send("Please configure modlog channel to enable modlog functionality")
async def permoverride(self, user, **overwrites):
for i in user.guild.channels:
overwrite = i.overwrites_for(user)
overwrite.update(**overwrites)
await i.set_permissions(target=user, overwrite=overwrite)
if overwrite.is_empty():
await i.set_permissions(target=user, overwrite=None)
async def punishmenttimer(self, ctx, timing, target, lookup):
regexstring = re.compile(r"((?P<hours>\d+)h)?((?P<minutes>\d+)m)?")
regexiter = re.match(regexstring, timing)
matches = regexiter.groupdict()
try:
hours = int(matches.get('hours'))
except:
hours = 0
try:
minutes = int(matches.get('minutes'))
except:
minutes = 0
time = (hours * 3600) + (minutes * 60)
await asyncio.sleep(time)
with db.Session() as session:
user = session.query(lookup).filter_by(id=target.id).one_or_none()
if user is not None:
if lookup == Deafen:
self.bot.loop.create_task(coro=self.undeafen.callback(self=self, ctx=ctx, member_mentions=target))
if lookup == Guildmute:
self.bot.loop.create_task(coro=self.unmute.callback(self=self, ctx=ctx, member_mentions=target))
@command()
@has_permissions(ban_members=True)
@bot_has_permissions(ban_members=True)
async def ban(self, ctx, user_mentions: discord.User, *, reason="No reason provided"):
"Bans the user mentioned."
await ctx.guild.ban(user_mentions, reason=reason)
await self.modlogger(ctx=ctx, action="banned", target=user_mentions, reason=reason)
@command()
@has_permissions(ban_members=True)
@bot_has_permissions(ban_members=True)
async def unban(self, ctx, user_mentions: discord.User, *, reason="No reason provided"):
"Unbans the user ID mentioned."
await ctx.guild.unban(user_mentions, reason=reason)
await self.modlogger(ctx=ctx, action="unbanned", target=user_mentions, reason=reason)
@command()
@has_permissions(kick_members=True)
@bot_has_permissions(kick_members=True)
async def kick(self, ctx, user_mentions: discord.User, *, reason="No reason provided"):
"Kicks the user mentioned."
await ctx.guild.kick(user_mentions, reason=reason)
await self.modlogger(ctx=ctx, action="kicked", target=user_mentions, reason=reason)
@command()
@has_permissions(administrator=True)
async def modlogconfig(self, ctx, channel_mentions: discord.TextChannel):
"""Set the modlog channel for a server by passing the channel id"""
print(channel_mentions)
with db.Session() as session:
config = session.query(Guildmodlog).filter_by(id=str(ctx.guild.id)).one_or_none()
if config is not None:
print("config is not none")
config.name = ctx.guild.name
config.modlog_channel = str(channel_mentions.id)
else:
print("Config is none")
config = Guildmodlog(id=ctx.guild.id, modlog_channel=channel_mentions.id, name=ctx.guild.name)
session.add(config)
await ctx.send(ctx.message.author.mention + ', modlog settings configured!')
async def on_message(self, message):
if message.author.bot: return
if not message.guild.me.guild_permissions.manage_roles: return
with db.Session() as session:
config = session.query(GuildNewMember).filter_by(guild_id=message.guild.id).one_or_none()
if config is not None:
string = config.message
content = message.content.casefold()
if string not in content: return
channel = config.channel_id
role_id = config.role_id
if message.channel.id != channel: return
await message.author.add_roles(discord.utils.get(message.guild.roles, id=role_id))
@command()
@has_permissions(administrator=True)
async def nmconfig(self, ctx, channel_mention: discord.TextChannel, role: discord.Role, *, message):
"""Sets the config for the new members channel"""
with db.Session() as session:
config = session.query(GuildNewMember).filter_by(guild_id=ctx.guild.id).one_or_none()
if config is not None:
config.channel_id = channel_mention.id
config.role_id = role.id
config.message = message.casefold()
else:
config = GuildNewMember(guild_id=ctx.guild.id, channel_id=channel_mention.id, role_id=role.id, message=message.casefold())
session.add(config)
role_name = role.name
await ctx.send("New Member Channel configured as: {channel}. Role configured as: {role}. Message: {message}".format(channel=ctx.channel.name, role=role_name, message=message))
nmconfig.example_usage = """
`{prefix}nmconfig #new_members Member I have read the rules and regulations` - Configures the #new_members channel so if someone types "I have read the rules and regulations" it assigns them the Member role.
"""
@command()
@has_permissions(manage_roles=True)
@bot_has_permissions(manage_roles=True)
async def timeout(self, ctx, duration: float):
"""Set a timeout (no sending messages or adding reactions) on the current channel."""
with db.Session() as session:
settings = session.query(MemberRole).filter_by(id=ctx.guild.id).one_or_none()
if settings is None:
settings = MemberRole(id=ctx.guild.id)
session.add(settings)
member_role = discord.utils.get(ctx.guild.roles, id=settings.member_role) # None-safe - nonexistent or non-configured role return None
if member_role is not None:
targets = {member_role}
else:
await ctx.send('{0.author.mention}, the members role has not been configured. This may not work as expected. Use `{0.prefix}help memberconfig` to see how to set this up.'.format(ctx))
targets = set(sorted(ctx.guild.roles)[:ctx.author.top_role.position])
to_restore = [(target, ctx.channel.overwrites_for(target)) for target in targets]
for target, overwrite in to_restore:
new_overwrite = discord.PermissionOverwrite.from_pair(*overwrite.pair())
new_overwrite.update(send_messages=False, add_reactions=False)
await ctx.channel.set_permissions(target, overwrite=new_overwrite)
for allow_target in (ctx.me, ctx.author):
overwrite = ctx.channel.overwrites_for(allow_target)
new_overwrite = discord.PermissionOverwrite.from_pair(*overwrite.pair())
new_overwrite.update(send_messages=True)
await ctx.channel.set_permissions(allow_target, overwrite=new_overwrite)
to_restore.append((allow_target, overwrite))
e = discord.Embed(title='Timeout - {}s'.format(duration), description='This channel has been timed out.', color=discord.Color.blue())
e.set_author(name=ctx.author.display_name, icon_url=ctx.author.avatar_url_as(format='png', size=32))
msg = await ctx.send(embed=e)
await asyncio.sleep(duration)
for target, overwrite in to_restore:
if all(permission is None for _, permission in overwrite):
await ctx.channel.set_permissions(target, overwrite=None)
else:
await ctx.channel.set_permissions(target, overwrite=overwrite)
e.description = 'The timeout has ended.'
await msg.edit(embed=e)
timeout.example_usage = """
`{prefix}timeout 60` - prevents sending messages in this channel for 1 minute (60s)
"""
@command()
@has_permissions(administrator=True)
async def memberconfig(self, ctx, *, member_role: SafeRoleConverter):
"""
Set the member role for the guild.
The member role is the role used for the timeout command. It should be a role that all members of the server have.
"""
if member_role >= ctx.author.top_role:
raise BadArgument('member role cannot be higher than your top role!')
with db.Session() as session:
settings = session.query(MemberRole).filter_by(id=ctx.guild.id).one_or_none()
if settings is None:
settings = MemberRole(id=ctx.guild.id, member_role=member_role.id)
session.add(settings)
else:
settings.member_role = member_role.id
await ctx.send('Member role set as `{}`.'.format(member_role.name))
memberconfig.example_usage = """
`{prefix}memberconfig Members` - set a role called "Members" as the member role
`{prefix}memberconfig @everyone` - set the default role as the member role
`{prefix}memberconfig everyone` - set the default role as the member role (ping-safe)
`{prefix}memberconfig @ everyone` - set the default role as the member role (ping-safe)
`{prefix}memberconfig @.everyone` - set the default role as the member role (ping-safe)
`{prefix}memberconfig @/everyone` - set the default role as the member role (ping-safe)
"""
@command()
@has_permissions(administrator=True)
async def memberlogconfig(self, ctx, channel_mentions: discord.TextChannel):
"""Set the modlog channel for a server by passing the channel id"""
print(channel_mentions)
with db.Session() as session:
config = session.query(Guildmemberlog).filter_by(id=str(ctx.guild.id)).one_or_none()
if config is not None:
print("config is not none")
config.name = ctx.guild.name
config.memberlog_channel = str(channel_mentions.id)
else:
print("Config is none")
config = Guildmemberlog(id=ctx.guild.id, memberlog_channel=channel_mentions.id, name=ctx.guild.name)
session.add(config)
await ctx.send(ctx.message.author.mention + ', memberlog settings configured!')
@command()
@has_permissions(administrator=True)
async def messagelogconfig(self, ctx, channel_mentions: discord.TextChannel):
"""Set the modlog channel for a server by passing the channel id"""
print(channel_mentions)
with db.Session() as session:
config = session.query(Guildmessagelog).filter_by(id=str(ctx.guild.id)).one_or_none()
if config is not None:
print("config is not none")
config.name = ctx.guild.name
config.messagelog_channel = str(channel_mentions.id)
else:
print("Config is none")
config = Guildmessagelog(id=ctx.guild.id, messagelog_channel=channel_mentions.id, name=ctx.guild.name)
session.add(config)
await ctx.send(ctx.message.author.mention + ', messagelog settings configured!')
async def on_member_join(self, member):
memberjoinedmessage = "{} has joined the server! Enjoy your stay! This server now has {} members".format(member.display_name, member.guild.member_count)
with db.Session() as session:
memberlogchannel = session.query(Guildmemberlog).filter_by(id=member.guild.id).one_or_none()
if memberlogchannel is not None:
channel = member.guild.get_channel(memberlogchannel.memberlog_channel)
await channel.send(memberjoinedmessage)
user = session.query(Guildmute).filter_by(id=member.id).one_or_none()
if user is not None and user.guild == member.guild.id:
await self.permoverride(member, add_reactions=False, send_messages=False)
user = session.query(Deafen).filter_by(id=member.id).one_or_none()
if user is not None and user.guild == member.guild.id:
await self.permoverride(member, read_messages=False)
async def on_member_remove(self, member):
memberleftmessage = "{} has left the server! This server now has {} members".format(member.display_name, member.guild.member_count)
with db.Session() as session:
memberlogchannel = session.query(Guildmemberlog).filter_by(id=member.guild.id).one_or_none()
if memberlogchannel is not None:
channel = member.guild.get_channel(memberlogchannel.memberlog_channel)
await channel.send(memberleftmessage)
async def on_message_delete(self, message):
e = discord.Embed(type='rich')
e.title = 'Message Deletion'
e.color = 0xFF0000
e.add_field(name='Author', value=message.author)
e.add_field(name='Author pingable', value=message.author.mention)
e.add_field(name='Channel', value=message.channel)
if 1024 > len(message.content) > 0:
e.add_field(name="Deleted message", value=message.content)
elif len(message.content) != 0:
e.add_field(name="Deleted message", value=message.content[0:1023])
e.add_field(name="Deleted message continued", value=message.content[1024:2000])
elif len(message.content) == 0:
for i in message.embeds:
e.add_field(name="Title", value=i.title)
e.add_field(name="Description", value=i.description)
e.add_field(name="Timestamp", value=i.timestamp)
for x in i.fields:
e.add_field(name=x.name, value=x.value)
e.add_field(name="Footer", value=i.footer)
with db.Session() as session:
messagelogchannel = session.query(Guildmessagelog).filter_by(id=message.guild.id).one_or_none()
if messagelogchannel is not None:
channel = message.guild.get_channel(messagelogchannel.messagelog_channel)
await channel.send(embed=e)
async def on_message_edit(self, before, after):
if after.edited_at is not None or before.edited_at is not None:
# There is a reason for this. That reason is that otherwise, an infinite spam loop occurs
e = discord.Embed(type='rich')
e.title = 'Message Edited'
e.color = 0xFF0000
e.add_field(name='Author', value=before.author)
e.add_field(name='Author pingable', value=before.author.mention)
e.add_field(name='Channel', value=before.channel)
if 1024 > len(before.content) > 0:
e.add_field(name="Old message", value=before.content)
elif len(before.content) != 0:
e.add_field(name="Old message", value=before.content[0:1023])
e.add_field(name="Old message continued", value=before.content[1024:2000])
elif len(before.content) == 0 and before.edited_at is not None:
for i in before.embeds:
e.add_field(name="Title", value=i.title)
e.add_field(name="Description", value=i.description)
e.add_field(name="Timestamp", value=i.timestamp)
for x in i.fields:
e.add_field(name=x.name, value=x.value)
e.add_field(name="Footer", value=i.footer)
if 0 < len(after.content) < 1024:
e.add_field(name="New message", value=after.content)
elif len(after.content) != 0:
e.add_field(name="New message", value=after.content[0:1023])
e.add_field(name="New message continued", value=after.content[1024:2000])
elif len(after.content) == 0 and after.edited_at is not None:
for i in after.embeds:
e.add_field(name="Title", value=i.title)
e.add_field(name="Description", value=i.description)
e.add_field(name="Timestamp", value=i.timestamp)
for x in i.fields:
e.add_field(name=x.name, value=x.value)
with db.Session() as session:
messagelogchannel = session.query(Guildmessagelog).filter_by(id=before.guild.id).one_or_none()
if messagelogchannel is not None:
channel = before.guild.get_channel(messagelogchannel.messagelog_channel)
await channel.send(embed=e)
@command(aliases=["purge"])
@has_permissions(manage_messages=True)
@bot_has_permissions(manage_messages=True, read_message_history=True)
async def prune(self, ctx, num_to_delete: int):
"""Bulk delete a set number of messages from the current channel."""
await ctx.message.channel.purge(limit=num_to_delete + 1)
await ctx.send("Deleted {n} messages under request of {user}".format(n=num_to_delete, user=ctx.message.author.mention), delete_after=5)
prune.example_usage = """
`{prefix}prune 10` - Delete the last 10 messages in the current channel.
"""
@command()
@has_permissions(kick_members=True)
@bot_has_permissions(manage_roles=True)
async def mute(self, ctx, member_mentions: discord.Member, *, reason="No reason provided"):
with db.Session() as session:
user = session.query(Guildmute).filter_by(id=member_mentions.id).one_or_none()
if user is not None:
await ctx.send("User is already muted!")
else:
user = Guildmute(id=member_mentions.id, guild=ctx.guild.id)
session.add(user)
await self.permoverride(member_mentions, send_messages=False, add_reactions=False)
await self.modlogger(ctx=ctx, action="muted", target=member_mentions, reason=reason)
@command()
@has_permissions(kick_members=True)
@bot_has_permissions(manage_roles=True)
async def unmute(self, ctx, member_mentions: discord.Member, reason="No reason provided"):
with db.Session() as session:
user = session.query(Guildmute).filter_by(id=member_mentions.id, guild=ctx.guild.id).one_or_none()
if user is not None:
session.delete(user)
await self.permoverride(member_mentions, send_messages=None, add_reactions=None)
await self.modlogger(ctx=ctx, action="unmuted", target=member_mentions, reason=reason)
else:
await ctx.send("User is not muted!")
@command()
@has_permissions(kick_members=True)
@bot_has_permissions(manage_roles=True)
async def deafen(self, ctx, member_mentions: discord.Member, *, reason="No reason provided"):
with db.Session() as session:
user = session.query(Deafen).filter_by(id=member_mentions.id).one_or_none()
if user is not None:
await ctx.send("User is already deafened!")
else:
user = Deafen(id=member_mentions.id, guild=ctx.guild.id, self_inflicted=False)
session.add(user)
await self.permoverride(member_mentions, read_messages=False)
await self.modlogger(ctx=ctx, action="deafened", target=member_mentions, reason=reason)
@command()
@bot_has_permissions(manage_roles=True)
async def selfdeafen(self, ctx, timing, *, reason="No reason provided"):
await ctx.send("Deafening {}...".format(ctx.author))
with db.Session() as session:
user = session.query(Deafen).filter_by(id=ctx.author.id).one_or_none()
if user is not None:
await ctx.send("You are already deafened!")
else:
user = Deafen(id=ctx.author.id, guild=ctx.guild.id, self_inflicted=True)
session.add(user)
await self.permoverride(user=ctx.author, read_messages=False)
await self.modlogger(ctx=ctx, action="deafened", target=ctx.author, reason=reason)
self.bot.loop.create_task(self.punishmenttimer(ctx, timing, ctx.author, lookup=Deafen))
selfdeafen.example_usage = """
``[prefix]selfdeafen time (1h5m, both optional) reason``: deafens you if you need to get work done
"""
@command()
@has_permissions(kick_members=True)
@bot_has_permissions(manage_roles=True)
async def undeafen(self, ctx, member_mentions: discord.Member, reason="No reason provided"):
with db.Session() as session:
user = session.query(Deafen).filter_by(id=member_mentions.id, guild=ctx.guild.id).one_or_none()
if user is not None:
await self.permoverride(user=member_mentions, read_messages=None)
session.delete(user)
if user.self_inflicted:
reason = "self deafen timer expired"
await self.modlogger(ctx=ctx, action="undeafened", target=member_mentions, reason=reason)
else:
await ctx.send("User is not deafened!")
class Guildmute(db.DatabaseObject):
__tablename__ = 'mutes'
id = db.Column(db.Integer, primary_key=True)
guild = db.Column(db.Integer, primary_key=True)
class Deafen(db.DatabaseObject):
__tablename__ = 'deafens'
id = db.Column(db.Integer, primary_key=True)
guild = db.Column(db.Integer, primary_key=True)
self_inflicted = db.Column(db.Boolean)
class Guildmodlog(db.DatabaseObject):
__tablename__ = 'modlogconfig'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
modlog_channel = db.Column(db.Integer, nullable=True)
class MemberRole(db.DatabaseObject):
__tablename__ = 'member_roles'
id = db.Column(db.Integer, primary_key=True)
member_role = db.Column(db.Integer, nullable=True)
class GuildNewMember(db.DatabaseObject):
__tablename__ = 'new_members'
guild_id = db.Column(db.Integer, primary_key=True)
channel_id = db.Column(db.Integer)
role_id = db.Column(db.Integer)
message = db.Column(db.String)
class Guildmemberlog(db.DatabaseObject):
__tablename__ = 'memberlogconfig'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
memberlog_channel = db.Column(db.Integer)
class Guildmessagelog(db.DatabaseObject):
__tablename__ = 'messagelogconfig'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
messagelog_channel = db.Column(db.Integer)
def setup(bot):
bot.add_cog(Moderation(bot))