You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

434 lines
19KB

  1. # -*- coding: utf-8 -*-
  2. # MIT License
  3. #
  4. # Copyright (c) 2017-2018 Roxanne Gibson
  5. #
  6. # Permission is hereby granted, free of charge, to any person obtaining a copy
  7. # of this software and associated documentation files (the "Software"), to deal
  8. # in the Software without restriction, including without limitation the rights
  9. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. # copies of the Software, and to permit persons to whom the Software is
  11. # furnished to do so, subject to the following conditions:
  12. #
  13. # The above copyright notice and this permission notice shall be included in all
  14. # copies or substantial portions of the Software.
  15. #
  16. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  22. # SOFTWARE.
  23. import datetime
  24. import discord
  25. from discord.ext import commands
  26. import roxbot
  27. from roxbot.db import *
  28. class AdminSingle(db.Entity):
  29. warning_limit = Required(int, default=0)
  30. guild_id = Required(int, unique=True, size=64)
  31. class AdminWarnings(db.Entity):
  32. user_id = Required(int, size=64)
  33. warned_by = Required(int, size=64)
  34. warning = Optional(str)
  35. date = Required(datetime.datetime, unique=True)
  36. guild_id = Required(int, size=64)
  37. class Admin(commands.Cog):
  38. """
  39. Admin Commands for those admins
  40. """
  41. OK_SLOWMODE_ON = "Slowmode on :snail: ({} seconds)"
  42. OK_SLOWMODE_OFF = "Slowmode off"
  43. OK_SLOWMODE_CHANGED = "Slowmode set to :snail: ({} seconds)"
  44. ERROR_SLOWMODE_SECONDS = "Rate limit has to be between 0-120."
  45. OK_PURGE_CONFIRMATION = "{} message(s) purged from chat."
  46. OK_WARN_ADD = "Reported {}."
  47. OK_WARN_ADD_USER_LIMIT_DM = "{} has been reported {} time(s). This is a reminder that this is over the set limit of {}."
  48. WARN_WARN_ADD_LIMIT_REACHED = "You can only warn a user {} times!"
  49. OK_WARN_LIST_NO_WARNINGS = "No warnings on record."
  50. OK_WARN_LIST_USER_NO_WARNINGS = "This user doesn't have any warning on record."
  51. OK_WARN_REMOVE_REMOVED_WARNING = "Removed Warning {} from {}"
  52. OK_WARN_REMOVE_REMOVED_WARNINGS = "Removed all warnings for {}"
  53. WARN_WARN_REMOVE_USER_NOT_FOUND = "Could not find user {} in warning list."
  54. ERROR_WARN_REMOVE_INDEXERROR = "Index Error: Warning index doesn't exist. User only has {} warning(s)."
  55. ERROR_WARN_REMOVE_VALUEERROR = "Value Error: Please enter a valid index number."
  56. OK_WARN_PRUNE_PRUNED = "Pruned {} banned users from the warn list."
  57. OK_WARN_SL_SET = "Number of warnings needed to DM's set to {}"
  58. OK_WARN_SL_SET_ZERO = "DM's to mods for warning limits disabled."
  59. ERROR_WARN_SL_NEG = "number_of_warnings can only be a positive integer."
  60. OK_MOD_ACTION = "{} with reason: '{}'"
  61. WARN_MOD_LACK_PERMS = "Cannot kick owner or users higher or equal to me role hierarchy."
  62. WARN_UNBAN_NOTFOUND = "User is not banned."
  63. def __init__(self, bot_client):
  64. self.bot = bot_client
  65. self.autogen_db = AdminSingle
  66. @commands.guild_only()
  67. @commands.has_permissions(manage_messages=True)
  68. @commands.bot_has_permissions(manage_messages=True)
  69. @commands.command()
  70. async def slowmode(self, ctx, seconds: int):
  71. """Puts the channel in slowmode. Users with manage_channel or manage_messages permissions will not be effected.
  72. Options:
  73. - `seconds` - Has to be between 0 - 120. This will set the timeout a user receives once they send a message in this channel. If 0, Roxbot will disable slowmode.
  74. Examples:
  75. # Set slowmode to 30 seconds
  76. ;slowmode 30
  77. # Turn slowmode off
  78. ;slowmode 0
  79. """
  80. if seconds == 0: # Turn Slow Mode off
  81. await ctx.channel.edit(slowmode_delay=seconds, reason="{} requested to turn off slowmode.".format(ctx.author))
  82. embed = discord.Embed(description=self.OK_SLOWMODE_OFF, colour=roxbot.EmbedColours.pink)
  83. return await ctx.send(embed=embed)
  84. elif 0 < seconds <= 120 and ctx.channel.slowmode_delay == 0: # Turn Slow Mode On
  85. await ctx.channel.edit(slowmode_delay=seconds, reason="{} requested slowmode with a timer of {}".format(ctx.author, seconds))
  86. embed = discord.Embed(description=self.OK_SLOWMODE_ON.format(seconds), colour=roxbot.EmbedColours.pink)
  87. return await ctx.send(embed=embed)
  88. elif 0 < seconds <= 120 and ctx.channel.slowmode_delay > 0: # Change value of Slow Mode timer
  89. await ctx.channel.edit(slowmode_delay=seconds, reason="{} requested slowmode timer be changed to {}".format(ctx.author, seconds))
  90. embed = discord.Embed(description=self.OK_SLOWMODE_CHANGED.format(seconds), colour=roxbot.EmbedColours.pink)
  91. return await ctx.send(embed=embed)
  92. elif seconds < 0 or seconds > 120:
  93. raise commands.BadArgument(self.ERROR_SLOWMODE_SECONDS)
  94. @commands.guild_only()
  95. @commands.has_permissions(manage_messages=True)
  96. @commands.bot_has_permissions(manage_messages=True, read_message_history=True)
  97. @commands.cooldown(1, 5)
  98. @commands.command()
  99. async def purge(self, ctx, limit=0, *, author: roxbot.converters.User = None):
  100. """Purges the text channel the command is execture in. You can specify a certain user to purge as well.
  101. Options:
  102. - `limit` - This the the amount of messages Roxbot will take from the chat and purge. Note: This **does not** mean the amount that will be purged. Limit is the amount of messages Roxbot will look at. If a user is given, it will only delete messages from that user in that list of messages.
  103. - `USER` - A name, ID, or mention of a user. If the user has left the guild, this **has** to be the ID.
  104. Examples:
  105. # Delete 20 messages from the chat
  106. ;purge 20
  107. # Take 20 messages, and delete any message in it by user @Spammer
  108. ;purge 20 @Spammer
  109. """
  110. # TODO: To sort out the limit == how many to delete for the author, and not just a limit.
  111. if author:
  112. predicate = lambda message: message.author.id == author.id and message.id != ctx.message.id
  113. else:
  114. predicate = lambda message: message.id != ctx.message.id
  115. messages = await ctx.channel.purge(limit=limit, check=predicate)
  116. embed = discord.Embed(description=self.OK_PURGE_CONFIRMATION.format(len(messages)), colour=roxbot.EmbedColours.pink)
  117. return await ctx.send(embed=embed)
  118. @commands.guild_only()
  119. @commands.has_permissions(kick_members=True)
  120. @commands.group(case_insensitive=True)
  121. async def warn(self, ctx):
  122. """The warn command group allows Discord moderators to warn users and log them within the bot.
  123. The command group also supports setting limits to warn mods if a user has been warned over a certain threshold.
  124. """
  125. if ctx.invoked_subcommand is None:
  126. raise commands.CommandNotFound("Subcommand '{}' does not exist.".format(ctx.subcommand_passed))
  127. @warn.command()
  128. async def add(self, ctx, user: discord.User = None, *, warning=""):
  129. """Adds a warning to a user.
  130. Options:
  131. - `USER` - A name, ID, or mention of a user.
  132. - `warning` - OPTIONAL. A reason for the warning. This supports markdown formatting.
  133. Example:
  134. # Add warning to user @Roxbot for being a meanie
  135. ;warn add @Roxbot "for being a meanie"
  136. """
  137. # Warning in the settings is a dictionary of user ids. The user ids are equal to a list of dictionaries.
  138. with db_session:
  139. warning_limit = AdminSingle.get(guild_id=ctx.guild.id).warning_limit
  140. user_warnings = select(w for w in AdminWarnings if w.user_id == user.id and w.guild_id == ctx.guild.id)[:]
  141. amount_warnings = len(user_warnings)
  142. warn_limit = 10
  143. if amount_warnings > warn_limit:
  144. embed = discord.Embed(description=self.WARN_WARN_ADD_LIMIT_REACHED.format(warn_limit), colour=roxbot.EmbedColours.red)
  145. return await ctx.send(embed=embed)
  146. with db_session:
  147. AdminWarnings(user_id=user.id, warned_by=ctx.author.id, date=datetime.datetime.utcnow(), warning=warning, guild_id=ctx.guild.id)
  148. if amount_warnings >= warning_limit > 0:
  149. await ctx.author.send(self.OK_WARN_ADD_USER_LIMIT_DM.format(str(user), amount_warnings, warning_limit))
  150. embed = discord.Embed(description=self.OK_WARN_ADD.format(str(user)), colour=roxbot.EmbedColours.pink)
  151. return await ctx.send(embed=embed)
  152. @warn.command()
  153. async def list(self, ctx, *, user: roxbot.converters.User = None):
  154. """Lists all warning in this guild or all the warnings for one user.
  155. Options:
  156. - `USER` - OPTIONAL. A name, ID, or mention of a user.
  157. Examples:
  158. # List all warnings in the guild
  159. ;warn list
  160. # List all warnings for @Roxbot
  161. ;warn list @Roxbot
  162. """
  163. if user is None:
  164. paginator = commands.Paginator()
  165. warnings = {}
  166. with db_session:
  167. for warning in select(warn for warn in AdminWarnings if warn.guild_id == ctx.guild.id)[:]:
  168. if warning.user_id not in warnings:
  169. warnings[warning.user_id] = []
  170. else:
  171. warnings[warning.user_id].append(warning)
  172. for u, warning in warnings.items():
  173. member_obj = discord.utils.get(ctx.guild.members, id=u)
  174. if member_obj:
  175. paginator.add_line("{}: {} Warning(s)".format(str(member_obj), len(warnings[u])))
  176. else:
  177. paginator.add_line("{}: {} Warning(s)".format(u, len(warnings[u])))
  178. # This is in case we have removed some users from the list.
  179. if not paginator.pages:
  180. embed = discord.Embed(description=self.OK_WARN_LIST_NO_WARNINGS, colour=roxbot.EmbedColours.orange)
  181. return await ctx.send(embed=embed)
  182. for page in paginator.pages:
  183. return await ctx.send(page)
  184. else:
  185. with db_session:
  186. user_warnings = select(w for w in AdminWarnings if w.user_id == user.id and w.guild_id == ctx.guild.id).order_by(AdminWarnings.date)[:]
  187. if not user_warnings:
  188. embed = discord.Embed(description=self.OK_WARN_LIST_USER_NO_WARNINGS, colour=roxbot.EmbedColours.orange)
  189. return await ctx.send(embed=embed)
  190. em = discord.Embed(title="Warnings for {}".format(str(user)), colour=roxbot.EmbedColours.pink)
  191. em.set_thumbnail(url=user.avatar_url)
  192. x = 1
  193. for warning in user_warnings:
  194. try:
  195. warned_by = str(ctx.guild.get_member(warning.warned_by))
  196. if warned_by is None:
  197. warned_by = str(await self.bot.get_user_info(warning.warned_by))
  198. except discord.ext.commands.CommandInvokeError:
  199. warned_by = warning.warned_by
  200. date = datetime.datetime.strftime(warning.date, roxbot.datetime.strip("{:} UTC")+" UTC")
  201. em.add_field(name="Warning %s" % x, value="Warned by: {}\nTime: {}\nReason: {}".format(warned_by, date, warning.warning))
  202. x += 1
  203. return await ctx.send(embed=em)
  204. @warn.command()
  205. async def remove(self, ctx, user: roxbot.converters.User, index=None):
  206. """Removes one or all of the warnings for a user.
  207. Options:=
  208. - `USER` - A name, ID, or mention of a user.
  209. - `index` - OPTIONAL. The index of the single warning you want to remove.
  210. Examples:
  211. # Remove all warnings for Roxbot
  212. ;warn remove Roxbot
  213. # Remove warning 2 for Roxbot
  214. ;warn remove Roxbot 2
  215. """
  216. with db_session:
  217. if index:
  218. try:
  219. index = int(index) - 1
  220. query = select(w for w in AdminWarnings if w.user_id == user.id and w.guild_id == ctx.guild.id)
  221. if query:
  222. user_warnings = query[:]
  223. else:
  224. raise KeyError
  225. user_warnings[index].delete()
  226. embed = discord.Embed(description=self.OK_WARN_REMOVE_REMOVED_WARNING.format(index+1, str(user)), colour=roxbot.EmbedColours.pink)
  227. return await ctx.send(embed=embed)
  228. except Exception as e:
  229. embed = discord.Embed(colour=roxbot.EmbedColours.red)
  230. if isinstance(e, IndexError):
  231. embed.description = self.ERROR_WARN_REMOVE_INDEXERROR.format(len(user_warnings))
  232. elif isinstance(e, KeyError):
  233. embed.description = self.WARN_WARN_REMOVE_USER_NOT_FOUND.format(str(user))
  234. elif isinstance(e, ValueError):
  235. embed.description = self.ERROR_WARN_REMOVE_VALUEERROR
  236. else:
  237. raise e
  238. return await ctx.send(embed=embed)
  239. else:
  240. query = select(w for w in AdminWarnings if w.user_id == user.id and w.guild_id == ctx.guild.id)
  241. if query.exists():
  242. delete(w for w in AdminWarnings if w.user_id == user.id and w.guild_id == ctx.guild.id)
  243. embed = discord.Embed(description=self.OK_WARN_REMOVE_REMOVED_WARNINGS.format(str(user)), colour=roxbot.EmbedColours.pink)
  244. return await ctx.send(embed=embed)
  245. else:
  246. embed = discord.Embed(description=self.WARN_WARN_REMOVE_USER_NOT_FOUND.format(str(user)), colour=roxbot.EmbedColours.red)
  247. return await ctx.send(embed=embed)
  248. @commands.bot_has_permissions(ban_members=True)
  249. @warn.command()
  250. async def prune(self, ctx, dry_run=0):
  251. """Prunes the warnings of any banned users.
  252. You can add a 1 at the end to dryrun the command. This will show you how many would be deleted without deleting them.
  253. Options:
  254. - `dryrun` - Add `1` to the end of the command to do a dryrun of the prune command.
  255. Examples:
  256. # Prune the warnings of banned users in this guild
  257. ;warn prune
  258. # Dryrun the prune command to see how many warnings would be removed
  259. ;warn prune 1
  260. """
  261. x = 0
  262. for ban in await ctx.guild.bans():
  263. with db_session:
  264. query = select(w for w in AdminWarnings if w.user_id == ban.user.id and w.guild_id == ctx.guild.id)
  265. if query.exists():
  266. if dry_run == 0:
  267. query.delete()
  268. x += 1
  269. embed = discord.Embed(description=self.OK_WARN_PRUNE_PRUNED.format(x), colour=roxbot.EmbedColours.pink)
  270. return await ctx.send(embed=embed)
  271. @warn.command(aliases=["sl", "setlimit"])
  272. async def set_limit(self, ctx, number_of_warnings: int):
  273. """
  274. Sets the limit for how many warnings a user can get before mod's are notified.
  275. Example: if 3 is set, on the third warning, mods will be DM'd. If this is set to 0, DM's will be disabled.
  276. """
  277. if number_of_warnings < 0:
  278. raise commands.BadArgument(self.ERROR_WARN_SL_NEG)
  279. with db_session:
  280. guild_settings = AdminSingle.get(guild_id=ctx.guild.id)
  281. guild_settings.warning_limit = number_of_warnings
  282. if number_of_warnings == 0:
  283. embed = discord.Embed(description=self.OK_WARN_SL_SET_ZERO, colour=roxbot.EmbedColours.pink)
  284. return await ctx.send(embed=embed)
  285. else:
  286. embed = discord.Embed(description=self.OK_WARN_SL_SET.format(number_of_warnings), colour=roxbot.EmbedColours.pink)
  287. return await ctx.send(embed=embed)
  288. @commands.guild_only()
  289. @commands.has_permissions(kick_members=True)
  290. @commands.bot_has_permissions(kick_members=True)
  291. @commands.command()
  292. async def kick(self, ctx, member: discord.Member, *, reason=""):
  293. """Kicks the mentioned user with the ability to give a reason.
  294. Requires the Kick Members permission.
  295. Options:
  296. - `USER` - A name, ID, or mention of a user.
  297. - `reason` - OPTIONAL. A short reason for the kicking.
  298. Examples:
  299. # Kick user BadUser
  300. ;kick @BadUser
  301. # Kick user Roxbot for being a meanie
  302. ;kick Roxbot "for being a meanie"
  303. """
  304. try:
  305. await member.kick(reason=reason)
  306. embed = discord.Embed(description=self.OK_MOD_ACTION.format("Kicked", member, reason), colour=roxbot.EmbedColours.pink)
  307. return await ctx.send(embed=embed)
  308. except discord.Forbidden:
  309. embed = discord.Embed(description=self.WARN_MOD_LACK_PERMS, colour=roxbot.EmbedColours.red)
  310. return await ctx.send(embed=embed)
  311. @commands.guild_only()
  312. @commands.has_permissions(ban_members=True)
  313. @commands.bot_has_permissions(ban_members=True)
  314. @commands.command()
  315. async def ban(self, ctx, member: discord.Member, *, reason=""):
  316. """Bans the mentioned user with the ability to give a reason.
  317. Requires the Ban Members permission.
  318. Options:
  319. - `USER` - A name, ID, or mention of a user.
  320. - `reason` - OPTIONAL. A short reason for the banning.
  321. Examples:
  322. # Ban user BadUser
  323. ;ban @BadUser
  324. # Ban user Roxbot for being a meanie
  325. ;ban Roxbot "for being a meanie"
  326. """
  327. try:
  328. await member.ban(reason=reason, delete_message_days=0)
  329. embed = discord.Embed(description=self.OK_MOD_ACTION.format("Banned", member, reason), colour=roxbot.EmbedColours.pink)
  330. return await ctx.send(embed=embed)
  331. except discord.Forbidden:
  332. embed = discord.Embed(description=self.WARN_MOD_LACK_PERMS, colour=roxbot.EmbedColours.red)
  333. return await ctx.send(embed=embed)
  334. @commands.guild_only()
  335. @commands.has_permissions(ban_members=True)
  336. @commands.bot_has_permissions(ban_members=True)
  337. @commands.command()
  338. async def unban(self, ctx, member: roxbot.converters.User, *, reason=""):
  339. """Unbans the mentioned user with the ability to give a reason.
  340. Requires the Ban Members permission.
  341. Options:
  342. - `user_id` - The ID of a banned user.
  343. - `reason` - OPTIONAL. A short reason for the unbanning.
  344. Examples:
  345. # Unban user with ID 478294672394
  346. ;unban 478294672394
  347. """
  348. ban = await ctx.guild.get_ban(member)
  349. mem = ban.user
  350. if mem is None:
  351. embed = discord.Embed(description=self.WARN_UNBAN_NOTFOUND, colour=roxbot.EmbedColours.red)
  352. return await ctx.send(embed=embed)
  353. try:
  354. await ctx.guild.unban(mem, reason=reason)
  355. embed = discord.Embed(description=self.OK_MOD_ACTION.format("Unbanned", mem, reason), colour=roxbot.EmbedColours.pink)
  356. return await ctx.send(embed=embed)
  357. except discord.Forbidden:
  358. embed = discord.Embed(description=self.WARN_MOD_LACK_PERMS, colour=roxbot.EmbedColours.red)
  359. return await ctx.send(embed=embed)
  360. def setup(bot_client):
  361. bot_client.add_cog(Admin(bot_client))