1
0
Fork 0

Merge pull request #56 from flifloo/slash

Slash
This commit is contained in:
Ethanell 2021-02-04 11:01:40 +01:00 committed by flifloo
commit 269020de56
19 changed files with 727 additions and 687 deletions

View file

@ -1,10 +1,12 @@
from discord import Intents from discord import Intents
from discord_slash import SlashCommand
from administrator.config import config from administrator.config import config
import db import db
from discord.ext import commands from discord.ext import commands
bot = commands.Bot(command_prefix=config.get("prefix"), intents=Intents.all()) bot = commands.Bot(command_prefix=config.get("prefix"), intents=Intents.all())
slash = SlashCommand(bot, auto_register=True, auto_delete=True)
import extensions import extensions

View file

@ -1,4 +1,8 @@
import functools
from discord import Permissions
from discord.ext import commands from discord.ext import commands
from discord.ext.commands import NoPrivateMessage, NotOwner, MissingPermissions
import db import db
@ -8,12 +12,62 @@ class ExtensionDisabled(commands.CheckFailure):
def is_enabled(): def is_enabled():
async def check(ctx: commands.Context): def check(func):
if ctx.command.cog and ctx.guild: @functools.wraps(func)
async def wrapped(*args):
ctx = args[1]
if ctx.guild:
s = db.Session() s = db.Session()
es = s.query(db.ExtensionState).get((ctx.command.cog.qualified_name, ctx.guild.id)) es = s.query(db.ExtensionState).get((args[0].qualified_name, ctx.guild.id))
s.close() s.close()
if es and not es.state: if es and not es.state:
raise ExtensionDisabled() return
return True # raise ExtensionDisabled()
return commands.check(check) return await func(*args)
return wrapped
return check
def is_owner():
def check(func):
@functools.wraps(func)
async def wrapped(*args):
ctx = args[1]
if not await ctx._discord.is_owner(ctx.author):
raise NotOwner('You do not own this bot.')
return await func(*args)
return wrapped
return check
def guild_only():
def check(func):
@functools.wraps(func)
async def wrapped(*args):
if args[1].guild is None:
raise NoPrivateMessage()
return await func(*args)
return wrapped
return check
def has_permissions(**perms):
invalid = set(perms) - set(Permissions.VALID_FLAGS)
if invalid:
raise TypeError('Invalid permission(s): %s' % (', '.join(invalid)))
def check(func):
@functools.wraps(func)
async def wrapped(*args):
ctx = args[1]
ch = ctx.channel
permissions = ch.permissions_for(ctx.author)
missing = [perm for perm, value in perms.items() if getattr(permissions, perm) != value]
if not missing or permissions.administrator:
return await func(*args)
raise MissingPermissions(missing)
return wrapped
return check

View file

@ -1,12 +1,32 @@
import re import re
from datetime import timedelta from datetime import timedelta
from discord import Message
from discord.ext.commands import BadArgument from discord.ext.commands import BadArgument
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
import db import db
msg_url_re = re.compile(r"^https://.*discord.*\.com/channels/[0-9]+/([0-9+]+)/([0-9]+)$")
async def get_message_by_url(ctx, url: str) -> Message:
r = msg_url_re.fullmatch(url)
if not r:
raise BadArgument()
r = r.groups()
c = ctx.guild.get_channel(int(r[0]))
if not c:
raise BadArgument()
m = await c.fetch_message(int(r[1]))
if not m or m.is_system():
raise BadArgument()
return m
def time_pars(s: str) -> timedelta: def time_pars(s: str) -> timedelta:
match = re.fullmatch(r"(?:([0-9]+)W)*(?:([0-9]+)D)*(?:([0-9]+)H)*(?:([0-9]+)M)*(?:([0-9]+)S)*", match = re.fullmatch(r"(?:([0-9]+)W)*(?:([0-9]+)D)*(?:([0-9]+)H)*(?:([0-9]+)M)*(?:([0-9]+)S)*",
s.upper().replace(" ", "").strip()) s.upper().replace(" ", "").strip())

View file

@ -2,9 +2,13 @@ from traceback import format_exc
from discord.ext import commands from discord.ext import commands
from discord import Embed, Guild from discord import Embed, Guild
from discord.ext.commands import MissingPermissions, BadArgument, CommandError from discord.ext.commands import BadArgument
from discord_slash import cog_ext, SlashContext, SlashCommandOptionType
from discord_slash.utils import manage_commands
import db import db
from administrator import slash
from administrator.check import has_permissions
from administrator.logger import logger from administrator.logger import logger
@ -15,69 +19,66 @@ logger = logger.getChild(extension_name)
class Extension(commands.Cog): class Extension(commands.Cog):
def __init__(self, bot: commands.Bot): def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
slash.get_cog_commands(self)
def description(self): def description(self):
return "Manage bot's extensions" return "Manage bot's extensions"
@commands.group("extension", pass_context=True) @cog_ext.cog_subcommand(base="extension", name="list", description="List all enabled extensions")
async def extension(self, ctx: commands.Context): @has_permissions(administrator=True)
if ctx.invoked_subcommand is None: async def extension_list(self, ctx: SlashContext):
await ctx.invoke(self.extension_help)
@extension.group("help", pass_context=True)
async def extension_help(self, ctx: commands.Context):
embed = Embed(title="Extension help")
for c, n, v in [[self.extension_list, "extension list", "List all enabled extensions"],
[self.extension_enable, "extension enable", "Enable an extensions"],
[self.extension_disable, "extension disable", "Disable an extensions"],
[self.extension_loaded, "extension loaded", "List all loaded extensions"],
[self.extension_load, "extension load <name>", "Load an extension"],
[self.extension_unload, "extension unload <name>", "Unload an extension"],
[self.extension_reload, "extension reload <name>", "Reload an extension"]]:
try:
if await c.can_run(ctx):
embed.add_field(name=n, value=v, inline=False)
except CommandError:
pass
if not embed.fields:
raise MissingPermissions("")
await ctx.send(embed=embed)
@extension.group("list", pass_context=True)
@commands.has_guild_permissions(administrator=True)
async def extension_list(self, ctx: commands.Context):
s = db.Session() s = db.Session()
embed = Embed(title="Extensions list") embed = Embed(title="Extensions list")
for es in s.query(db.ExtensionState).filter(db.ExtensionState.guild_id == ctx.guild.id): for es in s.query(db.ExtensionState).filter(db.ExtensionState.guild_id == ctx.guild.id):
embed.add_field(name=es.extension_name, value="Enable" if es.state else "Disable") embed.add_field(name=es.extension_name, value="Enable" if es.state else "Disable")
await ctx.send(embed=embed) s.close()
await ctx.send(embeds=[embed])
@extension.group("enable", pass_context=True) @cog_ext.cog_subcommand(base="extension",
@commands.has_guild_permissions(administrator=True) name="enable",
async def extension_enable(self, ctx: commands.Context, name: str): description="Enable an extensions",
options=[manage_commands.create_option("extension", "The extension to enable",
SlashCommandOptionType.STRING, True)])
@has_permissions(administrator=True)
async def extension_enable(self, ctx: SlashContext, name: str):
s = db.Session() s = db.Session()
es = s.query(db.ExtensionState).get((name, ctx.guild.id)) es = s.query(db.ExtensionState).get((name, ctx.guild.id))
if not es or es.state: if not es:
raise BadArgument() raise BadArgument()
elif es.state:
message = "Extension already enabled"
else:
es.state = True es.state = True
s.add(es) s.add(es)
s.commit() s.commit()
s.close() s.close()
await ctx.message.add_reaction("\U0001f44d") message = "\U0001f44d"
await ctx.send(content=message)
@extension.group("disable", pass_context=True) @cog_ext.cog_subcommand(base="extension",
@commands.has_guild_permissions(administrator=True) name="disable",
async def extension_disable(self, ctx: commands.Context, name: str): description="Disable an extensions",
options=[manage_commands.create_option("extension", "The extension to disable",
SlashCommandOptionType.STRING, True)])
@has_permissions(administrator=True)
async def extension_disable(self, ctx: SlashContext, name: str):
s = db.Session() s = db.Session()
es = s.query(db.ExtensionState).get((name, ctx.guild.id)) es = s.query(db.ExtensionState).get((name, ctx.guild.id))
if not es or not es.state: if not es:
raise BadArgument() raise BadArgument()
elif not es.state:
message = "Extension already disabled"
else:
es.state = False es.state = False
s.add(es) s.add(es)
s.commit() s.commit()
s.close() s.close()
await ctx.message.add_reaction("\U0001f44d") message = "\U0001f44d"
await ctx.send(content=message)
@commands.group("extension", pass_context=True)
async def extension(self, ctx: commands.Context):
pass
@extension.group("loaded", pass_context=True) @extension.group("loaded", pass_context=True)
@commands.is_owner() @commands.is_owner()
@ -152,6 +153,9 @@ class Extension(commands.Cog):
s.commit() s.commit()
s.close() s.close()
def cog_unload(self):
slash.remove_cog_commands(self)
def setup(bot): def setup(bot):
logger.info(f"Loading...") logger.info(f"Loading...")

View file

