refactor(command|sondage): continue rewrite of sondage

known issues: datas are not commited in database on reaction on
This commit is contained in:
Romain J 2019-10-06 01:49:30 +02:00
parent d5f1f71a0a
commit 76e845e5be
18 changed files with 313 additions and 101 deletions

3
.gitignore vendored
View file

@ -3,7 +3,6 @@ __pycache__/
*.pyc
.env
config.py
!cogs/utils/*
.DS_Store
private.py
@ -11,4 +10,4 @@ private.py
.idea/
#other
logs/*
*.log

View file

@ -6,12 +6,12 @@
- [ ] Alias system for commands (e.g. `.alias .ci show .cs`)
- [ ] Migrate MySQL to postgresql
- [x] Prepare bot for python 3.8 and discord.py 1.3.0
- [x] Create launcher
- [ ] Create launcher
- [ ] Create documentation
## Launcher requirements :
- [x] Can install the bot
- [ ] Can install the bot
- [x] Can launch the bot
- [x] Can propose updates
@ -82,5 +82,5 @@
---
# Cogs.sondage commands `(renamed as cogs.poll)`
# Cogs.sondage commands `(renamed as cogs.poll)` `canceled until the frontend development`
- [ ] sondage (help?)

13
bot.py
View file

@ -26,6 +26,7 @@ l_extensions = (
'cogs.basics',
'cogs.utility',
'cogs.logs',
'cogs.poll',
'jishaku',
)
@ -127,11 +128,13 @@ class TuxBot(commands.AutoShardedBot):
@property
def logs_webhook(self) -> discord.Webhook:
logs_webhook = self.config.logs_webhook
webhook = discord.Webhook.partial(id=logs_webhook.get('id'),
token=logs_webhook.get('token'),
adapter=discord.AsyncWebhookAdapter(
self.session)
)
webhook = discord.Webhook.partial(
id=logs_webhook.get('id'),
token=logs_webhook.get('token'),
adapter=discord.AsyncWebhookAdapter(
self.session
)
)
return webhook

View file

@ -9,9 +9,8 @@ import humanize
from discord.ext import commands
from bot import TuxBot
from .utils.models.lang import Lang
from .utils.lang import Texts
from .utils.models.warn import Warn
from .utils.models import Warn, Lang
log = logging.getLogger(__name__)
@ -87,8 +86,9 @@ class Admin(commands.Cog):
message_id)
await message.edit(content=content)
except (discord.errors.NotFound, discord.errors.Forbidden):
await ctx.send(Texts('utils', ctx).get("Unable to find the message"),
delete_after=5)
await ctx.send(
Texts('utils', ctx).get("Unable to find the message"),
delete_after=5)
@_say.command(name='to')
async def _say_to(self, ctx: commands.Context,
@ -120,11 +120,13 @@ class Admin(commands.Cog):
await ctx.send(embed=e)
except discord.Forbidden:
await ctx.send(Texts('admin', ctx).get("Unable to ban this user"),
delete_after=5)
await ctx.send(
Texts('admin', ctx).get("Unable to ban this user"),
delete_after=5)
except discord.errors.NotFound:
await ctx.send(Texts('utils', ctx).get("Unable to find the user..."),
delete_after=5)
await ctx.send(
Texts('utils', ctx).get("Unable to find the user..."),
delete_after=5)
"""---------------------------------------------------------------------"""
@ -145,11 +147,13 @@ class Admin(commands.Cog):
await ctx.send(embed=e)
except discord.Forbidden:
await ctx.send(Texts('admin', ctx).get("Unable to kick this user"),
delete_after=5)
await ctx.send(
Texts('admin', ctx).get("Unable to kick this user"),
delete_after=5)
except discord.errors.NotFound:
await ctx.send(Texts('utils', ctx).get("Unable to find the user..."),
delete_after=5)
await ctx.send(
Texts('utils', ctx).get("Unable to find the user..."),
delete_after=5)
"""---------------------------------------------------------------------"""
@ -180,8 +184,9 @@ class Admin(commands.Cog):
for emoji in emojis:
await message.add_reaction(emoji)
except discord.errors.NotFound:
await ctx.send(Texts('utils', ctx).get("Unable to find the message"),
delete_after=5)
await ctx.send(
Texts('utils', ctx).get("Unable to find the message"),
delete_after=5)
@_react.command(name='clear')
async def _react_remove(self, ctx: commands.Context, message_id: int):
@ -190,8 +195,9 @@ class Admin(commands.Cog):
message_id)
await message.clear_reactions()
except discord.errors.NotFound:
await ctx.send(Texts('utils', ctx).get("Unable to find the message"),
delete_after=5)
await ctx.send(
Texts('utils', ctx).get("Unable to find the message"),
delete_after=5)
"""---------------------------------------------------------------------"""
@ -207,8 +213,9 @@ class Admin(commands.Cog):
message_id)
await message.delete()
except (discord.errors.NotFound, discord.errors.Forbidden):
await ctx.send(Texts('utils', ctx).get("Unable to find the message"),
delete_after=5)
await ctx.send(
Texts('utils', ctx).get("Unable to find the message"),
delete_after=5)
@_delete.command(name='from', aliases=['to', 'in'])
async def _delete_from(self, ctx: commands.Context,
@ -223,8 +230,9 @@ class Admin(commands.Cog):
message_id)
await message.delete()
except (discord.errors.NotFound, discord.errors.Forbidden):
await ctx.send(Texts('utils', ctx).get("Unable to find the message"),
delete_after=5)
await ctx.send(
Texts('utils', ctx).get("Unable to find the message"),
delete_after=5)
"""---------------------------------------------------------------------"""
@ -394,17 +402,18 @@ class Admin(commands.Cog):
@commands.command(name='language', aliases=['lang', 'langue', 'langage'])
async def _language(self, ctx: commands.Context, locale: str):
available = self.bot.engine\
.query(Lang.value)\
.filter(Lang.key == 'available')\
.one()[0]\
available = self.bot.engine \
.query(Lang.value) \
.filter(Lang.key == 'available') \
.one()[0] \
.split(', ')
if locale.lower() not in available:
await ctx.send(Texts('admin', ctx).get('Unable to find this language'))
await ctx.send(
Texts('admin', ctx).get('Unable to find this language'))
else:
current = self.bot.engine\
.query(Lang)\
current = self.bot.engine \
.query(Lang) \
.filter(Lang.key == str(ctx.guild.id))
if current.count() > 0:
@ -416,7 +425,8 @@ class Admin(commands.Cog):
self.bot.engine.add(new_row)
self.bot.engine.commit()
await ctx.send(Texts('admin', ctx).get('Language changed successfully'))
await ctx.send(
Texts('admin', ctx).get('Language changed successfully'))
def setup(bot: TuxBot):

View file

@ -55,7 +55,8 @@ class Basics(commands.Cog):
file_amount += 1
with open(file_dir, "r", encoding="utf-8") as file:
for line in file:
if not line.strip().startswith("#") or not line.strip():
if not line.strip().startswith("#") \
or not line.strip():
total += 1
return total, file_amount
@ -68,7 +69,7 @@ class Basics(commands.Cog):
with proc.oneshot():
mem = proc.memory_full_info()
e = discord.Embed(
title=f"{Texts('basics', ctx).get('Information about TuxBot')}",
title=Texts('basics', ctx).get('Information about TuxBot'),
color=0x89C4F9)
e.add_field(
@ -129,7 +130,7 @@ class Basics(commands.Cog):
name=f"__:link: {Texts('basics', ctx).get('Links')}__",
value="[tuxbot.gnous.eu](https://tuxbot.gnous.eu/) "
"| [gnous.eu](https://gnous.eu/) "
f"| [{Texts('basics').get('Invite')}](https://discordapp.com/oauth2/authorize?client_id=301062143942590465&scope=bot&permissions=268749888)",
f"| [{Texts('basics', ctx).get('Invite')}](https://discordapp.com/oauth2/authorize?client_id=301062143942590465&scope=bot&permissions=268749888)",
inline=False
)

View file

@ -1,22 +1,133 @@
from typing import Union
import discord
import bcrypt
from discord.ext import commands
from bot import TuxBot
from .utils.lang import Texts
from .utils.models import Poll
from .utils import emotes as utils_emotes
class Poll(commands.Cog):
class Polls(commands.Cog):
def __init__(self, bot: TuxBot):
self.bot = bot
def get_poll(self, pld) -> Union[bool, Poll]:
if pld.user_id != self.bot.user.id:
poll = self.bot.engine \
.query(Poll) \
.filter(Poll.message_id == pld.message_id) \
.one_or_none()
if poll is not None:
emotes = utils_emotes.get(len(poll.responses))
if pld.emoji.name in emotes:
return poll
return False
async def remove_reaction(self, pld):
channel: discord.TextChannel = self.bot.get_channel(
pld.channel_id
)
message: discord.Message = await channel.fetch_message(
pld.message_id
)
user: discord.User = await self.bot.fetch_user(pld.user_id)
await message.remove_reaction(pld.emoji.name, user)
@commands.Cog.listener()
async def on_raw_reaction_add(self, pld: discord.RawReactionActionEvent):
poll = self.get_poll(pld)
if poll:
if poll.is_anonymous:
await self.remove_reaction(pld)
user_id = str(pld.user_id).encode()
responses = poll.responses
choice = utils_emotes.get_index(pld.emoji.name) + 1
responders = responses.get(str(choice))
if not responders:
print(responders, 'before0')
user_id_hash = bcrypt.hashpw(user_id, bcrypt.gensalt())
responders.append(user_id_hash)
print(responders, 'after0')
else:
for i, responder in enumerate(responders):
if bcrypt.checkpw(user_id, responder.encode()):
print(responders, 'before1')
responders.pop(i)
print(responders, 'after1')
else:
print(responders, 'before2')
user_id_hash = bcrypt.hashpw(user_id, bcrypt.gensalt())
responders.append(user_id_hash)
print(responders, 'after2')
poll.responses = responses
print(poll.responses)
self.bot.engine.commit()
return 1
"""---------------------------------------------------------------------"""
async def make_poll(self, ctx: commands.Context, poll: str, anonymous):
question = (poll.split('|')[0]).strip()
responses = [response.strip() for response in poll.split('|')[1:]]
responses_row = {}
emotes = utils_emotes.get(len(responses))
stmt = await ctx.send(Texts('poll', ctx).get('**Preparation...**'))
poll_row = Poll()
self.bot.engine.add(poll_row)
self.bot.engine.flush()
e = discord.Embed(description=f"**{question}**")
e.set_author(
name=ctx.author,
icon_url='https://cdn.pixabay.com/photo/2017/05/15/23/48/survey-2316468_960_720.png'
)
for i, response in enumerate(responses):
responses_row[str(i+1)] = []
e.add_field(
name=f"{emotes[i]} __{response.capitalize()}__",
value="**0** vote"
)
e.set_footer(text=f"ID: {poll_row.id}")
poll_row.message_id = stmt.id
poll_row.poll = e.to_dict()
poll_row.is_anonymous = anonymous
poll_row.responses = responses_row
self.bot.engine.commit()
await stmt.edit(content='', embed=e)
for emote in range(len(responses)):
await stmt.add_reaction(emotes[emote])
@commands.group(name='sondage', aliases=['poll'])
async def _poll(self, ctx):
"""
todo: refer to readme.md
"""
async def _poll(self, ctx: commands.Context):
if ctx.invoked_subcommand is None:
...
@_poll.group(name='create', aliases=['new', 'nouveau'])
async def _poll_create(self, ctx: commands.Context, *, poll: str):
is_anonymous = '--anonyme' in poll
poll = poll.replace('--anonyme', '')
await self.make_poll(ctx, poll, anonymous=is_anonymous)
def setup(bot: TuxBot):
bot.add_cog(Poll(bot))
bot.add_cog(Polls(bot))

View file

@ -5,6 +5,7 @@ import discord
from discord.ext import commands
from bot import TuxBot
import socket
from socket import AF_INET6
from .utils.lang import Texts
@ -23,53 +24,60 @@ class Utility(commands.Cog):
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', ctx).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()
if response.get('status') == 'success':
e = discord.Embed(
title=f"{Texts('utility', ctx).get('Information for')} "
f"``{addr}`` *`({response.get('query')})`*",
color=0x5858d7
)
e.add_field(
name=Texts('utility', ctx).get('Belongs to :'),
value=response.get('org', 'N/A'),
inline=False
)
e.add_field(
name=Texts('utility', ctx).get('Is located at :'),
value=response.get('city', 'N/A'),
inline=True
)
e.add_field(
name="Region :",
value=f"{response.get('regionName', 'N/A')} "
f"({response.get('country', 'N/A')})",
inline=True
)
e.set_thumbnail(
url=f"https://www.countryflags.io/"
f"{response.get('countryCode')}/flat/64.png")
await ctx.send(embed=e)
try:
if ip_type in ('v6', 'ipv6'):
try:
ip = socket.getaddrinfo(addr, None, AF_INET6)[1][4][0]
except socket.gaierror:
return await ctx.send(
Texts('utility', ctx).get('ipv6 not available'))
else:
await ctx.send(
content=f"{Texts('utility', ctx).get('info not available')}"
f"``{response.get('query')}``")
ip = socket.gethostbyname(addr)
async with self.bot.session.get(f"http://ip-api.com/json/{ip}") \
as s:
response: dict = await s.json()
if response.get('status') == 'success':
e = discord.Embed(
title=f"{Texts('utility', ctx).get('Information for')}"
f" ``{addr}`` *`({response.get('query')})`*",
color=0x5858d7
)
e.add_field(
name=Texts('utility', ctx).get('Belongs to :'),
value=response.get('org', 'N/A'),
inline=False
)
e.add_field(
name=Texts('utility', ctx).get('Is located at :'),
value=response.get('city', 'N/A'),
inline=True
)
e.add_field(
name="Region :",
value=f"{response.get('regionName', 'N/A')} "
f"({response.get('country', 'N/A')})",
inline=True
)
e.set_thumbnail(
url=f"https://www.countryflags.io/"
f"{response.get('countryCode')}/flat/64.png")
await ctx.send(embed=e)
else:
await ctx.send(
content=f"{Texts('utility', ctx).get('info not available')}"
f"``{response.get('query')}``")
except Exception:
await ctx.send(
f"{Texts('utility', ctx).get('Cannot connect to host')} {addr}"
)
"""---------------------------------------------------------------------"""
@ -78,9 +86,10 @@ class Utility(commands.Cog):
if (addr.startswith('http') or addr.startswith('ftp')) is not True:
addr = f"http://{addr}"
await ctx.trigger_typing()
try:
async with self.bot.session.get(addr) as s:
await ctx.trigger_typing()
e = discord.Embed(
title=f"{Texts('utility', ctx).get('Headers of')} {addr}",
color=0xd75858
@ -95,9 +104,10 @@ class Utility(commands.Cog):
e.add_field(name=key, value=value, inline=True)
await ctx.send(embed=e)
except aiohttp.client_exceptions.ClientConnectorError:
await ctx.send(f"{Texts('utility', ctx).get('Cannot connect to host')} "
f"{addr}")
except aiohttp.client_exceptions.ClientError:
await ctx.send(
f"{Texts('utility', ctx).get('Cannot connect to host')} {addr}"
)
"""---------------------------------------------------------------------"""

10
cogs/utils/emotes.py Normal file
View file

@ -0,0 +1,10 @@
emotes = ['1⃣', '2⃣', '3⃣', '4⃣', '5⃣', '6⃣', '7⃣', '8⃣', '9⃣', '🔟', '0⃣',
'🇦', '🇧', '🇨', '🇩', '🇪', '🇫', '🇬', '🇭', '🇮']
def get(count):
return emotes[:count]
def get_index(emote):
return emotes.index(emote)

View file

@ -0,0 +1,3 @@
from .lang import Lang
from .warn import Warn
from .poll import Poll

View file

@ -4,7 +4,7 @@ Base = declarative_base()
class Lang(Base):
__tablename__ = 'lang'
__tablename__ = 'langs'
key = Column(String, primary_key=True)
value = Column(String)

20
cogs/utils/models/poll.py Normal file
View file

@ -0,0 +1,20 @@
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, Boolean, BigInteger, JSON
Base = declarative_base()
class Poll(Base):
__tablename__ = 'polls'
id = Column(Integer, primary_key=True)
message_id = Column(BigInteger)
poll = Column(JSON)
is_anonymous = Column(Boolean)
responses = Column(JSON, nullable=True)
def __repr__(self):
return "<Poll(id='%s', message_id='%s', poll='%s', " \
"is_anonymous='%s', responses='%s')>" % \
(self.id, self.message_id, self.poll,
self.is_anonymous, self.responses)

View file

@ -2,6 +2,7 @@ import datetime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, BIGINT, TIMESTAMP
Base = declarative_base()
@ -16,5 +17,5 @@ class Warn(Base):
def __repr__(self):
return "<Warn(server_id='%s', user_id='%s', reason='%s', " \
"created_at='%s')>"\
"created_at='%s')>" \
% (self.server_id, self.user_id, self.reason, self.created_at)

Binary file not shown.

View file

@ -0,0 +1,20 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-09-08 19:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
#: launcher.py:51
msgid "**Preparation...**"
msgstr ""

Binary file not shown.

View file

@ -0,0 +1,20 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-09-08 19:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
#: launcher.py:51
msgid "**Preparation...**"
msgstr "**Préparation...**"

View file

@ -1,5 +1,8 @@
{
"280805240977227776": [
"b."
"b!"
],
"303633056944881686": [
"b! "
]
}

View file

@ -7,4 +7,5 @@ asyncpg>=0.12.0
sqlalchemy
gitpython
requests
psutil
psutil
bcrypt