From 2d175b4453152192a0815642b07e10ad5464ac60 Mon Sep 17 00:00:00 2001
From: Romain J <romain.ordi@gmail.com>
Date: Sun, 22 Sep 2019 04:18:28 +0200
Subject: [PATCH] update(cog|admin): add commands for warns

---
 cogs/admin.py                          | 208 +++++++++++++++++++++----
 cogs/utility.py                        |  35 ++---
 extras/locales/en/LC_MESSAGES/admin.mo | Bin 363 -> 449 bytes
 extras/locales/en/LC_MESSAGES/admin.po |  27 ++++
 extras/locales/fr/LC_MESSAGES/admin.mo | Bin 634 -> 1144 bytes
 extras/locales/fr/LC_MESSAGES/admin.po |  29 +++-
 6 files changed, 247 insertions(+), 52 deletions(-)

diff --git a/cogs/admin.py b/cogs/admin.py
index 2c57b91..4bbb35b 100644
--- a/cogs/admin.py
+++ b/cogs/admin.py
@@ -1,6 +1,8 @@
 import datetime
+import logging
 from typing import Union
 
+import asyncio
 import discord
 import humanize
 from discord.ext import commands
@@ -8,12 +10,13 @@ from discord.ext import commands
 from bot import TuxBot
 from .utils.lang import Texts
 
+log = logging.getLogger(__name__)
+
 
 class Admin(commands.Cog):
 
     def __init__(self, bot: TuxBot):
         self.bot = bot