@ -1,18 +1,14 @@
from discord.ext import commands from discord.ext import commands
from discord import Member, Embed, Forbidden from discord import Member, Embed, Forbidden
from discord.ext.commands import BadArgument from discord_slash import cog_ext, SlashContext, SlashCommandOptionType
from discord_slash.utils import manage_commands
from administrator.check import is_enabled from administrator.check import is_enabled, guild_only, has_permissions
from administrator.logger import logger from administrator.logger import logger
from administrator import db, config from administrator import db, slash
from administrator.utils import event_is_enabled from administrator.utils import event_is_enabled
def check_greetings_message_type(message_type):
if message_type not in ["join", "leave"]:
raise BadArgument()
extension_name = "greetings" extension_name = "greetings"
logger = logger.getChild(extension_name) logger = logger.getChild(extension_name)
@ -20,67 +16,74 @@ logger = logger.getChild(extension_name)
class Greetings(commands.Cog): class Greetings(commands.Cog):
def __init__(self, bot: commands.Bot): def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
slash.get_cog_commands(self)
def description(self): def description(self):
return "Setup join and leave message" return "Setup join and leave message"
@commands.group("greetings", pass_context=True) @cog_ext.cog_subcommand(base="greetings", name="set",
description="Set the greetings message\n`{}` will be replace by the username",
options=[
manage_commands.create_option("type", "The join or leave message",
SlashCommandOptionType.STRING, True,
[manage_commands.create_choice("join", "join"),
manage_commands.create_choice("leave", "leave")]),
manage_commands.create_option("message", "The message", SlashCommandOptionType.STRING,
True)
])
@is_enabled() @is_enabled()
@commands.guild_only() @guild_only()
@commands.has_permissions(manage_guild=True) @has_permissions(manage_guild=True)
async def greetings(self, ctx: commands.Context): async def greetings_set(self, ctx: SlashContext, message_type: str, message: str):
if ctx.invoked_subcommand is None:
await ctx.invoke(self.greetings_help)
@greetings.group("help", pass_context=True)
async def greetings_help(self, ctx: commands.Context):
embed = Embed(title="Greetings help")
embed.add_field(name="set <join/leave> <message>", value="Set the greetings message\n"
"`{}` will be replace by the username",
inline=False)
embed.add_field(name="show <join/leave>", value="Show the greetings message", inline=False)
embed.add_field(name="toggle <join/leave>", value="Enable or disable the greetings message", inline=False)
await ctx.send(embed=embed)
@greetings.group("set", pass_context=True)
async def greetings_set(self, ctx: commands.Context, message_type: str):
check_greetings_message_type(message_type)
message = ctx.message.content.replace(config.get("prefix")+"greetings set " + message_type, "").strip()
s = db.Session() s = db.Session()
m = s.query(db.Greetings).filter(db.Greetings.guild == ctx.guild.id).first() m = s.query(db.Greetings).filter(db.Greetings.guild == ctx.guild.id).first()
if not m: if not m:
m = db.Greetings(ctx.guild.id) m = db.Greetings(ctx.guild.id)
s.add(m) s.add(m)
setattr(m, message_type+"_enable", True) setattr(m, message_type+"_enable", True)
setattr(m, message_type+"_message", message) setattr(m, message_type+"_message", message.replace("\\n", '\n'))
s.commit() s.commit()
await ctx.message.add_reaction("\U0001f44d") await ctx.send(content="\U0001f44d")
@greetings.group("show", pass_context=True) @cog_ext.cog_subcommand(base="greetings", name="show",
async def greetings_show(self, ctx: commands.Context, message_type: str): description="Show the greetings message",
check_greetings_message_type(message_type) options=[manage_commands.create_option("type", "The join or leave message",
SlashCommandOptionType.STRING, True,
[manage_commands.create_choice("join", "join"),
manage_commands.create_choice("leave", "leave")])])
@is_enabled()
@guild_only()
@has_permissions(manage_guild=True)
async def greetings_show(self, ctx: SlashContext, message_type: str):
s = db.Session() s = db.Session()
m = s.query(db.Greetings).filter(db.Greetings.guild == ctx.guild.id).first() m = s.query(db.Greetings).filter(db.Greetings.guild == ctx.guild.id).first()
s.close() s.close()
if not m: if not m:
await ctx.send(f"No {message_type} message set !") await ctx.send(content=f"No {message_type} message set !")
else: else:
if message_type == "join": if message_type == "join":
await ctx.send(embed=m.join_embed(ctx.guild.name, str(ctx.message.author))) await ctx.send(embeds=[m.join_embed(ctx.guild.name, str(ctx.author))])
else: else:
await ctx.send(m.leave_msg(str(ctx.message.author))) await ctx.send(content=m.leave_msg(str(ctx.author)))
@greetings.group("toggle", pass_context=True) @cog_ext.cog_subcommand(base="greetings", name="toggle",
async def greetings_toggle(self, ctx: commands.Context, message_type: str): description="Enable or disable the greetings message",
check_greetings_message_type(message_type) options=[manage_commands.create_option("type", "The join or leave message",
SlashCommandOptionType.STRING, True,
[manage_commands.create_choice("join", "join"),
manage_commands.create_choice("leave", "leave")])])
@is_enabled()
@guild_only()
@has_permissions(manage_guild=True)
async def greetings_toggle(self, ctx: SlashContext, message_type: str):
s = db.Session() s = db.Session()
m = s.query(db.Greetings).filter(db.Greetings.guild == ctx.guild.id).first() m = s.query(db.Greetings).filter(db.Greetings.guild == ctx.guild.id).first()
if not m: if not m:
await ctx.send(f"No {message_type} message set !") await ctx.send(content=f"No {message_type} message set !")
else: else:
setattr(m, message_type+"_enable", not getattr(m, message_type+"_enable")) setattr(m, message_type+"_enable", not getattr(m, message_type+"_enable"))
s.commit() s.commit()
await ctx.send(f"{message_type.title()} message is " + await ctx.send(content=f"{message_type.title()} message is " +
("enable" if getattr(m, message_type+"_enable") else "disable")) ("enable" if getattr(m, message_type+"_enable") else "disable"))
s.close() s.close()
@ -108,6 +111,9 @@ class Greetings(commands.Cog):
if m and m.leave_enable: if m and m.leave_enable:
await member.guild.system_channel.send(m.leave_msg(str(member))) await member.guild.system_channel.send(m.leave_msg(str(member)))
def cog_unload(self):
slash.remove_cog_commands(self)
def setup(bot): def setup(bot):
logger.info(f"Loading...") logger.info(f"Loading...")

View file

@ -1,9 +1,8 @@
from discord import Embed
from discord.ext import commands from discord.ext import commands
from discord.ext.commands import CommandNotFound, MissingRequiredArgument, BadArgument, MissingPermissions, \ from discord.ext.commands import CommandNotFound, MissingRequiredArgument, BadArgument, MissingPermissions, \
NoPrivateMessage, CommandError, NotOwner NoPrivateMessage, NotOwner
from discord_slash import SlashContext
from administrator import config
from administrator.check import ExtensionDisabled from administrator.check import ExtensionDisabled
from administrator.logger import logger from administrator.logger import logger
@ -19,38 +18,35 @@ class Help(commands.Cog):
def description(self): def description(self):
return "Give help and command list" return "Give help and command list"
@commands.command("help", pass_context=True)
async def help(self, ctx: commands.Context):
embed = Embed(title="Help")
for c in filter(lambda x: x != "Help", self.bot.cogs):
cog = self.bot.cogs[c]
try:
if await getattr(cog, c.lower()).can_run(ctx):
embed.add_field(name=c,
value=cog.description() + "\n" +
f"`{config.get('prefix')}{c.lower()} help` for more information",
inline=False)
except CommandError:
pass
await ctx.send(embed=embed)
@commands.Cog.listener() @commands.Cog.listener()
async def on_command_error(self, ctx: commands.Context, error): async def on_command_error(self, ctx: commands.Context, error):
await self.error_handler(ctx, error)
@commands.Cog.listener()
async def on_slash_command_error(self, ctx: SlashContext, error: Exception):
await self.error_handler(ctx, error)
async def error_handler(self, ctx, error: Exception):
if isinstance(error, CommandNotFound): if isinstance(error, CommandNotFound):
await ctx.message.add_reaction("\u2753") await self.reaction(ctx, "\u2753")
elif isinstance(error, MissingRequiredArgument) or isinstance(error, BadArgument): elif isinstance(error, MissingRequiredArgument) or isinstance(error, BadArgument):
await ctx.message.add_reaction("\u274C") await self.reaction(ctx, "\u274C")
elif isinstance(error, NotOwner) or isinstance(error, MissingPermissions)\ elif isinstance(error, NotOwner) or isinstance(error, MissingPermissions) \
or isinstance(error, NoPrivateMessage): or isinstance(error, NoPrivateMessage):
await ctx.message.add_reaction("\U000026D4") await self.reaction(ctx, "\U000026D4")
elif isinstance(error, ExtensionDisabled): elif isinstance(error, ExtensionDisabled):
await ctx.message.add_reaction("\U0001F6AB") await self.reaction(ctx, "\U0001F6AB")
else: else:
await ctx.send("An error occurred !") await ctx.send(content="An error occurred !")
raise error raise error
await ctx.message.delete(delay=30)
@staticmethod
async def reaction(ctx, react: str):
m = getattr(ctx, "message", None)
if m:
await m.add_reaction(react)
else:
await ctx.send(content=react)
def setup(bot): def setup(bot):

View file

@ -1,12 +1,16 @@
import re import re
from discord import Embed, Member, Guild from discord import Embed, Member, Guild, Role, CategoryChannel
from discord.abc import GuildChannel
from discord.errors import Forbidden from discord.errors import Forbidden
from discord.ext import commands from discord.ext import commands
from discord.ext.commands import BadArgument from discord.ext.commands import BadArgument
from discord_slash import cog_ext, SlashContext, SlashCommandOptionType
from discord_slash.utils import manage_commands
import db import db
from administrator.check import is_enabled from administrator import slash
from administrator.check import is_enabled, guild_only, has_permissions
from administrator.logger import logger from administrator.logger import logger
from administrator.utils import event_is_enabled from administrator.utils import event_is_enabled
@ -20,41 +24,31 @@ class Invite(commands.Cog):
def __init__(self, bot: commands.Bot): def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
self.invites = {} self.invites = {}
slash.get_cog_commands(self)
self.bot.loop.create_task(self.update_invites()) self.bot.loop.create_task(self.update_invites())
def description(self): def description(self):
return "Get role from a special invite link" return "Get role from a special invite link"
@commands.group("invite", pass_context=True)
@is_enabled() @is_enabled()
@commands.guild_only() @guild_only()
@commands.has_guild_permissions(administrator=True) @has_permissions(administrator=True)
async def invite(self, ctx: commands.Context): async def invite_add(self, ctx: SlashContext, channel: GuildChannel, role: Role):
if ctx.invoked_subcommand is None: if isinstance(channel, CategoryChannel):
await ctx.invoke(self.invite_help)
@invite.group("help", pass_context=True)
async def invite_help(self, ctx: commands.Context):
embed = Embed(title="Invite help")
embed.add_field(name="invite create <#channel> <@role>", value="Create a invite link to a role", inline=False)
embed.add_field(name="invite delete <code>", value="Remove a invite", inline=False)
await ctx.send(embed=embed)
@invite.group("create", pass_context=True)
async def invite_add(self, ctx: commands.Context, channel: str, role: str):
if not channel_mention_re.fullmatch(channel) or len(ctx.message.channel_mentions) != 1 or\
not role_mention_re.fullmatch(role) or len(ctx.message.role_mentions) != 1:
raise BadArgument() raise BadArgument()
inv = await channel.create_invite()
inv = await ctx.message.channel_mentions[0].create_invite()
s = db.Session() s = db.Session()
s.add(db.InviteRole(ctx.guild.id, inv.code, ctx.message.role_mentions[0].id)) s.add(db.InviteRole(ctx.guild.id, inv.code, role.id))
s.commit() s.commit()
s.close() s.close()
await ctx.send(f"Invite created: `{inv.url}`") await ctx.send(content=f"Invite created: `{inv.url}`")
@invite.group("delete", pass_context=True) @cog_ext.cog_subcommand(base="invite", name="delete", description="Remove a invite", options=[
async def invite_delete(self, ctx: commands.Context, code: str): manage_commands.create_option("code", "The invitation code", SlashCommandOptionType.STRING, True)])
@is_enabled()
@guild_only()
@has_permissions(administrator=True)
async def invite_delete(self, ctx: SlashContext, code: str):
inv = next(filter(lambda i: i.code == code, await ctx.guild.invites()), None) inv = next(filter(lambda i: i.code == code, await ctx.guild.invites()), None)
if not inv: if not inv:
raise BadArgument() raise BadArgument()
@ -68,7 +62,7 @@ class Invite(commands.Cog):
s.commit() s.commit()
s.close() s.close()
await inv.delete() await inv.delete()
await ctx.message.add_reaction("\U0001f44d") await ctx.send(content="\U0001f44d")
async def update_invites(self): async def update_invites(self):
for g in self.bot.guilds: for g in self.bot.guilds:

View file

@ -1,105 +1,74 @@
import re import re
from discord import Embed, Member from discord import Member, Role
from discord.ext import commands from discord.ext import commands
from discord.ext.commands import BadArgument, MissingPermissions, CommandError from discord.ext.commands import BadArgument
from discord_slash import cog_ext, SlashCommandOptionType, SlashContext
from discord_slash.utils import manage_commands
import db import db
from administrator import slash
from administrator.check import guild_only, has_permissions
from administrator.logger import logger from administrator.logger import logger
from administrator.utils import get_message_by_url
extension_name = "PCP" extension_name = "PCP"
logger = logger.getChild(extension_name) logger = logger.getChild(extension_name)
msg_url_re = re.compile(r"^https://.*discord.*\.com/channels/[0-9]+/([0-9+]+)/([0-9]+)$")
role_mention_re = re.compile(r"^<@&[0-9]+>$")
user_mention_re = re.compile(r"^<@![0-9]+>$")
class PCP(commands.Cog): class PCP(commands.Cog):
def __init__(self, bot: commands.Bot): def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
slash.get_cog_commands(self)
def description(self): def description(self):
return "PCP Univ Lyon 1" return "PCP Univ Lyon 1"
@commands.group("pcp", pass_context=True) @cog_ext.cog_subcommand(base="pcp", name="join", description="Join your group", options=[
@commands.guild_only() manage_commands.create_option("group", "The target group to join", SlashCommandOptionType.ROLE, True)])
async def pcp(self, ctx: commands.Context): @guild_only()
group = ctx.message.content.replace(f"{ctx.prefix}{ctx.command} ", "").upper() async def pcp(self, ctx: SlashContext, role: Role):
if group:
s = db.Session() s = db.Session()
p = s.query(db.PCP).get(ctx.guild.id) p = s.query(db.PCP).get(ctx.guild.id)
s.close() s.close()
if p and re.fullmatch(p.roles_re, group): if p and re.fullmatch(p.roles_re, role.name.upper()):
await ctx.message.add_reaction("\U000023f3") await ctx.send(content="\U000023f3")
role = next(filter(lambda r: r.name.upper() == group, ctx.guild.roles), None)
def roles() -> list: async def roles() -> list:
return list(filter( return list(filter(
lambda r: re.fullmatch(p.roles_re, r.name.upper()) or lambda r: re.fullmatch(p.roles_re, r.name.upper()) or
(p.start_role_re and re.fullmatch(p.start_role_re, r.name.upper())), (p.start_role_re and re.fullmatch(p.start_role_re, r.name.upper())),
ctx.author.roles (await ctx.guild.fetch_member(ctx.author.id)).roles
)) ))
if not role or role.name in map(lambda r: r.name, roles()): if not role or role.name in map(lambda r: r.name, await roles()):
await ctx.message.remove_reaction("\U000023f3", self.bot.user) await ctx.delete()
raise BadArgument() raise BadArgument()
while roles(): while await roles():
await ctx.author.remove_roles(*roles()) await ctx.author.remove_roles(*(await roles()))
while role not in ctx.author.roles: while role not in (await ctx.guild.fetch_member(ctx.author.id)).roles:
await ctx.author.add_roles(role) await ctx.author.add_roles(role)
await ctx.message.remove_reaction("\U000023f3", self.bot.user) await ctx.edit(content="\U0001f44d")
await ctx.message.add_reaction("\U0001f44d")
return
if ctx.invoked_subcommand is None: @cog_ext.cog_subcommand(base="pcp", name="pin", description="Pin a message with the url", options=[
await ctx.invoke(self.pcp_help) manage_commands.create_option("url", "message URL", SlashCommandOptionType.STRING, True)
])
@pcp.group("help", pass_context=True) @guild_only()
async def pcp_help(self, ctx: commands.Context): async def pcp_pin(self, ctx: SlashContext, url: str):
embed = Embed(title="PCP help")
s = db.Session()
p = s.query(db.PCP).get(ctx.guild.id)
s.close()
if p:
embed.add_field(name="pcp <group>", value="Join your group", inline=False)
for c, n, v in [[self.pcp_group, "pcp group", "Manage PCP group"],
[self.pcp_pin, "pcp pin <url>", "Pin a message with the url"],
[self.pcp_unpin, "pcp unpin <url>", "Unpin a message with the url"]]:
try:
if await c.can_run(ctx):
embed.add_field(name=n, value=v, inline=False)
except CommandError:
pass
if not embed.fields:
raise MissingPermissions("")
await ctx.send(embed=embed)
@pcp.group("pin", pass_context=True)
async def pcp_pin(self, ctx: commands.Context, url: str):
await self.pin(ctx, url, True) await self.pin(ctx, url, True)
@pcp.group("unpin", pass_context=True) @cog_ext.cog_subcommand(base="pcp", name="unpin", description="Unpin a message with the url", options=[
async def pcp_unpin(self, ctx: commands.Context, url: str): manage_commands.create_option("url", "message URL", SlashCommandOptionType.STRING, True)
])
@guild_only()
async def pcp_unpin(self, ctx: SlashContext, url: str):
await self.pin(ctx, url, False) await self.pin(ctx, url, False)
@staticmethod @staticmethod
async def pin(ctx: commands.Context, url: str, action: bool): async def pin(ctx: SlashContext, url: str, action: bool):
r = msg_url_re.fullmatch(url) m = await get_message_by_url(ctx, url)
if not r:
raise BadArgument()
r = r.groups()
c = ctx.guild.get_channel(int(r[0]))
if not c:
raise BadArgument()
m = await c.fetch_message(int(r[1]))
if not m:
raise BadArgument()
if action: if action:
await m.pin() await m.pin()
msg = "pinned a message" msg = "pinned a message"
@ -107,35 +76,23 @@ class PCP(commands.Cog):
await m.unpin() await m.unpin()
msg = "unpinned a message" msg = "unpinned a message"
await ctx.send(f"{ctx.author.mention} {msg}") await ctx.send(content=f"{ctx.author.mention} {msg}")
@pcp.group("group", pass_context=True) @cog_ext.cog_subcommand(base="pcp", subcommand_group="group", name="fix_vocal",
@commands.has_permissions(administrator=True) description="Check all text channel permissions to reapply vocal permissions")
async def pcp_group(self, ctx: commands.Context): @has_permissions(administrator=True)
if ctx.invoked_subcommand is None: async def pcp_group_fix_vocal(self, ctx: SlashContext):
await ctx.invoke(self.pcp_group_help)
@pcp_group.group("help", pass_context=True)
async def pcp_group_help(self, ctx: commands.Context):
embed = Embed(title="PCP group help")
embed.add_field(name="pcp group set <role Regex> [Welcome role Regex]",
value="Set regex for group role", inline=False)
embed.add_field(name="pcp group unset", value="Unset regex for group role", inline=False)
embed.add_field(name="pcp group subject", value="Manage subjects for group", inline=False)
embed.add_field(name="pcp group fix_vocal",
value="Check all text channel permissions to reapply vocal permissions", inline=False)
await ctx.send(embed=embed)
@pcp_group.group("fix_vocal", pass_context=True)
async def pcp_group_fix_vocal(self, ctx: commands.Context):
s = db.Session() s = db.Session()
p = s.query(db.PCP).get(ctx.guild.id) p = s.query(db.PCP).get(ctx.guild.id)
s.close() s.close()
if not p: if not p:
raise BadArgument() raise BadArgument()
message = "\U000023f3"
await ctx.send(content=message)
for cat in filter(lambda c: re.fullmatch(p.roles_re, c.name.upper()), ctx.guild.categories): for cat in filter(lambda c: re.fullmatch(p.roles_re, c.name.upper()), ctx.guild.categories):
await ctx.send(f"{cat.name}...") message += f"\n{cat.name}..."
await ctx.edit(content=message)
teachers = [] teachers = []
for t in cat.text_channels: for t in cat.text_channels:
for p in t.overwrites: for p in t.overwrites:
@ -144,11 +101,20 @@ class PCP(commands.Cog):
voc = next(filter(lambda c: c.name == "vocal-1", cat.voice_channels), None) voc = next(filter(lambda c: c.name == "vocal-1", cat.voice_channels), None)
for t in teachers: for t in teachers:
await voc.set_permissions(t, view_channel=True) await voc.set_permissions(t, view_channel=True)
await ctx.send(f"{cat.name} done") message += f"\n{cat.name} done"
await ctx.message.add_reaction("\U0001f44d") await ctx.edit(content=message)
message += "\n\U0001f44d"
await ctx.edit(content=message)
@pcp_group.group("set", pass_context=True) @cog_ext.cog_subcommand(base="pcp", subcommand_group="group", name="set", description="Set regex for group role",
async def pcp_group_set(self, ctx: commands.Context, roles_re: str, start_role_re: str = None): options=[
manage_commands.create_option("role", "Roles regex",
SlashCommandOptionType.STRING, True),
manage_commands.create_option("role2", "Start roles regex",
SlashCommandOptionType.STRING, False)
])
@has_permissions(administrator=True)
async def pcp_group_set(self, ctx: SlashContext, roles_re: str, start_role_re: str = None):
s = db.Session() s = db.Session()
p = s.query(db.PCP).get(ctx.guild.id) p = s.query(db.PCP).get(ctx.guild.id)
if p: if p:
@ -159,9 +125,11 @@ class PCP(commands.Cog):
s.add(p) s.add(p)
s.commit() s.commit()
s.close() s.close()
await ctx.message.add_reaction("\U0001f44d") await ctx.send(content="\U0001f44d")
@pcp_group.group("unset", pass_context=True) @cog_ext.cog_subcommand(base="pcp", subcommand_group="group", name="unset",
description="Unset regex for group role")
@has_permissions(administrator=True)
async def pcp_group_unset(self, ctx: commands.Context): async def pcp_group_unset(self, ctx: commands.Context):
s = db.Session() s = db.Session()
p = s.query(db.PCP).get(ctx.guild.id) p = s.query(db.PCP).get(ctx.guild.id)
@ -173,33 +141,21 @@ class PCP(commands.Cog):
s.close() s.close()
await ctx.message.add_reaction("\U0001f44d") await ctx.message.add_reaction("\U0001f44d")
@pcp_group.group("subject", pass_context=True) @cog_ext.cog_subcommand(base="pcp", subcommand_group="subject", name="add", description="Add a subject to a group",
async def pcp_group_subject(self, ctx: commands.Context): options=[
if ctx.invoked_subcommand is None: manage_commands.create_option("name", "The subject name",
await ctx.invoke(self.pcp_group_subject_help) SlashCommandOptionType.STRING, True),
manage_commands.create_option("group", "The group",
@pcp_group_subject.group("help", pass_context=True) SlashCommandOptionType.ROLE, True),
async def pcp_group_subject_help(self, ctx: commands.Context): manage_commands.create_option("teacher", "The teacher",
embed = Embed(title="PCP group subject help") SlashCommandOptionType.USER, False)
embed.add_field(name="pcp group subject add <name> <@group> [@teacher]", value="Add a subject to a group", ])
inline=False) @has_permissions(administrator=True)
embed.add_field(name="pcp group subject bulk <@group> [subject1] [subject2] ...", value="Bulk subject add", async def pcp_group_subject_add(self, ctx: SlashContext, name: str, group: Role, teacher: Member = None):
inline=False) if teacher and not next(filter(lambda r: r.name == "professeurs", teacher.roles), None):
embed.add_field(name="pcp group subject remove <name> <@group>", value="Remove a subject to a group",
inline=False)
await ctx.send(embed=embed)
@pcp_group_subject.group("add", pass_context=True)
async def pcp_group_subject_add(self, ctx: commands.Context, name: str, group: str, teacher: str = None):
if not role_mention_re.fullmatch(group):
raise BadArgument()
if teacher and not user_mention_re.fullmatch(teacher):
raise BadArgument()
elif teacher and\
not next(filter(lambda r: r.name == "professeurs", ctx.message.mentions[0].roles), None):
raise BadArgument() raise BadArgument()
cat = next(filter(lambda c: c.name.upper() == ctx.message.role_mentions[0].name.upper(), cat = next(filter(lambda c: c.name.upper() == group.name.upper(),
ctx.guild.categories), None) ctx.guild.categories), None)
if not cat: if not cat:
raise BadArgument() raise BadArgument()
@ -210,26 +166,32 @@ class PCP(commands.Cog):
voc = next(filter(lambda c: c.name == "vocal-1", cat.voice_channels), None) voc = next(filter(lambda c: c.name == "vocal-1", cat.voice_channels), None)
if not voc: if not voc:
voc = await cat.create_voice_channel("vocal-1") voc = await cat.create_voice_channel("vocal-1")
if ctx.message.mentions: if teacher:
await chan.set_permissions(ctx.message.mentions[0], read_messages=True) await chan.set_permissions(teacher, read_messages=True)
await voc.set_permissions(ctx.message.mentions[0], view_channel=True) await voc.set_permissions(teacher, view_channel=True)
await ctx.message.add_reaction("\U0001f44d") await ctx.send(content="\U0001f44d")
@pcp_group_subject.group("bulk", pass_context=True) @cog_ext.cog_subcommand(base="pcp", subcommand_group="subject", name="bulk",
async def pcp_group_subject_bulk(self, ctx: commands.Context, mention, *names): description="Remove a subject to a group", options=[
if not role_mention_re.fullmatch(mention): manage_commands.create_option("group", "The group", SlashCommandOptionType.ROLE, True),
raise BadArgument() manage_commands.create_option("names", "Subjects names", SlashCommandOptionType.STRING, True)
for n in names: ])
await ctx.invoke(self.pcp_group_subject_add, n, mention) @has_permissions(administrator=True)
async def pcp_group_subject_bulk(self, ctx: SlashContext, group: Role, names: str):
for n in names.split(" "):
await self.pcp_group_subject_add.invoke(ctx, n, group)
@pcp_group_subject.group("remove", pass_context=True) @cog_ext.cog_subcommand(base="pcp", subcommand_group="subject", name="remove",description="Bulk subject add",
async def pcp_group_subject_remove(self, ctx: commands.Context, name: str, group: str): options=[
if not role_mention_re.fullmatch(group): manage_commands.create_option("name", "The subject name",
raise BadArgument() SlashCommandOptionType.STRING, True),
manage_commands.create_option("group", "The group",
cat = next(filter(lambda c: c.name.upper() == ctx.message.role_mentions[0].name.upper(), SlashCommandOptionType.ROLE, True)
ctx.guild.categories), None) ])
@has_permissions(administrator=True)
async def pcp_group_subject_remove(self, ctx: SlashContext, name: str, group: Role):
cat = next(filter(lambda c: c.name.upper() == group.name.upper(), ctx.guild.categories), None)
if not cat: if not cat:
raise BadArgument() raise BadArgument()
@ -239,7 +201,7 @@ class PCP(commands.Cog):
await chan.delete() await chan.delete()
await ctx.message.add_reaction("\U0001f44d") await ctx.send(content="\U0001f44d")
def setup(bot): def setup(bot):