-        self.db = bot.db
 
     async def cog_check(self, ctx: commands.Context) -> bool:
         permissions: discord.Permissions = ctx.channel.permissions_for(
@@ -97,7 +100,7 @@ class Admin(commands.Cog):
     """---------------------------------------------------------------------"""
 
     @commands.command(name='ban')
-    async def _ban(self, ctx: commands.Context, user: discord.User, *,
+    async def _ban(self, ctx: commands.Context, user: discord.Member, *,
                    reason=""):
         try:
             member: discord.Member = await ctx.guild.fetch_member(user.id)
@@ -122,7 +125,7 @@ class Admin(commands.Cog):
     """---------------------------------------------------------------------"""
 
     @commands.command(name='kick')
-    async def _kick(self, ctx: commands.Context, user: discord.User, *,
+    async def _kick(self, ctx: commands.Context, user: discord.Member, *,
                     reason=""):
         try:
             member: discord.Member = await ctx.guild.fetch_member(user.id)
@@ -221,48 +224,189 @@ class Admin(commands.Cog):
 
     """---------------------------------------------------------------------"""
 
+    async def get_warn(self, ctx: commands.Context,
+                       member: discord.Member = False):
+        query = """
+        SELECT * FROM warns 
+        WHERE created_at >= $1 AND server_id = $2 
+        """
+        query += """AND user_id = $3""" if member else ""
+        query += """ORDER BY created_at DESC"""
+        week_ago = datetime.datetime.now() - datetime.timedelta(weeks=6)
+
+        async with self.bot.db.acquire() as con:
+            await ctx.trigger_typing()
+            args = [week_ago, ctx.guild.id]
+            if member:
+                args.append(member.id)
+
+            warns = await con.fetch(query, *args)
+            warns_list = ''
+
+            for warn in warns:
+                row_id = warn.get('id')
+                user_id = warn.get('user_id')
+                user = await self.bot.fetch_user(user_id)
+                reason = warn.get('reason')
+                ago = humanize.naturaldelta(
+                    datetime.datetime.now() - warn.get('created_at')
+                )
+
+                warns_list += f"[{row_id}] **{user}**: `{reason}` " \
+                              f"*({ago} ago)*\n"
+
+        return warns_list, warns
+
     @commands.group(name='warn', aliases=['warns'])
     async def _warn(self, ctx: commands.Context):
         if ctx.invoked_subcommand is None:
-            query = """
-            SELECT user_id, reason, created_at FROM warns 
-            WHERE created_at >= $1 AND server_id = $2
-            ORDER BY created_at 
-            DESC LIMIT 10
-            """
-            week_ago = datetime.datetime.now() - datetime.timedelta(weeks=6)
+            warns_list, warns = await self.get_warn(ctx)
+            e = discord.Embed(
+                title=f"{len(warns)} {Texts('admin').get('last warns')}: ",
+                description=warns_list
+            )
 
-            async with self.bot.db.acquire() as con:
-                await ctx.trigger_typing()
-                warns = await con.fetch(query, week_ago, ctx.guild.id)
-                warns_list = ''
+            await ctx.send(embed=e)
 
-                for warn in warns:
-                    user_id = warn.get('user_id')
-                    user = await self.bot.fetch_user(user_id)
-                    reason = warn.get('reason')
-                    ago = humanize.naturaldelta(
-                        datetime.datetime.now() - warn.get('created_at')
-                    )
+    async def add_warn(self, ctx: commands.Context, member: discord.Member,
+                       reason):
 
-                    warns_list += f"**{user}**: `{reason}` *({ago} ago)*\n"
+        query = """
+        INSERT INTO warns (server_id, user_id, reason, created_at)
+        VALUES ($1, $2, $3, $4)
+        """
 
-                e = discord.Embed(
-                    title=f"{len(warns)} {Texts('admin').get('last warns')}: ",
-                    description=warns_list
-                )
-
-                await ctx.send(embed=e)
+        now = datetime.datetime.now()
+        await self.bot.db.execute(query, ctx.guild.id, member.id, reason, now)
 
     @_warn.command(name='add', aliases=['new'])
     async def _warn_new(self, ctx: commands.Context, member: discord.Member,
-                        *, reason):
+                        *, reason="N/A"):
+
+        member = await ctx.guild.fetch_member(member.id)
+        if not member:
+            return await ctx.send(
+                Texts('utils').get("Unable to find the user...")
+            )
+
+        query = """
+        SELECT user_id, reason, created_at FROM warns 
+        WHERE created_at >= $1 AND server_id = $2 and user_id = $3
         """
-        todo: push in database
-        if warn > 2 for member:
-            todo: ask for confirmation to kick or ban
+        week_ago = datetime.datetime.now() - datetime.timedelta(weeks=6)
+
+        def check(payload: discord.RawReactionActionEvent):
+            if payload.message_id != choice.id \
+                    or payload.user_id != ctx.author.id:
+                return False
+            return payload.emoji.name in ('1⃣', '2⃣', '3⃣')
+
+        async with self.bot.db.acquire() as con:
+            await ctx.trigger_typing()
+            warns = await con.fetch(query, week_ago, ctx.guild.id, member.id)
+
+            if len(warns) >= 2:
+                e = discord.Embed(
+                    title=Texts('admin').get('More than 2 warns'),
+                    description=f"{member.mention} "
+                                + Texts('admin').get('has more than 2 warns')
+                )
+                e.add_field(
+                    name='__Actions__',
+                    value=':one: kick\n'
+                          ':two: ban\n'
+                          ':three: ' + Texts('admin').get('ignore')
+                )
+
+                choice = await ctx.send(embed=e)
+
+                for reaction in ('1⃣', '2⃣', '3⃣'):
+                    await choice.add_reaction(reaction)
+
+                try:
+                    payload = await self.bot.wait_for(
+                        'raw_reaction_add',
+                        check=check,
+                        timeout=50.0
+                    )
+                except asyncio.TimeoutError:
+                    return await ctx.send(
+                        Texts('admin').get('Took too long. Aborting.')
+                    )
+                finally:
+                    await choice.delete()
+
+                if payload.emoji.name == '1⃣':
+                    from jishaku.models import copy_context_with
+
+                    alt_ctx = await copy_context_with(
+                        ctx,
+                        content=f"{ctx.prefix}"
+                                f"kick "
+                                f"{member} "
+                                f"{Texts('admin').get('More than 2 warns')}"
+                    )
+                    return await alt_ctx.command.invoke(alt_ctx)
+
+                elif payload.emoji.name == '2⃣':
+                    from jishaku.models import copy_context_with
+
+                    alt_ctx = await copy_context_with(
+                        ctx,
+                        content=f"{ctx.prefix}"
+                                f"ban "
+                                f"{member} "
+                                f"{Texts('admin').get('More than 2 warns')}"
+                    )
+                    return await alt_ctx.command.invoke(alt_ctx)
+
+            await self.add_warn(ctx, member, reason)
+            await ctx.send(
+                content=f"{member.mention} **{Texts('admin').get('got a warn')}**"
+                f"\n**{Texts('admin').get('Reason')}:** `{reason}`"
+                if reason != 'N/A' else ''
+            )
+
+    @_warn.command(name='remove', aliases=['revoke'])
+    async def _warn_remove(self, ctx: commands.Context, warn_id: int):
+        query = """
+        DELETE FROM warns 
+        WHERE id = $1
         """
 
+        async with self.bot.db.acquire() as con:
+            await ctx.trigger_typing()
+            await con.fetch(query, warn_id)
+
+        await ctx.send(f"{Texts('admin').get('Warn with id')} `{warn_id}`"
+                       f" {Texts('admin').get('successfully removed')}")
+
+    @_warn.command(name='show', aliases=['list'])
+    async def _warn_show(self, ctx: commands.Context, member: discord.Member):
+        warns_list, warns = await self.get_warn(ctx, member)
+        e = discord.Embed(
+            title=f"{len(warns)} {Texts('admin').get('last warns')}: ",
+            description=warns_list
+        )
+
+        await ctx.send(embed=e)
+
+    @_warn.command(name='edit', aliases=['change'])
+    async def _warn_edit(self, ctx: commands.Context, warn_id: int, *,
+                           reason):
+        query = """
+            UPDATE warns 
+            SET reason = $2 
+            WHERE id = $1
+            """
+
+        async with self.bot.db.acquire() as con:
+            await ctx.trigger_typing()
+            await con.fetch(query, warn_id, reason)
+
+        await ctx.send(f"{Texts('admin').get('Warn with id')} `{warn_id}`"
+                       f" {Texts('admin').get('successfully edited')}")
+
 
 def setup(bot: TuxBot):
     bot.add_cog(Admin(bot))
diff --git a/cogs/utility.py b/cogs/utility.py
index 13a0ecd..ecf6cb0 100644
--- a/cogs/utility.py
+++ b/cogs/utility.py
@@ -15,7 +15,22 @@ class Utility(commands.Cog):
 
     """---------------------------------------------------------------------"""
 
-    async def fetch_api(self, ctx: commands.Context, ip, addr):
+    @commands.command(name='iplocalise')
+    async def _iplocalise(self, ctx: commands.Context, addr, ip_type=''):
+        addr = re.sub(r'http(s?)://', '', addr)
+        addr = addr[:-1] if addr.endswith('/') else addr
+
+        await ctx.trigger_typing()
+
+        if ip_type in ('v6', 'ipv6'):
+            try:
+                ip = socket.getaddrinfo(addr, None, socket.AF_INET6)[1][4][0]
+            except socket.gaierror:
+                return await ctx.send(
+                    Texts('utility').get('ipv6 not available'))
+        else:
+            ip = socket.gethostbyname(addr)
+
         async with self.bot.session.get(f"http://ip-api.com/json/{ip}") as s:
             response: dict = await s.json()
 
@@ -55,24 +70,6 @@ class Utility(commands.Cog):
                     content=f"{Texts('utility').get('info not available')}"
                             f"``{response.get('query')}``")
 
-    @commands.command(name='iplocalise')
-    async def _iplocalise(self, ctx: commands.Context, addr, ip_type=''):
-        addr = re.sub(r'http(s?)://', '', addr)
-        addr = addr[:-1] if addr.endswith('/') else addr
-
-        await ctx.trigger_typing()
-
-        if ip_type in ('v6', 'ipv6'):
-            try:
-                ip = socket.getaddrinfo(addr, None, socket.AF_INET6)[1][4][0]
-            except socket.gaierror:
-                return await ctx.send(
-                    Texts('utility').get('ipv6 not available'))
-        else:
-            ip = socket.gethostbyname(addr)
-
-        await self.fetch_api(ctx, ip, addr)
-
 
 def setup(bot: TuxBot):
     bot.add_cog(Utility(bot))
diff --git a/extras/locales/en/LC_MESSAGES/admin.mo b/extras/locales/en/LC_MESSAGES/admin.mo
index e56e9c9c4f698196d1c9f23390bf6da85a3ddd45..cd65b5e0abbb1e0d9c33537cd85a4730136c80d2 100644
GIT binary patch
delta 140
zcmaFObdWjdo)F7a1|VPpVi_RT0dbIk4UjDg#I_*J$iNT`r1gP#B9P6Hkyxydn_rZw
wP?C|Dr(mQ|o>-Juyiv!V(EzKAjzW1xVu?aZzCvYwDNu1<i9$&}h-=RP0B5EikpKVy

delta 53
ocmX@e{F+JUo)F7a1|VPrVi_P-0dbIk4v=jNl+*>%8-wf_0lXdsf&c&j

diff --git a/extras/locales/en/LC_MESSAGES/admin.po b/extras/locales/en/LC_MESSAGES/admin.po
index 46df0e2..3bec23b 100644
--- a/extras/locales/en/LC_MESSAGES/admin.po
+++ b/extras/locales/en/LC_MESSAGES/admin.po
@@ -25,4 +25,31 @@ msgid "Unable to kick this user"
 msgstr ""
 
 msgid "last warns"
+msgstr ""
+
+msgid "More than 2 warns"
+msgstr ""
+
+msgid "has more than 2 warns"
+msgstr "has more than 2 warns, what do you want to do ?"
+
+msgid "ignore"
+msgstr ""
+
+msgid "Took too long. Aborting."
+msgstr ""
+
+msgid "got a warn"
+msgstr ""
+
+msgid "Reason"
+msgstr ""
+
+msgid "Warn with id"
+msgstr ""
+
+msgid "successfully removed"
+msgstr ""
+
+msgid "successfully edited"
 msgstr ""
\ No newline at end of file
diff --git a/extras/locales/fr/LC_MESSAGES/admin.mo b/extras/locales/fr/LC_MESSAGES/admin.mo
index cdfee52546358357d655671a8242b05104a7cfd9..f08390cf07ba5261daf655473466283fca31f7e5 100644
GIT binary patch
delta 677
zcmZ{fJxe4(5QckoQISLO`|FOYK+!-AHV_lh3(iPk5y8X*+nsWEu(x}i?wM7S1us2>
zWAPtcvRq_hBA9GuZmPe){16ZB?VAl;#fEpDuIj0(x1O?pdTRHB*$aWw2YrNo_~6BP
z#(e<#3ynbi86mpB8E_n&2gks#t@s=G0dWHMg5N=3cMkfxU#<Laa7c)nxWZ)^54Ye7
z^Z@zBpE`w@0GB{tm<N4+2b=~g;0$;H`v0y$Z{!ZlfsbG}NZB+$2TmbgYjGD8o9iZG
zawVxLM^zbVEAon^Wn%3(B1sV<qs3N=ZDS6}8AHnG;v#+7H<1f*`+1ft?Vy~hp({}s
zh@x>Q_8mn@S}MO86BadcLP_gdgKb<Wu(b!VQYXYg=r|B<xroaq;o$OSH8Y-9v890F
zS|$t+)^Zt^ZT&ej^?6H%-t#t>E0*}U5=PC2H!F2uv|X%^c5EDO%(fN!HU(0%u<my*
z#1!lH*2q`h89Gl#F;ik<#it8^r2`p8yeiTq>w6uO%aWQ6*KFX6&A|Wj()G|eePgXS
HdG+=Wc?qyE

delta 192
zcmeyt@rxzro)F7a1|VPsVi_QI0dbH(4v;Mh#JNB$4#dqsECIx`ftU-3w*av%BLl;J
zAT0^Re}QZjAT7?sz@Q4GZGbdLUlNd>%*QA`*_F|dD<`qIM4>#fD6e?)Jw|&*N#E3>
z<V=MW_0+tQqSPXV(!5lKqQuPN{JhEe%svt+sYQ92sYS&KiDjupC7H#=skuOf#S8#4
CH6~dA

diff --git a/extras/locales/fr/LC_MESSAGES/admin.po b/extras/locales/fr/LC_MESSAGES/admin.po
index e3601b4..00278ef 100644
--- a/extras/locales/fr/LC_MESSAGES/admin.po
+++ b/extras/locales/fr/LC_MESSAGES/admin.po
@@ -25,4 +25,31 @@ msgid "Unable to kick this user"
 msgstr "Impossible d'expulser cet utilisateur"
 
 msgid "last warns"
-msgstr "derniers avertissements"
\ No newline at end of file
+msgstr "derniers avertissements"
+
+msgid "More than 2 warns"
+msgstr "Plus de 2 avertissements"
+
+msgid "has more than 2 warns"
+msgstr "a plus de 2 avertissements, que voulez-vous faire?"
+
+msgid "ignore"
+msgstr "ignorer"
+
+msgid "Took too long. Aborting."
+msgstr "Temps expiré. Abandons."
+
+msgid "got a warn"
+msgstr "a recu un avertissement"
+
+msgid "Reason"
+msgstr "Raison"
+
+msgid "Warn with id"
+msgstr "L'avertissement avec l'id"
+
+msgid "successfully removed"
+msgstr "a été enlevé avec succes"
+
+msgid "successfully edited"
+msgstr "a été édité avec succes"
\ No newline at end of file