View file

@ -1,12 +1,16 @@
import shlex
from datetime import datetime from datetime import datetime
from discord.abc import GuildChannel from discord.abc import GuildChannel
from discord.ext import commands from discord.ext import commands
from discord import Embed, RawReactionActionEvent, RawMessageDeleteEvent, RawBulkMessageDeleteEvent, TextChannel, Guild from discord import Embed, RawReactionActionEvent, RawMessageDeleteEvent, RawBulkMessageDeleteEvent, TextChannel, Guild
from discord.ext.commands import BadArgument from discord.ext.commands import BadArgument
from discord_slash import cog_ext, SlashCommandOptionType, SlashContext
from discord_slash.utils import manage_commands
import db import db
from administrator.check import is_enabled from administrator import slash
from administrator.check import is_enabled, guild_only
from administrator.logger import logger from administrator.logger import logger
from administrator.utils import event_is_enabled from administrator.utils import event_is_enabled
@ -21,47 +25,41 @@ REACTIONS.append("\U0001F51F")
class Poll(commands.Cog): class Poll(commands.Cog):
def __init__(self, bot: commands.Bot): def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
slash.get_cog_commands(self)
def description(self): def description(self):
return "Create poll with a simple command" return "Create poll with a simple command"
@commands.group("poll", pass_context=True) @cog_ext.cog_slash(name="poll",
description="Create a poll",
options=[
manage_commands.create_option("name", "Poll name",
SlashCommandOptionType.STRING, True),
manage_commands.create_option("choices", "All pool choice",
SlashCommandOptionType.STRING, True),
manage_commands.create_option("multi", "Allow multiple choice",
SlashCommandOptionType.BOOLEAN, False)
])
@is_enabled() @is_enabled()
@commands.guild_only() @guild_only()
async def poll(self, ctx: commands.Context, name: str, *choices): async def poll(self, ctx: SlashContext, name: str, choices: str, multi: bool = False):
if name == "help": choices = shlex.split(choices)
await ctx.invoke(self.poll_help) if len(choices) > 11:
else:
multi = False
if choices and choices[0] in ["multi", "m"]:
multi = True
choices = choices[1:]
if len(choices) == 0 or len(choices) > 11:
raise BadArgument() raise BadArgument()
else: else:
embed = Embed(title=f"Poll: {name}") embed = Embed(title=f"Poll: {name}")
embed.set_author(name=str(ctx.author), icon_url=ctx.author.avatar_url) embed.set_author(name=str(ctx.author), icon_url=ctx.author.avatar_url)
embed.set_footer(text=f"Created: {ctx.message.created_at.strftime('%d/%m/%Y %H:%M')}") embed.set_footer(text=f"Created: {datetime.now().strftime('%d/%m/%Y %H:%M')}")
for i, choice in enumerate(choices): for i, choice in enumerate(choices):
embed.add_field(name=REACTIONS[i], value=choice, inline=False) embed.add_field(name=REACTIONS[i], value=choice, inline=False)
message = await ctx.send(embed=embed) message = await ctx.channel.send(embed=embed)
reactions = REACTIONS[0:len(choices)] + ["\U0001F5D1"] reactions = REACTIONS[0:len(choices)] + ["\U0001F5D1"]
for reaction in reactions: for reaction in reactions:
await message.add_reaction(reaction) await message.add_reaction(reaction)
s = db.Session() s = db.Session()
s.add(db.Polls(message.id, ctx.channel.id, ctx.guild.id, ctx.message.author.id, reactions, multi)) s.add(db.Polls(message.id, ctx.channel.id, ctx.guild.id, ctx.author.id, reactions, multi))
s.commit() s.commit()
s.close() s.close()
await ctx.message.delete()
@poll.group("help", pass_context=True)
async def poll_help(self, ctx: commands.Context):
embed = Embed(title="Poll help")
embed.add_field(name="poll <name> [multi|m] <Choice N°1> <Choice N°2> ... <Choice N°11>",
value="Create a poll, the argument multi (or m) after the name allow multiple response\n"
"User the \U0001F5D1 to close the poll",
inline=False)
await ctx.send(embed=embed)
@commands.Cog.listener() @commands.Cog.listener()
async def on_raw_reaction_add(self, payload: RawReactionActionEvent): async def on_raw_reaction_add(self, payload: RawReactionActionEvent):

View file

@ -1,10 +1,13 @@
from discord.abc import GuildChannel
from discord.ext import commands from discord.ext import commands
from discord import Embed, Message from discord import Message, Role, TextChannel
from discord.ext.commands import BadArgument from discord.ext.commands import BadArgument
from discord_slash import cog_ext, SlashCommandOptionType, SlashContext
from discord_slash.utils import manage_commands
from administrator.check import is_enabled from administrator.check import is_enabled, guild_only, has_permissions
from administrator.logger import logger from administrator.logger import logger
from administrator import db from administrator import db, slash
from administrator.utils import event_is_enabled from administrator.utils import event_is_enabled
extension_name = "presentation" extension_name = "presentation"
@ -14,51 +17,49 @@ logger = logger.getChild(extension_name)
class Presentation(commands.Cog): class Presentation(commands.Cog):
def __init__(self, bot: commands.Bot): def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
slash.get_cog_commands(self)
def description(self): def description(self):
return "Give role to user who make a presentation in a dedicated channel" return "Give role to user who make a presentation in a dedicated channel"
@commands.group("presentation", pass_context=True) @cog_ext.cog_subcommand(base="presentation", name="set",
description="Set the presentation channel and the role to give",
options=[
manage_commands.create_option("channel", "The presentation channel",
SlashCommandOptionType.CHANNEL, True),
manage_commands.create_option("role", "The role to give",
SlashCommandOptionType.ROLE, True)
])
@is_enabled() @is_enabled()
@commands.guild_only() @guild_only()
@commands.has_permissions(manage_guild=True) @has_permissions(manage_guild=True)
async def presentation(self, ctx: commands.Context): async def presentation_set(self, ctx: SlashContext, channel: GuildChannel, role: Role):
if ctx.invoked_subcommand is None: if not isinstance(channel, TextChannel):
await ctx.invoke(self.presentation_help)
@presentation.group("help", pass_context=True)
async def presentation_help(self, ctx: commands.Context):
embed = Embed(title="Presentation help", description="Give a role to a new member after a presentation")
embed.add_field(name="set <#channel> <@role>", value="Set the presentation channel and the role to give",
inline=False)
embed.add_field(name="disable", value="Disable the auto role give", inline=False)
await ctx.send(embed=embed)
@presentation.group("set", pass_context=True)
async def presentation_set(self, ctx: commands.Context):
if len(ctx.message.channel_mentions) != 1 and not len(ctx.message.role_mentions) != 1:
raise BadArgument() raise BadArgument()
s = db.Session() s = db.Session()
p = s.query(db.Presentation).filter(db.Presentation.guild == ctx.guild.id).first() p = s.query(db.Presentation).filter(db.Presentation.guild == ctx.guild.id).first()
if not p: if not p:
p = db.Presentation(ctx.guild.id, ctx.message.channel_mentions[0].id, ctx.message.role_mentions[0].id) p = db.Presentation(ctx.guild.id, channel.id, role.id)
s.add(p) s.add(p)
else: else:
p.channel = ctx.message.channel_mentions[0].id p.channel = channel.id
p.role = ctx.message.role_mentions[0].id p.role = role.id
s.commit() s.commit()
await ctx.message.add_reaction("\U0001f44d") await ctx.send(content="\U0001f44d")
@presentation.group("disable", pass_context=True) @cog_ext.cog_subcommand(base="presentation", name="disable", description="Disable the auto role give")
async def presentation_disable(self, ctx: commands.Context): @is_enabled()
@guild_only()
@has_permissions(manage_guild=True)
async def presentation_disable(self, ctx: SlashContext):
s = db.Session() s = db.Session()
p = s.query(db.Presentation).filter(db.Presentation.guild == ctx.guild.id).first() p = s.query(db.Presentation).filter(db.Presentation.guild == ctx.guild.id).first()
if not p: if not p:
await ctx.send(f"Nothing to disable !") await ctx.send(content="Nothing to disable !")
else: else:
s.delete(p) s.delete(p)
s.commit() s.commit()
await ctx.message.add_reaction("\U0001f44d") await ctx.send(content="\U0001f44d")
s.close() s.close()
@commands.Cog.listener() @commands.Cog.listener()

View file

@ -2,8 +2,10 @@ from asyncio import sleep
from discord.ext import commands from discord.ext import commands
from discord import Embed, RawReactionActionEvent from discord import Embed, RawReactionActionEvent
from discord_slash import SlashContext, cog_ext
from administrator.check import is_enabled from administrator import slash
from administrator.check import is_enabled, guild_only, has_permissions
from administrator.logger import logger from administrator.logger import logger
from administrator.utils import event_is_enabled from administrator.utils import event_is_enabled
@ -15,34 +17,23 @@ class Purge(commands.Cog):
def __init__(self, bot: commands.Bot): def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
self.purges = {} self.purges = {}
slash.get_cog_commands(self)
def description(self): def description(self):
return "Purge all messages between the command and the next add reaction" return "Purge all messages between the command and the next add reaction"
@commands.group("purge", pass_context=True) @cog_ext.cog_slash(name="purge", description="Purge all message delimited by the command to your next reaction")
@is_enabled() @is_enabled()
@commands.guild_only() @guild_only()
@commands.has_permissions(manage_messages=True) @has_permissions(manage_messages=True)
async def purge(self, ctx: commands.Context): async def purge(self, ctx: SlashContext):
if ctx.invoked_subcommand is None: message = await ctx.channel.send(content="\U0001f44d")
self.purges[ctx.message.author.id] = ctx.message self.purges[ctx.author.id] = message
await ctx.message.add_reaction("\U0001f44d")
await sleep(2*60) await sleep(2*60)
try: if ctx.author.id in self.purges and self.purges[ctx.author.id] == message:
if self.purges[ctx.message.author.id] == ctx.message: await message.delete()
await ctx.message.clear_reactions() del self.purges[ctx.author.id]
del self.purges[ctx.message.author.id]
except:
pass
@purge.group("help", pass_context=True)
@commands.guild_only()
async def purge_help(self, ctx: commands.Context):
embed = Embed(title="Purge help")
embed.add_field(name="purge", value="Purge all message delimited by the command to your next reaction",
inline=False)
await ctx.send(embed=embed)
@commands.Cog.listener() @commands.Cog.listener()
async def on_raw_reaction_add(self, payload: RawReactionActionEvent): async def on_raw_reaction_add(self, payload: RawReactionActionEvent):
@ -55,8 +46,7 @@ class Purge(commands.Cog):
if user.id in self.purges: if user.id in self.purges:
if message.channel == self.purges[user.id].channel: if message.channel == self.purges[user.id].channel:
async with message.channel.typing(): async with message.channel.typing():
await message.channel.purge(before=self.purges[user.id], after=message, await message.channel.purge(before=self.purges[user.id], after=message, limit=None)
limit=None)
await self.purges[user.id].delete() await self.purges[user.id].delete()
await message.delete() await message.delete()
del self.purges[user.id] del self.purges[user.id]

View file

@ -1,14 +1,15 @@
import re from datetime import datetime
from datetime import datetime, timedelta
from discord.ext import commands from discord.ext import commands
from discord import Embed from discord import Embed
from discord.ext.commands import BadArgument from discord.ext.commands import BadArgument
from discord.ext import tasks from discord.ext import tasks
from discord_slash import SlashContext, cog_ext, SlashCommandOptionType
from discord_slash.utils import manage_commands
from administrator.check import is_enabled from administrator.check import is_enabled
from administrator.logger import logger from administrator.logger import logger
from administrator import db from administrator import db, slash
from administrator.utils import time_pars, seconds_to_time_string from administrator.utils import time_pars, seconds_to_time_string
extension_name = "reminders" extension_name = "reminders"
@ -18,58 +19,49 @@ logger = logger.getChild(extension_name)
class Reminders(commands.Cog, name="Reminder"): class Reminders(commands.Cog, name="Reminder"):
def __init__(self, bot: commands.Bot): def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
slash.get_cog_commands(self)
def description(self): def description(self):
return "Create and manage reminders" return "Create and manage reminders"
@commands.group("reminder", pass_context=True) @cog_ext.cog_subcommand(base="reminder", name="add", description="Add a reminder to your reminders list", options=[
manage_commands.create_option("message", "The message", SlashCommandOptionType.STRING, True),
manage_commands.create_option("time", "When, ?D?H?M?S", SlashCommandOptionType.STRING, True)
])
@is_enabled() @is_enabled()
async def reminder(self, ctx: commands.Context): async def reminder_add(self, ctx: SlashContext, message: str, time: str):
if ctx.invoked_subcommand is None:
await ctx.invoke(self.reminder_help)
@reminder.group("help", pass_context=True)
async def reminder_help(self, ctx: commands.Context):
embed = Embed(title="Reminder help")
embed.add_field(name="reminder add <message> <time>", value="Add a reminder to your reminders list\n"
"Time: ?D?H?M?S", inline=False)
embed.add_field(name="reminder list", value="Show your tasks list", inline=False)
embed.add_field(name="reminder remove [N°]", value="Show your tasks list with if no id given\n"
"Remove the task withe the matching id", inline=False)
await ctx.send(embed=embed)
@reminder.group("add", pass_context=True)
async def reminder_add(self, ctx: commands.Context, message: str, time: str):
time = time_pars(time) time = time_pars(time)
now = datetime.now() now = datetime.now()
s = db.Session() s = db.Session()
s.add(db.Task(message, ctx.author.id, ctx.channel.id, now + time, ctx.message.created_at)) s.add(db.Task(message, ctx.author.id, ctx.channel.id, now + time, datetime.now()))
s.commit() s.commit()
s.close() s.close()
await ctx.send(f"""Remind you in {seconds_to_time_string(time.total_seconds())} !""") await ctx.send(content=f"""Remind you in {seconds_to_time_string(time.total_seconds())} !""")
@reminder.group("list", pass_context=True) @cog_ext.cog_subcommand(base="reminder", name="list", description="Show your tasks list")
async def reminder_list(self, ctx: commands.Context): @is_enabled()
async def reminder_list(self, ctx: SlashContext):
embed = Embed(title="Tasks list") embed = Embed(title="Tasks list")
s = db.Session() s = db.Session()
for t in s.query(db.Task).filter(db.Task.user == ctx.author.id).all(): for t in s.query(db.Task).filter(db.Task.user == ctx.author.id).all():
embed.add_field(name=f"{t.id} | {t.date.strftime('%d/%m/%Y %H:%M')}", value=f"{t.message}", inline=False) embed.add_field(name=f"{t.id} | {t.date.strftime('%d/%m/%Y %H:%M')}", value=f"{t.message}", inline=False)
s.close() s.close()
await ctx.send(embed=embed) await ctx.send(embeds=[embed])
@reminder.group("remove", pass_context=True) @cog_ext.cog_subcommand(base="reminder", name="remove", description="Remove the task withe the matching id",
async def reminder_remove(self, ctx: commands.Context, n: int = None): options=[
if n is None: manage_commands.create_option("id", "The reminder id",
await ctx.invoke(self.reminder_list) SlashCommandOptionType.INTEGER, True)])
else: @is_enabled()
async def reminder_remove(self, ctx: SlashContext, n: int):
s = db.Session() s = db.Session()
t = s.query(db.Task).filter(db.Task.id == n).first() t = s.query(db.Task).filter(db.Task.id == n).first()
if t and t.user == ctx.author.id: if t and t.user == ctx.author.id:
s.delete(t) s.delete(t)
s.commit() s.commit()
s.close() s.close()
await ctx.message.add_reaction("\U0001f44d") await ctx.send(content="\U0001f44d")
else: else:
s.close() s.close()
raise BadArgument() raise BadArgument()

View file

@ -1,84 +1,64 @@
import re
from discord.abc import GuildChannel from discord.abc import GuildChannel
from discord.ext import commands from discord.ext import commands
from discord import Embed, RawReactionActionEvent, RawBulkMessageDeleteEvent, RawMessageDeleteEvent, NotFound, \ from discord import Embed, RawReactionActionEvent, RawBulkMessageDeleteEvent, RawMessageDeleteEvent, NotFound, \
InvalidArgument, HTTPException, TextChannel, Forbidden InvalidArgument, HTTPException, TextChannel, Forbidden, Role, Message
from discord.ext.commands import BadArgument from discord.ext.commands import BadArgument
from discord_slash import cog_ext, SlashContext, SlashCommandOptionType
from discord_slash.utils import manage_commands
from administrator import db from administrator import db, slash
from administrator.check import is_enabled from administrator.check import is_enabled, guild_only, has_permissions
from administrator.logger import logger from administrator.logger import logger
from administrator.utils import event_is_enabled from administrator.utils import event_is_enabled, get_message_by_url
extension_name = "rorec" extension_name = "rorec"
logger = logger.getChild(extension_name) logger = logger.getChild(extension_name)
channel_id_re = re.compile(r"^<#([0-9]+)>$")
class RoRec(commands.Cog): class RoRec(commands.Cog):
def __init__(self, bot: commands.Bot): def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
slash.get_cog_commands(self)
def description(self): def description(self):
return "Create role-reaction message to give role from a reaction add" return "Create role-reaction message to give role from a reaction add"
@staticmethod @staticmethod
def get_message(session: db.Session, message_id: int, guild_id: int) -> db.RoRec: async def get_message(session: db.Session, ctx: SlashContext, url: str) -> db.RoRec:
m = session.query(db.RoRec).filter(db.RoRec.message == message_id and db.RoRec.guild == guild_id).first() m = session.query(db.RoRec).filter(db.RoRec.message == (await get_message_by_url(ctx, url)).id and
db.RoRec.guild == ctx.guild.id).first()
if not m: if not m:
raise BadArgument() raise BadArgument()
else: else:
return m return m
async def try_emoji(self, ctx: commands.Context, emoji: str): async def try_emoji(self, msg: Message, emoji: str):
try: try:
await ctx.message.add_reaction(emoji) await msg.add_reaction(emoji)
except (HTTPException, NotFound, InvalidArgument): except (HTTPException, NotFound, InvalidArgument):
raise BadArgument() raise BadArgument()
else: else:
await (await ctx.channel.fetch_message(ctx.message.id)).remove_reaction(emoji, self.bot.user) await (await msg.channel.fetch_message(msg.id)).remove_reaction(emoji, self.bot.user)
@commands.group("rorec", pass_context=True) @cog_ext.cog_subcommand(base="rorec", name="new",
description="Create a new role-reaction message on the mentioned channel",
options=[
manage_commands.create_option("title", "The title",
SlashCommandOptionType.STRING, True),
manage_commands.create_option("channel", "The target channel",
SlashCommandOptionType.CHANNEL, True),
manage_commands.create_option("description", "The description",
SlashCommandOptionType.STRING, False),
manage_commands.create_option("one", "If only one role is packable",
SlashCommandOptionType.BOOLEAN, False)
])
@is_enabled() @is_enabled()
@commands.guild_only() @guild_only()
@commands.has_permissions(manage_roles=True) @has_permissions(manage_roles=True)
async def rorec(self, ctx: commands.Context): async def rorec_new(self, ctx: SlashContext, title: str, channel: GuildChannel, description: str = "",
if ctx.invoked_subcommand is None:
await ctx.invoke(self.rorec_help)
@rorec.group("help", pass_context=True)
async def rorec_help(self, ctx: commands.Context):
embed = Embed(title="Role-Reaction help")
embed.add_field(name="new <title> <#channel> [description] [Only one (True/False)]",
value="Create a new role-reaction message on the mentioned channel.\n"
"You can specify a description and if you can pick only one role",
inline=False)
embed.add_field(name="edit <message id> <title> [description}", value="Edit a role-reaction message title and "
"description",
inline=False)
embed.add_field(name="set <message_id> <emoji> <@role1> [@role2] ...",
value="Add/edit a emoji with linked roles", inline=False)
embed.add_field(name="remove <message_id> <emoji>", value="Remove a emoji of a role-reaction message",
inline=False)
embed.add_field(name="reload <message_id>", value="Reload the message and the reactions", inline=False)
embed.add_field(name="delete <message_id>", value="Remove a role-reaction message", inline=False)
await ctx.send(embed=embed)
@rorec.group("new", pass_context=True)
async def rorec_new(self, ctx: commands.Context, title: str, channel: str, description: str = "",
one: bool = False): one: bool = False):
channel = channel_id_re.findall(channel) if not isinstance(channel, TextChannel):
if len(channel) != 1:
raise BadArgument() raise BadArgument()
channel = ctx.guild.get_channel(int(channel[0]))
if not channel:
raise BadArgument()
if description in ["True", "False"]:
one = True if description == "True" else False
description = ""
embed = Embed(title=title, description=description) embed = Embed(title=title, description=description)
embed.add_field(name="Roles", value="No role yet...") embed.add_field(name="Roles", value="No role yet...")
@ -88,12 +68,24 @@ class RoRec(commands.Cog):
s.add(r) s.add(r)
s.commit() s.commit()
s.close() s.close()
await ctx.message.add_reaction("\U0001f44d") await ctx.send(content="\U0001f44d")
@rorec.group("edit", pass_context=True) @cog_ext.cog_subcommand(base="rorec", name="edit",
async def rorec_edit(self, ctx: commands.Context, message_id: int, title: str, description: str = None): description="Edit a role-reaction message title and description",
options=[
manage_commands.create_option("url", "The message url",
SlashCommandOptionType.STRING, True),
manage_commands.create_option("title", "The new title",
SlashCommandOptionType.STRING, True),
manage_commands.create_option("description", "The new description",
SlashCommandOptionType.STRING, False)
])
@is_enabled()
@guild_only()
@has_permissions(manage_roles=True)
async def rorec_edit(self, ctx: SlashContext, url: str, title: str, description: str = ""):
s = db.Session() s = db.Session()
m = self.get_message(s, message_id, ctx.guild.id) m = await self.get_message(s, ctx, url)
s.close() s.close()
message = await ctx.guild.get_channel(m.channel).fetch_message(m.message) message = await ctx.guild.get_channel(m.channel).fetch_message(m.message)
@ -101,31 +93,57 @@ class RoRec(commands.Cog):
embed.title = title embed.title = title
embed.description = description embed.description = description
await message.edit(embed=embed) await message.edit(embed=embed)
await ctx.message.add_reaction("\U0001f44d") await ctx.send(content="\U0001f44d")
@rorec.group("set", pass_context=True) @cog_ext.cog_subcommand(base="rorec", name="set",
async def rorec_set(self, ctx: commands.Context, message_id: int, emoji: str): description="Add/edit a emoji with linked roles",
options=[
manage_commands.create_option("url", "The message url",
SlashCommandOptionType.STRING, True),
manage_commands.create_option("emoji", "The emoji",
SlashCommandOptionType.STRING, True),
manage_commands.create_option("role", "The role",
SlashCommandOptionType.ROLE, True)
])
@is_enabled()
@guild_only()
@has_permissions(manage_roles=True)
async def rorec_set(self, ctx: SlashContext, url: str, emoji: str, role: Role):
await ctx.send(content="\U000023f3")
s = db.Session() s = db.Session()
m = self.get_message(s, message_id, ctx.guild.id) m = await self.get_message(s, ctx, url)
if len(ctx.message.role_mentions) == 0: await ctx.delete()
raise BadArgument() msg = await ctx.channel.send("\U000023f3")
await self.try_emoji(msg, emoji)
await self.try_emoji(ctx, emoji)
data = m.get_data() data = m.get_data()
data[emoji] = list(map(lambda x: x.id, ctx.message.role_mentions)) data[emoji] = list(map(lambda x: x.id, [role]))
m.set_data(data) m.set_data(data)
await self.rorec_update(m) await self.rorec_update(m)
s.commit() s.commit()
s.close() s.close()
await ctx.message.add_reaction("\U0001f44d") await msg.edit(content="\U0001f44d")
@rorec.group("remove", pass_context=True) @cog_ext.cog_subcommand(base="rorec", name="remove",
async def rorec_remove(self, ctx: commands.Context, message_id: int, emoji: str): description="Remove a emoji of a role-reaction message",
options=[
manage_commands.create_option("url", "The message url",
SlashCommandOptionType.STRING, True),
manage_commands.create_option("emoji", "The emoji",
SlashCommandOptionType.STRING, True)
])
@is_enabled()
@guild_only()
@has_permissions(manage_roles=True)
async def rorec_remove(self, ctx: SlashContext, url: str, emoji: str):
await ctx.send(content="\U000023f3")
s = db.Session() s = db.Session()
m = self.get_message(s, message_id, ctx.guild.id) m = await self.get_message(s, ctx, url)
await self.try_emoji(ctx, emoji)
await ctx.delete()
msg = await ctx.channel.send("\U000023f3")
await self.try_emoji(msg, emoji)
data = m.get_data() data = m.get_data()
if emoji not in data: if emoji not in data:
@ -136,24 +154,37 @@ class RoRec(commands.Cog):
await self.rorec_update(m) await self.rorec_update(m)
s.commit() s.commit()
s.close() s.close()
await ctx.message.add_reaction("\U0001f44d") await msg.edit("\U0001f44d")
@rorec.group("reload", pass_context=True) @cog_ext.cog_subcommand(base="rorec", name="reload",
async def rorec_reload(self, ctx: commands.Context, message_id: int): description="Reload the message and the reactions",
options=[manage_commands.create_option("url", "The message url",
SlashCommandOptionType.STRING, True)])
@is_enabled()
@guild_only()
@has_permissions(manage_roles=True)
async def rorec_reload(self, ctx: SlashContext, url: str):
s = db.Session() s = db.Session()
m = self.get_message(s, message_id, ctx.guild.id) m = await self.get_message(s, ctx, url)
await self.rorec_update(m) await self.rorec_update(m)
s.close() s.close()
await ctx.message.add_reaction("\U0001f44d") await ctx.send(content="\U0001f44d")
@rorec.group("delete", pass_context=True) @cog_ext.cog_subcommand(base="rorec", name="delete",
async def rorec_delete(self, ctx: commands.Context, message_id: int): description="Remove a role-reaction message",
options=[manage_commands.create_option("url", "The message link",
SlashCommandOptionType.STRING, True)])
@is_enabled()
@guild_only()
@has_permissions(manage_roles=True)
async def rorec_delete(self, ctx: SlashContext, url: str):
msg = await get_message_by_url(ctx, url)
s = db.Session() s = db.Session()
m = self.get_message(s, message_id, ctx.guild.id) await self.get_message(s, ctx, url)
s.close() s.close()
await (await self.bot.get_channel(m.channel).fetch_message(m.message)).delete() await msg.delete()
await ctx.message.add_reaction("\U0001f44d") await ctx.send(content="\U0001f44d")
async def rorec_update(self, m: db.RoRec): async def rorec_update(self, m: db.RoRec):
channel = self.bot.get_channel(m.channel) channel = self.bot.get_channel(m.channel)
@ -173,6 +204,8 @@ class RoRec(commands.Cog):
value += ", ".join(map(lambda x: self.bot.get_guild(m.guild).get_role(x).mention, data[d])) value += ", ".join(map(lambda x: self.bot.get_guild(m.guild).get_role(x).mention, data[d]))
value += "\n" value += "\n"
await message.add_reaction(d) await message.add_reaction(d)
if not value:
value = "No role yet..."
embed.add_field(name=name, value=value) embed.add_field(name=name, value=value)
await message.edit(embed=embed) await message.edit(embed=embed)

View file

@ -1,8 +1,11 @@
from discord.ext import commands from discord.ext import commands
from discord import Member, VoiceState, Embed, Reaction, Guild from discord import Member, VoiceState, Embed, Reaction, Guild
from discord.ext.commands import CommandNotFound from discord.ext.commands import BadArgument
from discord_slash import SlashContext, cog_ext, SlashCommandOptionType
from discord_slash.utils import manage_commands
from administrator.check import is_enabled from administrator import slash
from administrator.check import is_enabled, guild_only, has_permissions
from administrator.logger import logger from administrator.logger import logger
from administrator.utils import event_is_enabled from administrator.utils import event_is_enabled
@ -21,29 +24,24 @@ class Speak(commands.Cog):
self.last_reaction = None self.last_reaction = None
self.voice_message = None self.voice_message = None
self.last_message = None self.last_message = None
slash.get_cog_commands(self)
def description(self): def description(self):
return "Speech manager" return "Speech manager"
@commands.group("speak", pass_context=True) @cog_ext.cog_subcommand(base="speak", name="setup",
description="Set your current voice channel as the speak channel",
options=[
manage_commands.create_option("strict", "Mute everyone on setup",
SlashCommandOptionType.BOOLEAN, False)
])
@is_enabled() @is_enabled()
@commands.guild_only() @guild_only()
@commands.has_guild_permissions(mute_members=True) @has_permissions(mute_members=True)
async def speak(self, ctx: commands.Context): async def speak_setup(self, ctx: SlashContext, strict: bool = False):
if ctx.invoked_subcommand is None: self.strict = strict
raise CommandNotFound if not ctx.author.voice:
raise BadArgument()
@speak.group("help", pass_context=True)
async def speak_help(self, ctx: commands.Context):
embed = Embed(title="Speak help")
embed.add_field(name="speak setup [strict]",
value="Set your current voice channel as the speak channel", inline=False)
embed.add_field(name="speak mute", value="Mute everyone on the speak channel except you", inline=False)
embed.add_field(name="speak unmute", value="Unmute everyone on the speak channel except you", inline=False)
await ctx.send(embed=embed)
@speak.group("setup", pass_context=True)
async def speak_setup(self, ctx: commands.Context, *args):
self.voice_chan = ctx.author.voice.channel.id self.voice_chan = ctx.author.voice.channel.id
embed = Embed(title="Speak \U0001f508") embed = Embed(title="Speak \U0001f508")
embed.add_field(name="Waiting list \u23f3", value="Nobody", inline=False) embed.add_field(name="Waiting list \u23f3", value="Nobody", inline=False)
@ -57,24 +55,30 @@ class Speak(commands.Cog):
"\u274C Clear the speak\n" "\u274C Clear the speak\n"
"Remove your reaction to remove from list", "Remove your reaction to remove from list",
inline=False) inline=False)
self.voice_message = await ctx.send(embed=embed) self.voice_message = await ctx.channel.send(embed=embed)
for reaction in ["\U0001f5e3", "\u2757", "\u27A1", "\U0001F512", "\U0001F507", "\U0001F50A", "\u274C"]: for reaction in ["\U0001f5e3", "\u2757", "\u27A1", "\U0001F512", "\U0001F507", "\U0001F50A", "\u274C"]:
await self.voice_message.add_reaction(reaction) await self.voice_message.add_reaction(reaction)
self.voice_message = await self.voice_message.channel.fetch_message(self.voice_message.id) self.voice_message = await self.voice_message.channel.fetch_message(self.voice_message.id)
@speak.group("mute", pass_context=True) @cog_ext.cog_subcommand(base="speak", name="mute", description="Mute everyone on the speak channel except you")
async def speak_mute(self, ctx: commands.Context): @is_enabled()
@guild_only()
@has_permissions(mute_members=True)
async def speak_mute(self, ctx: SlashContext):
if not await self.mute(True, ctx.author): if not await self.mute(True, ctx.author):
await ctx.message.add_reaction("\u274C") await ctx.send(content="\u274C")
else: else:
await ctx.message.add_reaction("\U0001f44d") await ctx.send(content="\U0001f44d")
@speak.group("unmute", pass_context=True) @cog_ext.cog_subcommand(base="speak", name="unmute", description="Unmute everyone on the speak channel except you")
async def speak_unmute(self, ctx: commands.Context): @is_enabled()
@guild_only()
@has_permissions(mute_members=True)
async def speak_unmute(self, ctx: SlashContext):
if not await self.mute(False, ctx.author): if not await self.mute(False, ctx.author):
await ctx.message.add_reaction("\u274C") await ctx.send(content="\u274C")
else: else:
await ctx.message.add_reaction("\U0001f44d") await ctx.send(content="\U0001f44d")
@commands.Cog.listener() @commands.Cog.listener()
async def on_voice_state_update(self, member: Member, before: VoiceState, after: VoiceState): async def on_voice_state_update(self, member: Member, before: VoiceState, after: VoiceState):
@ -91,7 +95,7 @@ class Speak(commands.Cog):
(after is not None and after.channel is not None and after.channel.id != self.voice_chan): (after is not None and after.channel is not None and after.channel.id != self.voice_chan):
await member.edit(mute=False) await member.edit(mute=False)
async def cog_after_invoke(self, ctx: commands.Context): async def cog_after_invoke(self, ctx: SlashContext):
await ctx.message.delete(delay=30) await ctx.message.delete(delay=30)
@commands.Cog.listener() @commands.Cog.listener()

View file

@ -1,8 +1,10 @@
from urllib.parse import urlencode from urllib.parse import urlencode
from discord import Embed
from discord.ext import commands from discord.ext import commands
from discord_slash import cog_ext, SlashContext, SlashCommandOptionType
from discord_slash.utils import manage_commands
from administrator import slash
from administrator.check import is_enabled from administrator.check import is_enabled
from administrator.logger import logger from administrator.logger import logger
@ -14,26 +16,16 @@ class TeX(commands.Cog):
def __init__(self, bot: commands.Bot): def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
self.polls = {} self.polls = {}
slash.get_cog_commands(self)
def description(self): def description(self):
return "Render TeX formula" return "Render TeX formula"
@commands.group("tex", pass_context=True) @cog_ext.cog_slash(name="tex", description="Render a TeX formula", options=[
manage_commands.create_option("formula", "The TeX formula", SlashCommandOptionType.STRING, True)])
@is_enabled() @is_enabled()
async def tex(self, ctx: commands.Context): async def tex(self, ctx: SlashContext, formula: str):
if ctx.message.content.count("`") == 2: await ctx.send(content=f"https://chart.apis.google.com/chart?cht=tx&chs=40&{urlencode({'chl': formula})}")
start = ctx.message.content.find("`")
end = ctx.message.content.find("`", start+1)
command = ctx.message.content[start+1:end]
await ctx.send(f"https://chart.apis.google.com/chart?cht=tx&chs=40&{urlencode({'chl': command})}")
elif ctx.invoked_subcommand is None:
await ctx.invoke(self.tex_help)
@tex.group("help", pass_context=True)
async def tex_help(self, ctx: commands.Context):
embed = Embed(title="TeX help")
embed.add_field(name="tex \`formula\`", value="Render a TeX formula", inline=False)
await ctx.send(embed=embed)
def setup(bot): def setup(bot):

View file

@ -5,9 +5,12 @@ from time import mktime
from discord import Embed, Forbidden, HTTPException from discord import Embed, Forbidden, HTTPException
from discord.ext import commands, tasks from discord.ext import commands, tasks
from discord.ext.commands import BadArgument from discord.ext.commands import BadArgument
from discord_slash import SlashContext, cog_ext, SlashCommandOptionType
from discord_slash.utils import manage_commands
from feedparser import parse from feedparser import parse
import db import db
from administrator import slash
from administrator.check import is_enabled from administrator.check import is_enabled
from administrator.logger import logger from administrator.logger import logger
@ -21,25 +24,14 @@ class Tomuss(commands.Cog):
def __init__(self, bot: commands.Bot): def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
self.tomuss_loop.start() self.tomuss_loop.start()
slash.get_cog_commands(self)
def description(self): def description(self):
return "PCP Univ Lyon 1" return "PCP Univ Lyon 1"
@commands.group("tomuss", pass_context=True) @cog_ext.cog_subcommand(base="tomuss", name="set", description="Set your tomuss RSS feed", options=[
@is_enabled() manage_commands.create_option("url", "The RSS URL", SlashCommandOptionType.STRING, True)])
async def tomuss(self, ctx: commands.Context): async def tomuss_set(self, ctx: SlashContext, url: str):
if ctx.invoked_subcommand is None:
await ctx.invoke(self.tomuss_help)
@tomuss.group("help", pass_context=True)
async def tomuss_help(self, ctx: commands.Context):
embed = Embed(title="Tomuss help")
embed.add_field(name="tomuss set <url>", value="Set your tomuss RSS feed", inline=False)
embed.add_field(name="tomuss unset", value="Unset your tomuss RSS feed", inline=False)
await ctx.send(embed=embed)
@tomuss.group("set", pass_context=True)
async def tomuss_set(self, ctx: commands.Context, url: str):
if not url_re.fullmatch(url): if not url_re.fullmatch(url):
raise BadArgument() raise BadArgument()
entries = parse(url).entries entries = parse(url).entries
@ -59,10 +51,10 @@ class Tomuss(commands.Cog):
s.commit() s.commit()
s.close() s.close()
await ctx.message.add_reaction("\U0001f44d") await ctx.channel.send(f"Tomuss RSS set for {ctx.author.mention} \U0001f44d")
@tomuss.group("unset", pass_context=True) @cog_ext.cog_subcommand(base="tomuss", name="unset", description="Unset your tomuss RSS feed")
async def tomuss_unset(self, ctx: commands.Context): async def tomuss_unset(self, ctx: SlashContext):
s = db.Session() s = db.Session()
t = s.query(db.Tomuss).get(ctx.author.id) t = s.query(db.Tomuss).get(ctx.author.id)
if not t: if not t:
@ -70,7 +62,7 @@ class Tomuss(commands.Cog):
s.delete(t) s.delete(t)
s.commit() s.commit()
s.close() s.close()
await ctx.message.add_reaction("\U0001f44d") await ctx.send(content="\U0001f44d")
@tasks.loop(minutes=5) @tasks.loop(minutes=5)
async def tomuss_loop(self): async def tomuss_loop(self):

View file

@ -1,9 +1,11 @@
from datetime import datetime from datetime import datetime
from discord import Embed, Member, Guild from discord import Embed, Guild
from discord.ext import commands from discord.ext import commands
from discord.ext.commands import BadArgument, CommandError from discord_slash import cog_ext, SlashContext, SlashCommandOptionType
from discord_slash.utils import manage_commands
from administrator import slash
from administrator.check import is_enabled from administrator.check import is_enabled
from administrator.logger import logger from administrator.logger import logger
@ -15,34 +17,17 @@ logger = logger.getChild(extension_name)
class Utils(commands.Cog): class Utils(commands.Cog):
def __init__(self, bot: commands.Bot): def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
slash.get_cog_commands(self)
def description(self): def description(self):
return "Some tools" return "Some tools"
@commands.group("utils", pass_context=True)
@is_enabled()
async def utils(self, ctx: commands.Context):
if ctx.invoked_subcommand is None:
await ctx.invoke(self.utils_help)
@utils.group("help", pass_context=True)
async def utils_help(self, ctx: commands.Context):
embed = Embed(title="Utils help")
try:
if await self.eval.can_run(ctx):
embed.add_field(name="eval \`\`\`code\`\`\`", value="Execute some code", inline=False)
except CommandError:
pass
embed.add_field(name="ping", value="Return the ping with the discord API", inline=False)
embed.add_field(name="info [@user]", value="Show information on guild or user specified", inline=False)
await ctx.send(embed=embed)
@commands.group("eval", pass_context=True) @commands.group("eval", pass_context=True)
@commands.is_owner() @commands.is_owner()
async def eval(self, ctx: commands.Context): async def eval(self, ctx: commands.Context):
start = ctx.message.content.find("```") start = ctx.message.content.find("```python")
end = ctx.message.content.find("```", start+3) end = ctx.message.content.find("```", start+9)
command = ctx.message.content[start+3:end] command = ctx.message.content[start+9:end]
try: try:
exec("async def __ex(self, ctx):\n" + command.replace("\n", "\n ")) exec("async def __ex(self, ctx):\n" + command.replace("\n", "\n "))
out = str(await locals()["__ex"](self, ctx)) out = str(await locals()["__ex"](self, ctx))
@ -55,20 +40,20 @@ class Utils(commands.Cog):
except Exception as e: except Exception as e:
await ctx.send(f"```{e.__class__.__name__}: {e}```") await ctx.send(f"```{e.__class__.__name__}: {e}```")
@commands.group("ping", pass_context=True) @cog_ext.cog_slash(name="ping", description="Return the ping with the discord API")
@is_enabled() @is_enabled()
async def ping(self, ctx: commands.Context): async def ping(self, ctx: SlashContext):
start = datetime.now() start = datetime.now()
msg = await ctx.send(f"Discord WebSocket latency: `{round(self.bot.latency*1000)}ms`") content = f"Discord WebSocket latency: `{round(self.bot.latency*1000)}ms`"
await msg.edit(content=msg.content+"\n"+f"Bot latency: `{round((msg.created_at - start).microseconds/1000)}ms`") await ctx.send(content=content)
await ctx.edit(content=content+"\n"+f"Bot latency: `{round((datetime.now() - start).microseconds/1000)}ms`")
@commands.group("info", pass_context=True) @cog_ext.cog_slash(name="info",
description="Show information on guild or user specified",
options=[manage_commands.create_option("user", "A user", SlashCommandOptionType.USER, False)])
@is_enabled() @is_enabled()
async def info(self, ctx: commands.Context): async def info(self, ctx: SlashContext, user: SlashCommandOptionType.USER = None):
if len(ctx.message.mentions) > 1: if user:
raise BadArgument()
elif ctx.message.mentions:
user: Member = ctx.message.mentions[0]
embed = Embed(title=str(user)) embed = Embed(title=str(user))
embed.set_author(name="User infos", icon_url=user.avatar_url) embed.set_author(name="User infos", icon_url=user.avatar_url)
embed.add_field(name="Display name", value=user.display_name) embed.add_field(name="Display name", value=user.display_name)
@ -129,11 +114,11 @@ class Utils(commands.Cog):
embed.add_field(name="Shard ID", value=guild.shard_id) embed.add_field(name="Shard ID", value=guild.shard_id)
embed.add_field(name="Created at", value=guild.created_at) embed.add_field(name="Created at", value=guild.created_at)
await ctx.send(embed=embed) await ctx.send(embeds=[embed])
@commands.group("about", pass_context=True) @cog_ext.cog_slash(name="about", description="Show information about the bot")
@is_enabled() @is_enabled()
async def about(self, ctx: commands.Context): async def about(self, ctx: SlashContext):
embed = Embed(title=self.bot.user.display_name, description=self.bot.description) embed = Embed(title=self.bot.user.display_name, description=self.bot.description)
embed.set_author(name="Administrator", icon_url=self.bot.user.avatar_url, url="https://github.com/flifloo") embed.set_author(name="Administrator", icon_url=self.bot.user.avatar_url, url="https://github.com/flifloo")
flifloo = self.bot.get_user(177393521051959306) flifloo = self.bot.get_user(177393521051959306)
@ -142,9 +127,12 @@ class Utils(commands.Cog):
value=(await self.bot.application_info()).owner.display_name) value=(await self.bot.application_info()).owner.display_name)
embed.add_field(name="Guilds", value=str(len(self.bot.guilds))) embed.add_field(name="Guilds", value=str(len(self.bot.guilds)))
embed.add_field(name="Extensions", value=str(len(self.bot.extensions))) embed.add_field(name="Extensions", value=str(len(self.bot.extensions)))
embed.add_field(name="Commands", value=str(len(self.bot.all_commands))) embed.add_field(name="Commands", value=str(len(self.bot.all_commands)+len(slash.commands)))
embed.add_field(name="Latency", value=f"{round(self.bot.latency*1000)} ms") embed.add_field(name="Latency", value=f"{round(self.bot.latency*1000)} ms")
await ctx.send(embed=embed) await ctx.send(embeds=[embed])
def cog_unload(self):
slash.remove_cog_commands(self)
def setup(bot): def setup(bot):
@ -165,3 +153,6 @@ def teardown(bot):
logger.error(f"Error unloading: {e}") logger.error(f"Error unloading: {e}")
else: else:
logger.info(f"Unload successful") logger.info(f"Unload successful")

View file

@ -1,9 +1,11 @@
from discord import Embed, Forbidden, Member, Guild from discord import Embed, Forbidden, Member, Guild
from discord.ext import commands from discord.ext import commands
from discord.ext.commands import BadArgument from discord.ext.commands import BadArgument
from discord_slash import cog_ext, SlashCommandOptionType, SlashContext
from discord_slash.utils import manage_commands
from administrator import db from administrator import db, slash
from administrator.check import is_enabled from administrator.check import is_enabled, guild_only, has_permissions
from administrator.logger import logger from administrator.logger import logger
from administrator.utils import time_pars, seconds_to_time_string from administrator.utils import time_pars, seconds_to_time_string
@ -14,12 +16,13 @@ logger = logger.getChild(extension_name)
class Warn(commands.Cog): class Warn(commands.Cog):
def __init__(self, bot: commands.Bot): def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
slash.get_cog_commands(self)
def description(self): def description(self):
return "Send warning to user and make custom action after a number of warn" return "Send warning to user and make custom action after a number of warn"
@staticmethod @staticmethod
async def check_warn(ctx: commands.Context, target: Member): async def check_warn(ctx: SlashContext, target: Member):
s = db.Session() s = db.Session()
c = s.query(db.Warn).filter(db.Warn.guild == ctx.guild.id, db.Warn.user == target.id).count() c = s.query(db.Warn).filter(db.Warn.guild == ctx.guild.id, db.Warn.user == target.id).count()
a = s.query(db.WarnAction).filter(db.WarnAction.guild == ctx.guild.id, db.WarnAction.count == c).first() a = s.query(db.WarnAction).filter(db.WarnAction.guild == ctx.guild.id, db.WarnAction.count == c).first()
@ -32,96 +35,72 @@ class Warn(commands.Cog):
elif a.action == "mute": elif a.action == "mute":
pass # Integration with upcoming ban & mute extension pass # Integration with upcoming ban & mute extension
@staticmethod @cog_ext.cog_subcommand(base="warn", name="add", description="Send a warn to a user", options=[
def get_target(ctx: commands.Context, user: str) -> Member: manage_commands.create_option("user", "The user", SlashCommandOptionType.USER, True),
users = {str(m): m for m in ctx.guild.members} manage_commands.create_option("description", "The description", SlashCommandOptionType.STRING, True)
if user not in users: ])
raise BadArgument()
return users[user]
@commands.group("warn", pass_context=True)
@is_enabled() @is_enabled()
@commands.guild_only() @guild_only()
#@commands.has_permissions(manage_roles=True, kick_members=True, ban_members=True, mute_members=True) @has_permissions(kick_members=True, ban_members=True, mute_members=True)
async def warn(self, ctx: commands.Context): async def warn_add(self, ctx: SlashContext, user: Member, description: str):
if ctx.invoked_subcommand is None:
await ctx.invoke(self.warn_help)
@warn.group("help", pass_context=True)
async def warn_help(self, ctx: commands.Context):
embed = Embed(title="Warn help")
embed.add_field(name="add <user> <description>", value="Send a warn to a user", inline=False)
embed.add_field(name="remove <user> <number>", value="Remove a number of warn to a user", inline=False)
embed.add_field(name="purge <user>", value="Remove all warn of a user", inline=False)
embed.add_field(name="list [user#discriminator|actions]", value="List warn of the guild or a specified user\n"
"If you specify `actions` instead of a user, "
"all the actions of the guild will be listed",
inline=False)
embed.add_field(name="action <count> <action>", value="Set an action for a count of warn\n"
"Actions: `mute<time>`, `kick`, `ban[time]`, `nothing`\n"
"Time: `?D?H?M?S`\n"
"Example: `action 1 mute1H` to mute someone for one hour "
"after only one war\n"
"or `action 3 ban1D` to ban someone for one day after 3 "
"warns", inline=False)
await ctx.send(embed=embed)
@warn.group("add", pass_context=True)
async def warn_add(self, ctx: commands.Context, user: str, description: str):
target = self.get_target(ctx, user)
s = db.Session() s = db.Session()
s.add(db.Warn(target.id, ctx.author.id, ctx.guild.id, description)) s.add(db.Warn(user.id, ctx.author.id, ctx.guild.id, description))
s.commit() s.commit()
s.close() s.close()
try: try:
embed = Embed(title="You get warned !", description="A moderator send you a warn", color=0xff0000) embed = Embed(title="You get warned !", description="A moderator send you a warn", color=0xff0000)
embed.add_field(name="Description:", value=description) embed.add_field(name="Description:", value=description)
await target.send(embed=embed) await user.send(embed=embed)
except Forbidden: except Forbidden:
await ctx.send("Fail to send warn notification to the user, DM close :warning:") await ctx.send(content="Fail to send warn notification to the user, DM close :warning:")
else: else:
await ctx.message.add_reaction("\U0001f44d") await ctx.send(content="\U0001f44d")
await self.check_warn(ctx, target) await self.check_warn(ctx, user)
@warn.group("remove", pass_context=True) @cog_ext.cog_subcommand(base="warn", name="remove", description="Remove a number of warn to a user", options=[
async def warn_remove(self, ctx: commands.Context, user: str, number: int): manage_commands.create_option("user", "The user", SlashCommandOptionType.USER, True),
target = self.get_target(ctx, user) manage_commands.create_option("number", "The warn to remove", SlashCommandOptionType.INTEGER, True)
])
@is_enabled()
@guild_only()
@has_permissions(kick_members=True, ban_members=True, mute_members=True)
async def warn_remove(self, ctx: SlashContext, user: Member, number: int):
s = db.Session() s = db.Session()
ws = s.query(db.Warn).filter(db.Warn.guild == ctx.guild.id, db.Warn.user == target.id).all() ws = s.query(db.Warn).filter(db.Warn.guild == ctx.guild.id, db.Warn.user == user.id).all()
if number <= 0 or number > len(ws): if number <= 0 or number > len(ws):
raise BadArgument() raise BadArgument()
s.delete(ws[number-1]) s.delete(ws[number-1])
s.commit() s.commit()
s.close() s.close()
await ctx.message.add_reaction("\U0001f44d") await ctx.send(content="\U0001f44d")
@warn.group("purge", pass_context=True) @cog_ext.cog_subcommand(base="warn", name="purge", description="Remove all warn of a user", options=[
async def warn_purge(self, ctx: commands.Context, user: str): manage_commands.create_option("user", "The user", SlashCommandOptionType.USER, True)])
target = self.get_target(ctx, user) @is_enabled()
@guild_only()
@has_permissions(kick_members=True, ban_members=True, mute_members=True)
async def warn_purge(self, ctx: SlashContext, user: Member):
s = db.Session() s = db.Session()
for w in s.query(db.Warn).filter(db.Warn.guild == ctx.guild.id, db.Warn.user == target.id).all(): for w in s.query(db.Warn).filter(db.Warn.guild == ctx.guild.id, db.Warn.user == user.id).all():
s.delete(w) s.delete(w)
s.commit() s.commit()
s.close() s.close()
await ctx.message.add_reaction("\U0001f44d") await ctx.send(content="\U0001f44d")
@warn.group("list", pass_context=True) @cog_ext.cog_subcommand(base="warn", name="list", description="List warn of the guild or a specified user",
async def warn_list(self, ctx: commands.Context, user: str = None): options=[
manage_commands.create_option("user", "The user", SlashCommandOptionType.USER, False)
])
@is_enabled()
@guild_only()
@has_permissions(kick_members=True, ban_members=True, mute_members=True)
async def warn_list(self, ctx: SlashContext, user: Member = None):
s = db.Session() s = db.Session()
embed = Embed(title="Warn list") embed = Embed(title="Warn list")
ws = {} ws = {}
if user:
if user == "actions": ws[user.id] = s.query(db.Warn).filter(db.Warn.guild == ctx.guild.id, db.Warn.user == user.id).all()
embed.title = "Actions list"
for a in s.query(db.WarnAction).filter(db.WarnAction.guild == ctx.guild.id).order_by(db.WarnAction.count)\
.all():
action = f"{a.action} for {seconds_to_time_string(a.duration)}" if a.duration else a.action
embed.add_field(name=f"{a.count} warn(s)", value=action, inline=False)
elif user:
target = self.get_target(ctx, user)
ws[target.id] = s.query(db.Warn).filter(db.Warn.guild == ctx.guild.id, db.Warn.user == target.id).all()
else: else:
for w in s.query(db.Warn).filter(db.Warn.guild == ctx.guild.id).all(): for w in s.query(db.Warn).filter(db.Warn.guild == ctx.guild.id).all():
if w.user not in ws: if w.user not in ws:
@ -134,10 +113,45 @@ class Warn(commands.Cog):
for w in ws[u]] for w in ws[u]]
embed.add_field(name=self.bot.get_user(u), value="\n".join(warns), inline=False) embed.add_field(name=self.bot.get_user(u), value="\n".join(warns), inline=False)
await ctx.send(embed=embed) await ctx.send(embeds=[embed])
@warn.group("action", pass_context=True) @cog_ext.cog_subcommand(base="warn", name="actions", description="List all the actions of the guild")
async def warn_action(self, ctx: commands.Context, count: int, action: str): @is_enabled()
@guild_only()
@has_permissions(kick_members=True, ban_members=True, mute_members=True)
async def warn_actions(self, ctx: SlashContext):
s = db.Session()
embed = Embed(title="Warn list")
ws = {}
embed.title = "Actions list"
for a in s.query(db.WarnAction).filter(db.WarnAction.guild == ctx.guild.id).order_by(db.WarnAction.count) \
.all():
action = f"{a.action} for {seconds_to_time_string(a.duration)}" if a.duration else a.action
embed.add_field(name=f"{a.count} warn(s)", value=action, inline=False)
s.close()
for u in ws:
warns = [f"{self.bot.get_user(w.author).mention} - {w.date.strftime('%d/%m/%Y %H:%M')}```{w.description}```"
for w in ws[u]]
embed.add_field(name=self.bot.get_user(u), value="\n".join(warns), inline=False)
await ctx.send(embeds=[embed])
@cog_ext.cog_subcommand(base="warn", name="action", description="Set an action for a count of warn", options=[
manage_commands.create_option("count", "The number of warns", SlashCommandOptionType.INTEGER, True),
manage_commands.create_option("action", "The action", SlashCommandOptionType.STRING, True, [
manage_commands.create_choice("mute", "mute"),
manage_commands.create_choice("kick", "kick"),
manage_commands.create_choice("ban", "ban"),
manage_commands.create_choice("nothing", "nothing")
]),
manage_commands.create_option("time", "The duration of the action, ?D?H?M?S",
SlashCommandOptionType.STRING, False)
])
@is_enabled()
@guild_only()
@has_permissions(administrator=True)
async def warn_action(self, ctx: SlashContext, count: int, action: str, time: str = None):
if count <= 0 or\ if count <= 0 or\
(action not in ["kick", "nothing"] and not action.startswith("mute") and not action.startswith("ban")): (action not in ["kick", "nothing"] and not action.startswith("mute") and not action.startswith("ban")):
raise BadArgument() raise BadArgument()
@ -151,14 +165,8 @@ class Warn(commands.Cog):
else: else:
raise BadArgument() raise BadArgument()
else: else:
time = None if time:
if action.startswith("mute"): time = time_pars(time).total_seconds()
time = time_pars(action.replace("mute", "")).total_seconds()
action = "mute"
elif action.startswith("ban"):
if action[3:]:
time = time_pars(action.replace("ban", "")).total_seconds()
action = "ban"
if a: if a:
a.action = action a.action = action
a.duration = time a.duration = time
@ -167,7 +175,7 @@ class Warn(commands.Cog):
s.commit() s.commit()
s.close() s.close()
await ctx.message.add_reaction("\U0001f44d") await ctx.send(content="\U0001f44d")
@commands.Cog.listener() @commands.Cog.listener()
async def on_guild_remove(self, guild: Guild): async def on_guild_remove(self, guild: Guild):

View file

@ -3,6 +3,7 @@ async-timeout==3.0.1
attrs==20.2.0 attrs==20.2.0
chardet==3.0.4 chardet==3.0.4
discord==1.0.1 discord==1.0.1
discord-py-slash-command==1.0.8.5
discord.py==1.5.1 discord.py==1.5.1
feedparser==6.0.2 feedparser==6.0.2
idna==2.10 idna==2.10