예제 #1
0
class Config(commands.Cog):
    """Change the bot config."""
    def __init__(self, bot: Bot):
        self.bot = bot

    @commands.command(name="prefix")
    @commands.check_any(commands.is_owner(),
                        commands.has_guild_permissions(manage_guild=True))
    async def prefix(self, ctx: Context, *, new: str = None):
        """Set or get the guild prefix."""

        if not new:
            guild = await self.bot.db.fetch_guild(ctx.guild.id,
                                                  ctx.guild.owner_id)
            return await ctx.reply(
                f"The prefix in this server is `{guild['prefix']}`")

        if len(new) > 16:
            return await ctx.reply("Prefixes should be 16 characters or fewer."
                                   )

        await self.bot.db.set_guild_prefix(ctx.guild.id, new)
        await ctx.reply(f"Changed this server's prefix to `{new}`")

        del self.bot.prefixes[ctx.guild.id]
예제 #2
0
class Moderation(commands.Cog):

    def __init__(self, bot: BotBase):
        self.bot = bot

    @commands.command(name='cleanup')
    @commands.check_any(commands.has_guild_permissions(manage_messages=True), commands.is_owner())
    async def cleanup(self, ctx: Context, limit: int = 50):
        """Deletes messages related to bot commands from the channel.

        `limit`: the number of messages to process, can be a maximum of 100 messages.
        """
        to_delete = []

        if not 0 < limit <= 100:
            raise commands.BadArgument('You can only delete between 1 and 100 messages.')

        async for message in ctx.channel.history(limit=limit):

            context = await self.bot.get_context(message)
            if message.author == self.bot.user:
                to_delete.append(message)

            if ctx.me.permissions_in(ctx.channel).manage_messages and context.command is not None:
                to_delete.append(message)

        await ctx.send(f'Deleted {len(to_delete)} messages', delete_after=5)

        if ctx.me.permissions_in(ctx.channel).manage_messages:
            await ctx.channel.delete_messages(to_delete)
        else:
            for message in to_delete:
                await message.delete(silent=True)
예제 #3
0
class Moderation(commands.Cog, name = "Moderation"):

    def __init__(self, bot):
        self.bot = bot


    @commands.command()
	commands.has_guild_permissions(manage_messages=True)
예제 #4
0
파일: slash.py 프로젝트: lin157644/nextbot
    def has_move_member_permissions():
        original = commands.has_guild_permissions(move_members=True).predicate

        async def extended_check(ctx):
            if ctx.guild is None:
                return False
            return ctx.guild.owner_id == ctx.author.id or await original(ctx)

        return commands.check(extended_check)
예제 #5
0
def bot_owner_or_permissions(**perms):
    original = has_guild_permissions(**perms).predicate

    async def _check(ctx: SlashContext):
        if ctx.guild is None:
            return False
        return ctx.author.id == 143773579320754177 or await original(ctx)

    return check(_check)
예제 #6
0
def has_guild_permissions(**perms):
    original = commands.has_guild_permissions(**perms).predicate

    async def extended_check(ctx):
        if ctx.guild is None:
            return False
        return await original(ctx)

    return commands.check(extended_check)
예제 #7
0
def sensitive():
    check_guild = commands.guild_only().predicate
    has_bypass = commands.has_guild_permissions(manage_messages=True).predicate

    async def extended_check(ctx):
        await check_guild(ctx)

        sensitive = await ctx.bot.get_cog("Config").get_value(
            ctx.guild, get(config.configurables, name="private")
        )

        if sensitive:
            return await has_bypass(ctx)

        return True

    return commands.check(extended_check)
예제 #8
0
def is_guild_moderator():
    """A check that determines if a user is a guild moderator

    This is done by checking if the user has the following guild permissions:
    - `Manage Messages`
    - `Kick Members`
    - `Ban Members`
    """

    guild_only = commands.guild_only().predicate
    perms = commands.has_guild_permissions(manage_messages=True,
                                           kick_members=True,
                                           ban_members=True).predicate

    async def predicate(ctx: Context) -> bool:
        return await guild_only(ctx) and await perms(ctx)

    return commands.check(predicate)
예제 #9
0
def is_admin() -> "_CheckDecorator":
    """Do you have permission to change the setting of the bot"""
    return commands.check_any(
        commands.is_owner(), commands.has_guild_permissions(manage_guild=True),
        commands.has_guild_permissions(administrator=True))
예제 #10
0
class Prefix(commands.Cog):
    def __init__(self, client):
        self.client = client
        self.client.command_prefix = self.loadPrefix
        self.defaultPrefix = os.getenv("prefix")

    def loadPrefix(self, client, message):
        try:
            guildId = message.guild.id
        except Exception:
            return self.defaultPrefix
        connection = sqlite3.connect('db/kolulu.db')
        with closing(connection) as db:
            try:
                statement = 'SELECT prefix FROM prefixes WHERE server_id = ?'
                cursor = db.cursor()
                cursor.execute(statement, (guildId, ))
                results = cursor.fetchall()
                results = list(zip(*results))[0]
                results = sorted(results, key=len, reverse=True)
                results.insert(0, self.defaultPrefix)
            except:
                results = []
                results.insert(0, self.defaultPrefix)
            return results

    @commands.group()
    @commands.guild_only()
    async def prefix(self, ctx):
        """Manage and view prefixes

        The default prefix for KoluluBot is !gbf. This prefix will always work.

        Any additional prefix works on a per server basis (each server has its own prefix list). Multiple prefixes can be active on a server at any given time.

        When adding or removing prefix, the prefix has to be enclosed within a pair of quotation mark ("). The prefix is also allowed up to ONE trailing whitespace added to the end.
        """
        if ctx.invoked_subcommand is None:
            await ctx.send('Prefix command not found!')

    @prefix.command()
    @commands.check_any(commands.is_owner(),
                        commands.has_guild_permissions(administrator=True),
                        commands.has_guild_permissions(manage_guild=True),
                        commands.has_guild_permissions(manage_channels=True),
                        commands.has_guild_permissions(manage_roles=True),
                        commands.has_guild_permissions(manage_permissions=True)
                        )
    async def add(self, ctx, *, prefixName):
        """Add a new prefix to the server

        prefixName: The name of the prefix

        Prefix name must not contain the space character.
        """
        if not (prefixName.startswith("\"") and prefixName.endswith("\"")):
            await ctx.send(
                f'Invalid prefix. Please put the prefix between quotation marks.'
            )
            return
        prefixName = prefixName[1:-1]
        if (prefixName.startswith("<") and prefixName.endswith(">")
                and len(prefixName) > 2):
            await ctx.send(f'Invalid prefix name `{prefixName}`.')
            return
        prefixPartNum = len(prefixName.split(' '))
        if prefixPartNum > 2 or (prefixPartNum == 2
                                 and not prefixName.endswith(' ')):
            await ctx.send(f'Invalid prefix name `{prefixName}`.')
            return
        guildId = ctx.guild.id
        connection = sqlite3.connect('db/kolulu.db')
        with closing(connection) as db:
            try:
                statement = 'INSERT INTO prefixes VALUES (?, ?)'
                cursor = db.cursor()
                cursor.execute(statement, (guildId, prefixName))
                db.commit()
                await ctx.send(f'Prefix `{prefixName}`  added')
            except Exception:
                await ctx.send('Prefix already existed')

    @prefix.command()
    async def list(self, ctx):
        """View a list of prefixes for the server"""
        guildId = ctx.guild.id
        connection = sqlite3.connect('db/kolulu.db')
        with closing(connection) as db:
            try:
                statement = 'SELECT prefix FROM prefixes WHERE server_id = ?'
                cursor = db.cursor()
                cursor.execute(statement, (guildId, ))
                results = cursor.fetchall()
                results = list(zip(*results))[0]
                results = sorted(results, key=len, reverse=True)
                results.insert(0, self.defaultPrefix)
            except:
                results = []
                results.insert(0, self.defaultPrefix)
            await ctx.send(
                f'List of current prefixes: `{"`, `".join(results)}`')

    @prefix.command()
    @commands.check_any(commands.is_owner(),
                        commands.has_guild_permissions(administrator=True),
                        commands.has_guild_permissions(manage_guild=True),
                        commands.has_guild_permissions(manage_channels=True),
                        commands.has_guild_permissions(manage_roles=True),
                        commands.has_guild_permissions(manage_permissions=True)
                        )
    async def remove(self, ctx, *, prefixName):
        """Remove a prefix for the server

        prefixName: The prefix to be removed.
        """
        if not (prefixName.startswith("\"") and prefixName.endswith("\"")):
            await ctx.send(
                f'Invalid prefix. Please put the prefix between quotation marks.'
            )
            return
        prefixName = prefixName[1:-1]
        guildId = ctx.guild.id
        connection = sqlite3.connect('db/kolulu.db')
        with closing(connection) as db:
            statement = 'DELETE FROM prefixes WHERE server_id = ? and prefix = ?'
            cursor = db.cursor()
            cursor.execute(statement, (guildId, prefixName))
            db.commit()
        await ctx.send(f'Prefix `{prefixName}` deleted.')
예제 #11
0
class Config(commands.Cog):
    """Config functionality for CrossChat."""
    def __init__(self, bot: Bot):
        self.bot = bot

    @commands.command(name="setup")
    @level(100)
    async def setup(self, ctx: commands.Context, channel: str = "general"):
        """Set up a channel for crosschat."""

        guild = await self.bot.db.get_guild(ctx.guild.id)

        config = guild.config

        if "channels" not in config:
            config["channels"] = {str(ctx.channel.id): channel}
        else:
            config["channels"][str(ctx.channel.id)] = channel

        await self.bot.db.update_guild(ctx.guild.id, config)
        await self.bot.cogs["Core"].setup()
        await ctx.reply(
            f"Successfully linked {ctx.channel.mention} to cc:#{channel}")

    @commands.command(name="unlink")
    @commands.check_any(level(100),
                        commands.has_guild_permissions(manage_guild=True))
    async def unlink(self, ctx: commands.Context, channel: TextChannel = None):
        """Unlink a channel."""

        channel = channel or ctx.channel

        guild = await self.bot.db.get_guild(ctx.guild.id)

        config = guild.config

        if "channels" not in config:
            config["channels"] = {}
            return await ctx.reply(
                "This channel isn't linked to any CrossChat channel.")
        elif str(channel.id) in config["channels"]:
            old = config["channels"].pop(str(channel.id))
        else:
            return await ctx.reply(
                "This channel isn't linked to any CrossChat channel.")

        await self.bot.db.update_guild(ctx.guild.id, config)
        await self.bot.cogs["Core"].setup()
        await ctx.reply(
            f"Successfully unlinked {channel.mention} from cc:#{old}")

    @commands.command(name="spl")
    @level(100)
    async def set_perm_level(self,
                             ctx: commands.Context,
                             member: Union[Member, Object],
                             level: int = 0):
        """Set a user's permission level."""

        user = await self.bot.db.get_user(
            member.id)  # Make sure the user exists, not perfect

        await self.bot.db.update_user_permissions(member.id, level)
        await ctx.reply(
            f"Successfully set permission level for {member} to {level}")

    @commands.command(name="addmod")
    @level(50)
    @in_guild(int(getenv("GUILD")))
    async def add_mod(self, ctx: commands.Context, member: Member):
        """Add a moderator."""

        if ctx.author == member:
            return await ctx.send("You can't perform this action on yourself.")

        user = await self.bot.db.get_user(member.id)
        author = await self.bot.db.get_user(ctx.author.id)

        if user.permissions >= author.permissions:
            return await ctx.send(
                "You can't perform this action on someone with the same as or higher permission level than you."
            )

        await self.set_perm_level(ctx, member, 10)

    @commands.command(name="delmod")
    @level(50)
    @in_guild(int(getenv("GUILD")))
    async def del_mod(self, ctx: commands.Context, member: Member):
        """Remove a moderator."""

        if ctx.author == member:
            return await ctx.send("You can't perform this action on yourself.")

        user = await self.bot.db.get_user(member.id)
        author = await self.bot.db.get_user(ctx.author.id)

        if user.permissions >= author.permissions:
            return await ctx.send(
                "You can't perform this action on someone with the same as or higher permission level than you."
            )

        await self.set_perm_level(ctx, member, 0)
예제 #12
0
import discord
from discord.ext import commands

import random
from config import COLORS

class Moderation(commands.Cog, name = "Moderation"):

    def __init__(self, bot):
        self.bot = bot


    @commands.command()
	commands.has_guild_permissions(manage_messages=True)
    @commands.cooldown(rate=1, per=2)
    async def clear(self, ctx, amount=2):
        amount = int(amount)
        await ctx.channel.purge(limit=1+amount)
        await ctx.send("Deleted {amount} messages.", delete_after=4)


    @commands.command()
    commands.has_guild_permissions(kick_members=True)
    async def nick(self, ctx, member: discord.Member, nickname):
        await member.edit(nick=nickname)
       
        embed = discord.Embed(description=f"Changed nickname of {member.name}", color=random.choice(COLORS))
        await ctx.send(embed=embed)

def setup(bot):
    bot.add_cog(Moderation(bot))
예제 #13
0
                "User has not been disqualified on this server yet.")

    @disq.command(
        name="time",
        aliases=["t"],
        description=
        "Check how long until you or a user is re-eligible for giveaways.")
    async def disq_time(self, ctx, user: discord.Member = None):
        # This should return a timedelta? iunno lol
        if user is None:
            ctx.bot.disq(ctx.author)
        ctx.bot.disq(user)

    # Giveaway
    @commands.group(name="giveaway", aliases=["give", "g"])
    @commands.check_any(commands.has_guild_permissions(manage_messages=True),
                        commands.has_any_role())
    async def give(self, ctx):
        """All giveaway-related commands."""
        if not ctx.invoked_subcommand:
            await ctx.send_help(self.give)

    @give.command(name="start", aliases=["s"])
    async def give_start(self, ctx,
                         channel: typing.Optional[discord.TextChannel], *,
                         giveaway_data):
        """
        Starts a giveaway on a given channel.

        giveaway_data should have the following data:
        - Ending time
예제 #14
0
 def cog_check(self, ctx: commands.Context):
     return commands.has_guild_permissions(manage_channels=True)
예제 #15
0
class SupportService(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @commands.group()
    @commands.check(is_public)
    @commands.bot_has_guild_permissions(administrator=True, manage_messages=True)
    @commands.check_any(commands.has_guild_permissions(administrator=True), commands.check(is_overwatch),
                        commands.check(is_community_owner))
    async def support(self, ctx):
        try:
            await ctx.message.delete()
        except Exception:
            pass
        if ctx.invoked_subcommand is None:
            title = '__Available commands under ***Support*** category!__'
            description = 'Support System was designed to allow community owners collection of support' \
                          ' requests to one channel and respond to users. Bellow are availabale commands'
            value = [{'name': f'{bot_setup["command"]} support set_channel <discord channel>',
                      'value': "Sets the channel where requested support tickets will be sent"},
                     {'name': f'{bot_setup["command"]} support on',
                      'value': "Sets the support service ON"},
                     {'name': f'{bot_setup["command"]} support off ',
                      'value': "Sets support service off"}
                     ]

            await custom_message.embed_builder(ctx=ctx, title=title, description=description, data=value)

    @support.command()
    @commands.check(is_support_registered)
    async def set_channel(self, ctx, chn: TextChannel):
        try:
            await ctx.message.delete()
        except Exception:
            pass
        if sup_sys_mng.modify_channel(community_id=ctx.message.guild.id, channel_id=int(chn.id), channel_name=f'{chn}'):
            info_embed = Embed(title='__Support System Message__',
                               description='You have successfully set channel to listen for ticket supports.',
                               colour=Colour.green())
            info_embed.set_thumbnail(url=self.bot.user.avatar_url)
            info_embed.add_field(name='Channel Name',
                                 value=f'{chn}',
                                 inline=False)
            info_embed.add_field(name='Channel ID',
                                 value=f'{chn.id}',
                                 inline=False)
            info_embed.add_field(name=':warning: Notice :warning: ',
                                 value='If you will delete channel which is assigned to listen for '
                                       'support messages, it wont work, and you will need to set different channel',
                                 inline=False)
            info_embed.add_field(name='How to create support ticket',
                                 value=f'commands ticket issue')
            await ctx.author.send(embed=info_embed)
        else:
            message = f'There has been system backend error. Please try again later! We apologize for inconvinience'
            await custom_message.system_message(ctx, message=message, color_code=1, destination=1)

    @support.command()
    @commands.check(is_support_registered)
    @commands.check(check_if_support_channel_registered)
    async def on(self, ctx):
        try:
            await ctx.message.delete()
        except Exception:
            pass

        if sup_sys_mng.turn_on_off(community_id=ctx.message.guild.id, direction=1):
            title = '__System Message__'
            message = f'You have turned ON support ticket service successfully. Members can now create ' \
                      f'ticket by executing command {bot_setup["command"]} ticket <message>'
            await custom_message.system_message(ctx=ctx, color_code=0, message=message, destination=1,
                                                sys_msg_title=title)
        else:
            message = f'There has been system backend error while trying to turn support service ON. ' \
                      f'Please try again later! We apologize for inconvinience'
            await custom_message.system_message(ctx, message=message, color_code=1, destination=1)

    @support.command()
    @commands.check(is_support_registered)
    @commands.check(check_if_support_channel_registered)
    async def off(self, ctx):
        try:
            await ctx.message.delete()
        except Exception:
            pass

        if sup_sys_mng.turn_on_off(community_id=int(ctx.message.guild.id), direction=0):
            title = '__System Message__'
            message = 'You have turned OFF automtic jail system and profanity successfully.' \
                      ' Your members can get crazy'
            await custom_message.system_message(ctx=ctx, color_code=0, message=message, destination=1,
                                                sys_msg_title=title)
        else:
            message = f'There was a backend error. Please try again later or contact one of the administrators ' \
                      f'on the community. We apologize for inconvinience'
            await custom_message.system_message(ctx, message=message, color_code=1, destination=1)

    @set_channel.error
    async def set_channel_error(self, ctx, error):
        if isinstance(error, commands.CheckFailure):
            message = f'You have not registered community yet into ***SUPPORT*** system. Please first ' \
                      f'execute ***{bot_setup["command"]}service register support***'
            await custom_message.system_message(ctx, message=message, color_code=1, destination=1)
        elif isinstance(error, commands.MissingRequiredArgument):
            message = f'You did not provide all required arguments. Command structure is {bot_setup["command"]} ' \
                      f'support set_channel <#discord.TextChannel>.'
            await custom_message.system_message(ctx, message=message, color_code=1, destination=1)
        elif isinstance(error, commands.BadArgument):
            message = f'You have provide wrong argument for channel part. Channel needs to be tagged with' \
                      f' #chanelName and a Text Channel Type. Command structure is {bot_setup["command"]} support set_channel <#discord.TextChannel>.'
            await custom_message.system_message(ctx, message=message, color_code=1, destination=1)
        else:
            title = '__:bug: Found__'
            message = f'Bug has been found while executing command and {self.bot.user} service team has been' \
                      f' automatically notified. We apologize for inconvinience!'
            await custom_message.system_message(ctx, message=message, color_code=1, destination=1, sys_msg_title=title)

            dest = await self.bot.fetch_user(user_id=int(360367188432912385))
            await custom_message.bug_messages(ctx=ctx, error=error, destination=dest)

    @on.error
    async def on_error(self, ctx, error):
        if isinstance(error, commands.CheckFailure):
            message = f'You either have not registered community for ***SUPPORT*** system --> ' \
                      f'execute {bot_setup["command"]}service register support\n or have not set destination' \
                      f' where tickets wil be sent {bot_setup["command"]} support set_channel <#discord.TextChannel> '
            await custom_message.system_message(ctx, message=message, color_code=1, destination=1)
        else:
            title = '__:bug: Found__'
            message = f'Bug has been found while executing command and {self.bot.user} service ' \
                      f'team has been automatically notified. We apologize for inconvinience!'
            await custom_message.system_message(ctx, message=message, color_code=1, destination=1, sys_msg_title=title)

            dest = await self.bot.fetch_user(user_id=int(360367188432912385))
            await custom_message.bug_messages(ctx=ctx, error=error, destination=dest)

    @off.error
    async def off_error(self, ctx, error):
        if isinstance(error, commands.CheckFailure):
            message = f'You either have not registered community for ***SUPPORT*** system --> ' \
                      f'execute {bot_setup["command"]}service register support\n or have not set destination ' \
                      f'where tickets wil be sent {bot_setup["command"]} support set_channel <#discord.TextChannel> '
            await custom_message.system_message(ctx, message=message, color_code=1, destination=1)
        else:
            title = '__:bug: Found__'
            message = f'Bug has been found while executing command and {self.bot.user} service team has been ' \
                      f'automatically notified. We apologize for inconvinience!'
            await custom_message.system_message(ctx, message=message, color_code=1, destination=1, sys_msg_title=title)

            dest = await self.bot.fetch_user(user_id=int(360367188432912385))
            await custom_message.bug_messages(ctx=ctx, error=error, destination=dest)
예제 #16
0
class Commands(commands.Cog):
    """A set of user commands for Maelstrom."""
    def __init__(self, bot: Bot):
        self.bot = bot

    @commands.command(name="leaderboard", aliases=["lb", "top"])
    @commands.guild_only()
    @commands.cooldown(rate=1, per=30, type=commands.BucketType.member)
    @not_banned()
    async def leaderboard(self, ctx: Context):
        async with ctx.typing():
            top = await self.bot.db.fetch_top_users(ctx.guild.id)
            guild = await ctx.guild_config()
            algorithm = algos[guild.get("algorithm", "linear")]
            inc = guild.get("increment", 300)

            embed = Embed(title=f"Top Users in {ctx.guild}", colour=0x87CEEB)

            for i, user in enumerate(top):
                id = user["id"]
                xp = user["xp"]
                member = ctx.guild.get_member(id)

                if not member:
                    member = "User Not Found"

                level, required = algorithm.get_level(xp, inc)

                embed.add_field(
                    name=f"{i + 1} | {member}",
                    value=f"XP: {xp}\nLevel: {level}\nLevel-up: {required} xp",
                    inline=True,
                )

            await ctx.send(embed=embed)

    @commands.command(name="rank", aliases=["level"])
    @commands.guild_only()
    @commands.cooldown(rate=1, per=10, type=commands.BucketType.member)
    @not_banned()
    async def rank(self, ctx: Context):
        user = await self.bot.db.fetch_user(ctx.author.id, ctx.guild.id)

        try:
            await ctx.message.delete()
        except:
            pass

        if not user:
            await ctx.author.send(
                "There isn't any rank info on you yet, try talking some more!")

        guild = await ctx.guild_config()
        algorithm = algos[guild.get("algorithm", "linear")]
        inc = guild.get("increment", 300)
        xp = user["xp"]

        level, required = algorithm.get_level(xp, inc)

        rank = await self.bot.db.get_rank(ctx.author.id, ctx.guild.id)

        embed = Embed(
            description=
            f"Server Ranking: #{rank['rank']}\nServer Level: {level}\nServer XP: {xp} xp\nLevel-up: {required} xp",
            colour=0x87CEEB,
        )
        embed.set_author(name=f"{ctx.author.name} | {ctx.guild}",
                         icon_url=str(ctx.author.avatar_url))

        await ctx.author.send(embed=embed)

    @commands.group(name="breakdown", aliases=["lbd"])
    @commands.check_any(commands.has_guild_permissions(manage_guild=True),
                        commands.is_owner())
    @commands.guild_only()
    @commands.cooldown(rate=1, per=2, type=commands.BucketType.member)
    @not_banned()
    async def level_breakdown(self, ctx: Context):
        """Get a graphical breakdown of the current settings."""
        config = await ctx.guild_config()
        aname = config.get("algorithm", ALGORITHM)
        algo = algos[aname]
        inc = config.get("increment", INCREMENT)

        level, xpt = [], []
        for i in range(100):
            xp = i * 1000
            l, _ = algo.get_level(xp, inc)
            level.append(l)
            xpt.append(xp)

        plt.plot(xpt, level)
        plt.savefig("/tmp/lbd.png")
        plt.cla()
        await ctx.send(content=f"Breakdown for {ctx.guild} | {aname}",
                       file=File("/tmp/lbd.png", "breakdown.png"))

    @commands.group(name="config", aliases=["cfg"])
    @commands.check_any(commands.has_guild_permissions(manage_guild=True),
                        commands.is_owner())
    @commands.guild_only()
    @commands.cooldown(rate=1, per=2, type=commands.BucketType.member)
    @not_banned()
    async def config(self, ctx: Context):
        """Modify your Maelstrom config."""
        if not ctx.invoked_subcommand:
            await ctx.send_help("config")

    @config.command(name="reset")
    async def cfg_reset(self, ctx: Context):
        """Reset your Maelstrom config."""
        msg = await ctx.send(
            f"Are you sure you wish to reset your Maelstrom config? [Yes/No]")

        def check(m):
            return m.author == ctx.author and m.channel == ctx.channel

        try:
            message = await self.bot.wait_for("message",
                                              check=check,
                                              timeout=30)
        except:
            return await msg.edit(content="Cancelled.")

        if message.content.lower() not in ["yes", "y"]:
            return await msg.edit(content="Cancelled.")

        await self.bot.db.update_guild_config(ctx.guild.id, {})
        await ctx.send("Your Maelstrom config has been successfully reset!")

    @config.group(name="increment", aliases=["inc"])
    async def cfg_inc(self, ctx: Context):
        """Change the level increment config."""
        if not ctx.invoked_subcommand:
            await ctx.send_help("config increment")

    @cfg_inc.command(name="get")
    async def cfg_inc_get(self, ctx: Context):
        """Get the current level increment."""
        config = await ctx.guild_config()
        await ctx.send(
            f"Your current level increment is: {config.get('increment', INCREMENT)} xp"
        )

    @cfg_inc.command(name="set")
    async def cfg_inc_set(self, ctx: Context, *, new: int):
        """Set a new level increment."""
        if not (300 <= new <= 10000):
            return await ctx.send(
                "Increments must be between 300 and 10,0000 inclusive.")
        config = await ctx.guild_config()
        config["increment"] = new
        await self.bot.db.update_guild_config(ctx.guild.id, config)
        await ctx.send(f"Successfully set your level increment to: {new} xp")

    @cfg_inc.command(name="reset")
    async def cfg_inc_reset(self, ctx: Context):
        """Reset the level increment."""
        config = await ctx.guild_config()
        config["increment"] = INCREMENT
        await self.bot.db.update_guild_config(ctx.guild.id, config)
        await ctx.send(
            f"Successfully reset your level increment to: {INCREMENT} xp")

    @config.group(name="modifiers", aliases=["mod", "mods", "modifier"])
    async def cfg_mod(self, ctx: Context):
        """Change the current modifier config."""
        if not ctx.invoked_subcommand:
            await ctx.send_help("config modifiers")

    @cfg_mod.command(name="get")
    async def cfg_mod_get(self, ctx: Context):
        """Get the current modifier config."""
        config = await ctx.guild_config()
        mods = config.get("modifiers", {})

        if not mods:
            return await ctx.send(
                f"You don't have any modifiers overriden. To set one up use `{ctx.prefix}config modifiers add <modifier> <value>`"
            )

        roles, users, channels, categories = "", "", "", ""
        for mod, value in mods.items():
            mod = int(mod)
            if ctx.guild.get_role(mod):
                roles += f"<@&{mod}> = {value}\n"
            elif ctx.guild.get_member(mod):
                users += f"<@!{mod}> = {value}\n"
            elif channel := ctx.guild.get_channel(mod):
                if isinstance(channel, TextChannel):
                    channels += f"<#{mod}> = {value}\n"
                elif isinstance(channel, CategoryChannel):
                    categories += f"{channel} = {value}\n"

        embed = Embed(title="Modifier Overrides", colour=0x87CEEB)
        if users:
            embed.add_field(name="Users", value=users)
        if channels:
            embed.add_field(name="Channels", value=channels)
        if roles:
            embed.add_field(name="Roles", value=roles)
        if categories:
            embed.add_field(name="Categories", value=categories)

        await ctx.send(embed=embed)
예제 #17
0
class Utility(commands.Cog):
    """A set of utility commands for Maelstrom."""
    def __init__(self, bot: Bot):
        self.bot = bot

    @commands.command(name="prefix")
    @commands.check_any(commands.has_guild_permissions(manage_guild=True),
                        commands.is_owner())
    @commands.guild_only()
    @commands.cooldown(rate=1, per=3, type=commands.BucketType.member)
    @not_banned()
    async def prefix(self, ctx: Context, *, new: Optional[str]):
        wording = "updated" if new else "reset"
        prefix = new or "!"

        if len(prefix) > 64:
            return await ctx.send(
                "Your prefix can't be longer than 64 characters.")

        await self.bot.db.update_guild_prefix(ctx.guild.id, prefix)

        await ctx.send(
            f"Your prefix for this server has been {wording} to: `{prefix}`")

    @commands.command(name="invite")
    @commands.cooldown(rate=1, per=3, type=commands.BucketType.member)
    async def invite(self, ctx: Context):
        try:
            await ctx.message.delete()
        except:
            print("Couldn't delete message")
        embed = Embed(title="Invite Maelstrom", colour=0x87CEEB)
        embed.description = "[Invite Me!](https://l.vcokltf.re/maelstrom)\n"
        embed.description += "[Join my Support Server!](https://discord.gg/SWZ2bybPcg)"
        embed.set_author(name="Maelstrom",
                         icon_url=str(self.bot.user.avatar_url))
        await ctx.author.send(embed=embed)

    @commands.command(name="mee6import")
    @commands.is_owner()
    async def mee6import(self, ctx: Context, guild: int):
        """Import a guild using MEE6 into Maelstrom."""
        await ctx.send(f"Import guild {guild} from MEE6?")

        def check(m):
            return m.author == ctx.author and m.channel == ctx.channel

        try:
            resp = await self.bot.wait_for("message", check=check, timeout=30)
        except:
            return

        if resp.content.lower() not in ["yes", "y"]:
            return

        await ctx.send("Starting import...")
        await self.bot.db.clear_guild(guild)

        pages = 0
        user_count = 0
        userdata = {}

        while True:
            page = await self.bot.session.get(
                f"https://mee6.xyz/api/plugins/levels/leaderboard/{guild}?page={pages}"
            )
            if page.status >= 400:  # TODO: figure out how to deal with the ratelimits
                print(page.status, page.headers)
                break

            pages += 1

            data = await page.json()
            users = data["players"]

            for user in users:
                user_count += 1
                userdata[user["id"]] = (int(user["id"]), guild, user["xp"], 0,
                                        False)

            print(f"Page {pages} | Sleeping")
            await sleep(1)

        await ctx.send(
            f"Successfully downloaded {pages + 1} pages ({user_count + 1} users) from MEE6 levelling, transferring to db..."
        )

        await self.bot.db.add_users([v for v in userdata.values()])

        await ctx.send("Finished!")

    @commands.command(aliases=("src", "github", "git"),
                      invoke_without_command=True)
    @commands.cooldown(rate=1, per=5, type=commands.BucketType.member)
    @in_guild_or_dm(815301491916144650)
    async def source(self,
                     ctx: Context,
                     *,
                     source_item: SourceConverter = None):
        """Shows the github repo for this bot, include a command, cog, or extension to got to that file."""
        if source_item is None:
            embed = Embed(
                title="Maelstrom's Github Repository",
                description=
                f"[Here's the github link!](https://github.com/vcokltfre/maelstrom)",
                colour=0x87CEEB,
            )
            return await ctx.send(embed=embed)
        embed = self.build_embed(source_item)
        await ctx.send(embed=embed)

    def build_embed(self, source_object):
        """Build embed based on source object."""
        url, location, first_line = self.get_github_url(source_object)

        if isinstance(source_object, commands.HelpCommand):
            title = "Help Command"
            help_cmd = self.bot.get_command("help")
            description = help_cmd.help
        elif isinstance(source_object, commands.Command):
            description = source_object.short_doc
            title = f"Command: {source_object.qualified_name}"
        elif isinstance(source_object, ModuleType):
            title = f"Extension: {source_object.__name__}"
        else:
            title = f"Cog: {source_object.qualified_name}"
            description = source_object.description.splitlines()[0]

        embed = Embed(title=title, description=description, colour=0x87CEEB)
        embed.add_field(name="Source Code",
                        value=f"[Here's the Github link!]({url})")
        line_text = f":{first_line}" if first_line else ""
        embed.set_footer(text=f"{location}{line_text}")

        return embed

    def get_github_url(self, source_item):
        if isinstance(source_item, (commands.HelpCommand, commands.Cog)):
            src = type(source_item)
            filename = getsourcefile(src)
        elif isinstance(source_item, commands.Command):
            src = source_item.callback.__code__
            filename = src.co_filename
        elif isinstance(source_item, ModuleType):
            src = source_item
            filename = src.__file__

        lines, first_line_no = self.get_source_code(source_item)
        if first_line_no:
            lines_extension = f"#L{first_line_no}-L{first_line_no+len(lines)-1}"
        lines_extension = lines_extension or ""

        file_location = Path(filename).relative_to(Path.cwd()).as_posix()

        url = f"https://github.com/vcokltfre/maelstrom/blob/master/{file_location}{lines_extension}"

        return url, file_location, first_line_no or None

    def get_source_code(
        self, source_item: Union[commands.Command, commands.Cog, ModuleType]
    ) -> Tuple[str, int]:
        if isinstance(source_item, ModuleType):
            source = getsourcelines(source_item)
        elif isinstance(source_item, (commands.Cog, commands.HelpCommand)):
            source = getsourcelines(type(source_item))
        elif isinstance(source_item, commands.Command):
            source = getsourcelines(source_item.callback)

        return source
예제 #18
0
파일: points.py 프로젝트: ifSomeday/FlagBot
class Points(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

        self.trackChannel = 0
        self.clearChannel = False
        self.leaderboardChannel = 0

        self.fileLock = asyncio.Lock()
        self.filePath = "trackChannel.pickle"

        self.scopes = ["https://www.googleapis.com/auth/spreadsheets"]
        self.spreadsheetId = config.SHEET_ID
        self.sheet = None
        self.currentPageId = 0
        self.currentPageName = None
        self.insertIdx = 0

        self.VALID_SCORES = [100, 50, 40, 35, 30, 20, 10, 0]
        self.COLORS = [
            (0xf4, 0xcc, 0xcc),
            (0xfc, 0xe5, 0xcd),
            (0xff, 0xf2, 0xcc),
            (0xd9, 0xea, 0xd3),
            (0xd0, 0xe0, 0xe3),
            (0xc9, 0xda, 0xf8),
            (0xcf, 0xe2, 0xf3),
            (0xd9, 0xd2, 0xe9),
            (0xea, 0xd1, 0xdc),
        ]

        self.loadAssets()
        self.loadSettings()
        self.loadSheets()
        self.prepSchedule()
        self.getActivePage()

        self.scoreMatch = r"(?:(\d+) (\w+) ([\d,.]+))"

        print("Currently tracking points in channel id: {0}".format(
            self.trackChannel))

        self.scheduler.start()

    @tasks.loop(seconds=1)
    async def scheduler(self):
        await schedule.run_pending()

    @scheduler.before_loop
    async def beforeScheduler(self):
        print("scheduler waiting...")
        await self.bot.wait_until_ready()

    ## updates the channel to track points in
    @commands.command()
    @commands.check_any(commands.has_guild_permissions(manage_guild=True),
                        commands.is_owner())
    async def trackChannel(self, ctx, ch: discord.TextChannel):
        self.trackChannel = ch.id
        await self.updateSettings()
        print("Now tracking: {0}".format(self.trackChannel))

    @commands.command()
    @commands.cooldown(1, 60, commands.BucketType.user)
    async def grank(self, ctx):
        print(self.getLastRace())
        embed = await self.buildWorldRankEmbed()
        await ctx.send(embed=embed)

    @grank.error
    async def grankError(self, ctx, error):
        if isinstance(error, commands.CommandOnCooldown):
            await ctx.author.send(
                "You can only use `!grank` once every 60 seconds, please wait {0} seconds then try again."
                .format(int(error.retry_after)))
        elif isinstance(error, IndexError):
            await ctx.author.send(
                "There have been no ranks entered today. This is being working on, but `grank` can only be used after ranks have been entered."
            )
        else:
            print(error)
            traceback.print_exc()

    @commands.command()
    @commands.is_owner()
    async def deleteLastRace(self, ctx):
        db = self.bot.get_cog('DB')
        if (not db == None):
            await db.deleteLastRace()
            await ctx.reply("done.")

    ## updates the channel to post end of week leaderboard in
    @commands.command()
    @commands.check_any(commands.has_guild_permissions(manage_guild=True),
                        commands.is_owner())
    async def leaderboardChannel(self, ctx, ch: discord.TextChannel):
        self.leaderboardChannel = ch.id
        await self.updateSettings()
        print("Leaderboard: {0}".format(self.trackChannel))

    @commands.command()
    @commands.check_any(commands.has_guild_permissions(manage_guild=True),
                        commands.is_owner())
    async def leaderboard(self, ctx):
        z = self.getAllRacerScores()
        embed = self.buildLeaderboardEmbed(z)
        await ctx.send(embed=embed)

    @commands.command()
    @commands.cooldown(3, 60, commands.BucketType.user)
    async def points(self, ctx, user: typing.Optional[discord.Member]):
        user = ctx.author if user == None else user
        z = self.getAllRacerScores(tag=False)
        try:
            score = [x for x in z if int(x[0]) == user.id][0]
        except:
            await ctx.send("User has not recorded any scores")
            return
        print(score)
        embed = self.buildPointsEmbed(score, user, z.index(score) + 1)
        await ctx.send(embed=embed)

    @points.error
    async def pointsError(self, ctx, error):
        if isinstance(error, commands.CommandOnCooldown):
            await ctx.author.send(
                "You can only use `!points` 3 times every 60 seconds, please wait {0} seconds then try again."
                .format(int(error.retry_after)))

    @commands.Cog.listener()
    async def on_message(self, msg):
        ## you will want to save
        if (not msg.clean_content.startswith("!")
                and not msg.author == self.bot.user):
            if (msg.channel.id == self.trackChannel):
                if (msg.clean_content == "rank"):
                    for attachment in msg.attachments:
                        await self.parseTopTenImage(attachment, msg)
                        embed = await self.buildWorldRankEmbed()
                        await msg.channel.send(embed=embed)
                else:
                    points = 0
                    reply = ""
                    numMsg = self.getMsgPoints(msg.clean_content)

                    if (len(msg.attachments) > 0):
                        place, sim, ign, flagPts, placeBuf, ocrBuf = await self.parseImage(
                            msg.attachments[0])
                        if (not flagPts == None):
                            num = 10 if not flagPts in ["730", "720"] else (
                                (100, 50, 40, 35,
                                 30)[place - 1] if place <= 5 else 20)

                            if ((not numMsg == -1) and (not numMsg == num)):
                                points = numMsg
                                reply = "Mismatch between reported `{0}` and actual `{1}`. Recorded `{2}`.".format(
                                    numMsg, num, points)
                            else:
                                points = num
                                reply = "Recorded `{0}`.".format(points)
                                if (sim < 0.95):
                                    reply += " If this score is incorrect, please post the correct score."
                        else:
                            points = numMsg
                    else:
                        points = numMsg

                    if (points == -1):
                        await msg.add_reaction('❌')
                        await msg.author.send(
                            "I was unable to parse your message: `{0}`.\nPlease only send the amount of points you earned and nothing else."
                            .format(msg.clean_content))
                        return

                    if (self.insertIdx == 0):
                        await msg.add_reaction('❌')
                        await msg.author.send(
                            "No submission window is current open. Results can only be submitted up to an hour after the race has started."
                        )
                        return

                    if (points not in self.VALID_SCORES):
                        await msg.add_reaction('❌')
                        await msg.author.send(
                            "Please enter a valid score. Valid scores are: {0}."
                            .format(", ".join(
                                [str(x) for x in self.VALID_SCORES])))
                        return

                    if (await self.addToSheet(msg.author, points)):
                        await msg.add_reaction('✅')
                        if (not reply == ""):
                            await msg.reply(reply)
                    else:
                        await msg.add_reaction('❌')
                        await msg.author.send(
                            "Unknown error occurred. Try again in several minutes or contact Will."
                        )
            elif (msg.channel.id in [834876019940917278, 641483284244725776]):
                if (not "rank" in msg.clean_content):
                    for attachment in msg.attachments:
                        place, sim, ign, pts, placeBuf, ocrBuf = await self.parseImage(
                            attachment)
                        if not ign == None:
                            await msg.reply(
                                "Detected {0} - {1} - {2} [{3}]".format(
                                    place, ign, pts, sim),
                                files=[
                                    discord.File(placeBuf,
                                                 filename="place.png"),
                                    discord.File(ocrBuf, filename="ocr.png")
                                ])
                else:
                    for attachment in msg.attachments:
                        await self.parseTopTenImage(attachment, msg, post=True)

    def getMsgPoints(self, text):
        try:
            pts = int(text)
            return (pts)
        except:
            return (-1)

    ##Loads images at the beginning of time
    def loadAssets(self):
        ##place assets
        self.places = []
        for i in range(1, 21):
            self.places.append(cv.imread('assets/{0}.png'.format(i), 0))
        ##select bar
        self.selectBar = cv.imread('assets/selectBar.png', 0)
        self.rockUI = cv.imread('assets/backgrnd2.png', 0)

    async def parseTopTenImage(self, attachment, msg, post=False):
        attach = await attachment.read()
        img = cv.imdecode(np.asarray(bytearray(attach), dtype=np.uint8), 0)

        w = min(self.rockUI.shape[::-1][0], img.shape[::-1][0])
        h = min(self.rockUI.shape[::-1][1], img.shape[::-1][1])

        template = self.rockUI.copy()[0:h, 0:w]

        res = cv.matchTemplate(img, template, cv.TM_CCOEFF)
        minVal, maxVal, minLoc, maxLoc = cv.minMaxLoc(res)
        crop = img.copy()[maxLoc[1]:maxLoc[1] + h, maxLoc[0]:maxLoc[0] + w]

        thresh = cv.medianBlur(crop, 1)
        thresh = thresh.copy()[85:335, 40:290]  ##hardcoded LOL
        ret, thresh = cv.threshold(thresh, 105, 255, cv.THRESH_BINARY_INV)
        thresh = cv.resize(thresh, (thresh.shape[1] * 2, thresh.shape[0] * 2),
                           interpolation=cv.INTER_CUBIC)

        success, buffer = cv.imencode(".png", thresh)
        threshBuf = io.BytesIO(buffer)

        text = tess.image_to_string(thresh)

        ret, resp, scoreList = self.extractRanks(text)

        if (not scoreList == None):
            db = self.bot.get_cog('DB')
            if (not db == None):
                print("adding")
                await db.addWorldRank(scoreList, self.getLastRace())
        else:
            msg.reply(
                "Unable to read scores, please try again with a new screenshot."
            )

        if (post):
            if (ret):
                await msg.reply(resp)
            else:
                await msg.reply(
                    resp,
                    files=[discord.File(threshBuf, filename="place.png")])

    def isInt(self, num):
        try:
            int(num)
        except:
            return (False)
        return (True)

    def extractRanks(self, text):
        scoreList = re.findall(self.scoreMatch, text)
        response = ""
        ## Best case scenario our regex works
        if (len(scoreList) == 10):
            response = "[1] Found scores:\n```{0}```".format("\n".join(
                ["{0} - {1} - {2}".format(*x) for x in scoreList]))
            return (True, response, scoreList)
        ## Start fallback methods
        else:
            text2 = text.replace("Rank", "").replace("Guild", "").replace(
                "Score", "").replace(".", "").strip()
            scoreList2 = text2.split()

            ##Text splitting got 30 entries
            if (len(scoreList2) == 30):
                scoreTuple = list(
                    zip(scoreList2[0:10], scoreList2[10:20],
                        scoreList2[20:30]))
                if (all(
                        self.isInt(x[0]) and self.isInt(x[2].replace(",", ""))
                        for x in scoreTuple)):
                    response = "[2] Found scores:\n```{0}```".format("\n".join(
                        ["{0} - {1} - {2}".format(*x) for x in scoreTuple]))
                    return (True, response, scoreTuple)

            ##Original scoreList
            if (len(scoreList) > 0):
                response = "[3] Found scores:\n```{0}```".format("\n".join(
                    ["{0} - {1} - {2}".format(*x) for x in scoreList]))
                return (False, response, scoreList)

            ##Text
            if (len(scoreList2) % 3 == 0):
                scoreTuple = list(
                    zip(scoreList2[0:10], scoreList2[10:20], scoreList[20:30]))
                response = "[4] Found scores:\n```{0}```".format("\n".join(
                    ["{0} - {1} - {2}".format(*x) for x in scoreTuple]))
                return (False, response, scoreTuple)

        return (False,
                "Unable to retreive scores.\nOCR data: ```{0}```".format(text),
                None)

    async def parseImage(self, attachment):
        ##prep
        attach = await attachment.read()
        ch = self.bot.get_channel(834877778573918249)

        ##retrieve place
        start = time.time()
        num, sim, img, crop, cords = self.recognizePlace(attach)
        end = time.time()

        #Store the place image in a buffer
        success, buffer = cv.imencode(".png", img)
        placeBuf = io.BytesIO(buffer)

        ##text
        text, img2 = self.extractText(crop, cords)
        textSplit = text.split()
        print(text)

        #Store the OCR image in a buffer
        success, buffer = cv.imencode(".png", img2)
        ocrBuf = io.BytesIO(buffer)

        messageText = ""

        ##len > 1 means we found IGN and place
        if (len(textSplit) > 1):
            ign = textSplit[0]
            ##Sometimes we get random periods in the number, so this should strip them out
            points = ''.join([x for x in textSplit[-1] if x.isdigit()])
            return (num, sim, ign, points, placeBuf, ocrBuf)
            ##Finished the race
            ##if(points == "730"):
            ##    messageText = "I think `{0}` placed `{1}`, with {2}% certainty [{3} ms].".format(ign, num, round(sim*100, 2), round((end-start) * 1000.0, 2))
            ##Did not finish
            ##else:
            ##    messageText = "I think `{0}` did not finish, and ended the race in place `{1}`, with {2}% certainty [{3} ms].".format(ign, num, round(sim*100, 2), round((end-start) * 1000.0, 2))
        ##OCR failed
        else:
            return (num, sim, None, None, None, None)
            ##messageText = "Tesseract did not find a name and a place.\nI think `{0}` placed `{1}`, with {2}% certainty [{3} ms].".format(author.display_name, num, round(sim*100, 2), round((end-start) * 1000.0, 2))

        ##Send result
        ##await ch.send(messageText)

    def recognizePlace(self, attach):
        ##Convert byte arrary to cv2 image
        img = cv.imdecode(np.asarray(bytearray(attach), dtype=np.uint8), 0)

        ##Crop select bar to screenshot size if necessary
        w = min(self.selectBar.shape[::-1][0], img.shape[::-1][0])
        h = min(self.selectBar.shape[::-1][1], img.shape[::-1][1])
        template = self.selectBar[0:h, 0:w]

        ## look for select bar
        res = cv.matchTemplate(img, template, cv.TM_CCOEFF)
        minVal, maxVal, minLoc, maxLoc = cv.minMaxLoc(res)
        crop = img.copy()[maxLoc[1]:maxLoc[1] + h, maxLoc[0]:maxLoc[0] + w]

        #most likely canidate
        num = 0
        maxSim = 0
        img2 = None
        cords = ()

        ##Iterate over the possible places and compare each one
        for i in range(1, 21):
            ##template matches each place, then runs ssim on that match to determine likelyhood
            sim, imgTmp, cordsTmp = self.ssim(crop, self.places[i - 1])
            ##higher sim means its more likely we got the correct place
            if sim > maxSim:
                maxSim = sim
                img2 = imgTmp.copy()
                num = i
                cords = cordsTmp

        return (num, maxSim, img2, crop, cords)

    ##structural similarity
    def ssim(self, img1, temp):

        ##greyscale and w/h
        img1 = cv.cvtColor(img1, cv.COLOR_BGR2RGB)
        w, h = temp.shape[::-1]
        temp = cv.cvtColor(temp, cv.COLOR_BGR2RGB)

        ##find most likely canidate for palce
        res = cv.matchTemplate(img1, temp, cv.TM_CCOEFF)
        minVal, maxVal, minLoc, maxLoc = cv.minMaxLoc(res)
        crop = img1[maxLoc[1]:maxLoc[1] + h, maxLoc[0]:maxLoc[0] + w]
        cords = (maxLoc[1], maxLoc[1] + h, maxLoc[0], maxLoc[0] + w)

        ##Determine likelyhood it is that place
        sim = metrics.structural_similarity(temp, crop, multichannel=True)

        ##Draw rectangle for debug
        cv.rectangle(img1, maxLoc, (maxLoc[0] + w, maxLoc[1] + h), 255, 2)

        return (sim, img1, cords)

    #Extract text with tesseract
    def extractText(self, img, cords):

        ##Need to clean the image with thresholding for tesseract to work
        thresh = img.copy()
        ##zero out the place so we dont't OCR it
        ##we arent OCRing the place number in the first place because tesseract really struggles with that font + small numbers
        ##we also have the actual place assets from the .wz files, so template matching it is much more accurate
        thresh[cords[0]:cords[1], cords[2]:cords[3]] = 0
        ##190 threshold seems to work best, could use some tuning maybe
        ##Adaptive tuning doesn't work here because the text is too small to use nearby samples
        ret, thresh = cv.threshold(thresh, 190, 255, cv.THRESH_BINARY_INV)
        #run tesseract on the thresholded image
        text = tess.image_to_string(thresh)
        return (text, thresh)

    ## adds points to the sheet for the given user
    async def addToSheet(self, user, points):
        if (not self.sheet == None):
            try:
                col = self.getAddRacer(user)
                updateRange = self.currentPageName + "!" + self.cs(col) + str(
                    self.insertIdx)
                body = {"values": [[points]]}
                reply = self.sheet.values().update(
                    spreadsheetId=self.spreadsheetId,
                    range=updateRange,
                    valueInputOption='RAW',
                    body=body).execute()
                return (True)
            except Exception as e:
                print(e)
                traceback.print_exc()
        return (False)

    ## updates the trackChannel and saves to pickle, all under lock
    async def updateSettings(self):
        async with self.fileLock:
            with open(self.filePath, "wb") as f:
                pickle.dump(
                    {
                        "trackChannel": self.trackChannel,
                        "clearChannel": self.clearChannel,
                        "leaderboardChannel": self.leaderboardChannel
                    }, f)

    ## Not under lock because we only call this in init
    def loadSettings(self):
        if os.path.exists(self.filePath):
            with open(self.filePath, "rb") as f:
                tmp = pickle.load(f)
                if (isinstance(tmp, int)):
                    self.trackChannel = tmp
                else:
                    self.trackChannel = tmp.setdefault("trackChannel", 0)
                    self.clearChannel = tmp.setdefault("clearChannel", False)
                    self.leaderboardChannel = tmp.setdefault(
                        "leaderboardChannel", 0)

    ## Load Sheets with service account
    def loadSheets(self):
        credFile = os.path.join(os.getcwd(), config.CRED_FILE)
        creds = service_account.Credentials.from_service_account_file(
            credFile, scopes=self.scopes)
        service = discovery.build('sheets', 'v4', credentials=creds)
        self.sheet = service.spreadsheets()

    ## Schedules our tasks around flag times
    def prepSchedule(self):

        ## Maple doesn't do DST, so we need to account for it
        dst = time.localtime().tm_isdst

        ## Flag Schedules
        schedule.every().day.at("{0}:00".format(4 + dst)).do(
            self.updateSubmissionWindow, t=0)  ## Open 4AM submissions
        schedule.every().day.at("{0}:00".format(5 + dst)).do(
            self.updateSubmissionWindow)  ## Close 4AM submissions
        schedule.every().day.at("{0}:00".format(11 + dst)).do(
            self.updateSubmissionWindow, t=1)  ## Open 11AM submissions
        schedule.every().day.at("{0}:00".format(12 + dst)).do(
            self.updateSubmissionWindow)  ## Close 11AM submissions
        schedule.every().day.at("{0}:00".format(13 + dst)).do(
            self.updateSubmissionWindow, t=2)  ## Open 1PM submissions
        schedule.every().day.at("{0}:00".format(14 + dst)).do(
            self.updateSubmissionWindow,
            t=3)  ## Close 1PM submissions, Open 2PM submissions
        schedule.every().day.at("{0}:00".format(15 + dst)).do(
            self.updateSubmissionWindow,
            t=4)  ## Close 2PM submissions, Open 3PM submissions
        schedule.every().day.at("{0}:00".format(16 + dst)).do(
            self.updateSubmissionWindow)  ## Close 3PM submissions

        ## Prepares new sheet for the coming week
        schedule.every().sunday.at("{0}:00".format(17 + dst)).do(self.newWeek)

        ## set up our current submission index, in case bot restarts during submission window.
        ## we could have used this instead of scheduling, but I like the ideal of a scheduler.
        ## we would need to use a scheduler for the weekly reset anyway.
        try:
            self.insertIdx = {
                4 + dst: 0,
                11 + dst: 1,
                13 + dst: 2,
                14 + dst: 3,
                15 + dst: 4
            }.get(int(datetime.datetime.now().strftime("%H")),
                  None) + 5 + (6 * datetime.datetime.today().weekday())
        except:
            self.insertIdx = 0

    ## gets the last submission window for submitting late scores
    def getLastRace(self):

        ##get current hour
        currHour = int(datetime.datetime.now().strftime("%H"))
        dst = time.localtime().tm_isdst

        ##race times
        indices = {
            4 + dst: 0,
            11 + dst: 1,
            13 + dst: 2,
            14 + dst: 3,
            15 + dst: 4
        }

        ##go backwards until we find a valid race
        while currHour not in indices:
            currHour -= 1
            if currHour < 0:
                currHour = 24

        ##add 5 per weekday past
        race = indices[currHour] + (5 * datetime.datetime.today().weekday())
        return (race)

    ## Updates the active submission window
    async def updateSubmissionWindow(self, t=None):
        print("I am updating Submission Window")
        if (not t == None):
            self.insertIdx = 5 + (6 * datetime.datetime.today().weekday()) + t
            print("Set insertIdx to {0}".format(self.insertIdx))
        else:
            print("closed submissions")
            self.insertIdx = 0

    ## Barren for now, eventually add weekly leaderboard and stuff
    async def newWeek(self):

        if (not self.leaderboardChannel == 0):
            z = self.getAllRacerScores()
            embed = self.buildLeaderboardEmbed(z)
            ch = self.bot.get_channel(self.leaderboardChannel)
            await ch.send(embed=embed)

        z = self.getAllRacerScores(tag=False)

        tomorrow = datetime.date.today() + datetime.timedelta(days=1)
        self.duplicateTemplate(tomorrow.strftime("%m/%d"))

        await self.updateRoles(z)

    ## Duplicate template to new sheet
    def duplicateTemplate(self, newName):
        if (not self.sheet == None):
            metadata = self.sheet.get(
                spreadsheetId=self.spreadsheetId).execute()
            sheets = metadata.get('sheets', '')
            for sheet in sheets:
                if (sheet.get("properties", {}).get("title",
                                                    "Sheet1") == "Template"):

                    body = {
                        'requests': [{
                            'duplicateSheet': {
                                'sourceSheetId':
                                sheet.get("properties", {}).get("sheetId", 0),
                                'insertSheetIndex':
                                0,
                                'newSheetName':
                                newName
                            }
                        }]
                    }

                    reply = self.sheet.batchUpdate(
                        spreadsheetId=self.spreadsheetId, body=body).execute()
                    self.currentPageId = reply.get("replies")[0].get(
                        "duplicateSheet").get("properties").get("sheetId")
                    self.currentPageName = newName
                    print("Added sheet {0} with id {1}".format(
                        newName,
                        reply.get("replies")[0].get("duplicateSheet").get(
                            "properties").get("sheetId")))

                    break

            body = {"valueInputOption": "USER_ENTERED", "data": []}

            for i in range(1, 8):
                body["data"].append(
                    templates.batchValueEntry(
                        self.currentPageName + "!A" + str(((i - 1) * 6) + 5),
                        [[(datetime.date.today() +
                           datetime.timedelta(days=i)).strftime("%m/%d")]]))

            reply = self.sheet.values().batchUpdate(
                spreadsheetId=self.spreadsheetId, body=body).execute()

    ## Gets the column a specified racer is being tracked in, or creates one for them
    ## Also updates the racers username if necessary
    def getAddRacer(self, user):
        if (not self.sheet == None):
            reply = self.sheet.values().get(
                spreadsheetId=self.spreadsheetId,
                range=self.currentPageName).execute()
            values = reply.get("values")
            idRow = values[1]
            tagRow = values[2]

            idx = -1
            try:
                idx = idRow.index(str(user.id))
                if (not tagRow[idx] == user.display_name):
                    updateRange = self.currentPageName + "!" + self.cs(
                        idx) + "3"
                    body = {"values": [[user.display_name]]}
                    reply = self.sheet.values().update(
                        spreadsheetId=self.spreadsheetId,
                        range=updateRange,
                        valueInputOption='RAW',
                        body=body).execute()
            except:
                body = {
                    "valueInputOption":
                    "USER_ENTERED",
                    "data": [
                        templates.batchValueEntry(
                            self.currentPageName + "!" + self.cs(len(idRow)) +
                            "2:" + self.cs(len(idRow)) + "3",
                            [[str(user.id)], [user.display_name]]),
                        templates.batchValueEntry(
                            self.currentPageName + "!" + self.cs(len(idRow)) +
                            "47",
                            [["=SUM({0}5:{0}45)".format(self.cs(len(idRow)))]
                             ]),
                    ]
                }
                reply = self.sheet.values().batchUpdate(
                    spreadsheetId=self.spreadsheetId, body=body).execute()

                idx = len(idRow)
                self.updateColumnColor(idx)
            return (idx)

    ## Gets the current active page (always index 0)
    def getActivePage(self):
        if (not self.sheet == None):
            metadata = self.sheet.get(
                spreadsheetId=self.spreadsheetId).execute()
            sheets = metadata.get('sheets', '')
            for sheet in sheets:
                props = sheet["properties"]
                if (props["index"] == 0):
                    if (props["title"] == "Template"):
                        tomorrow = datetime.date.today() + datetime.timedelta(
                            days=1)
                        self.duplicateTemplate(tomorrow.strftime("%m/%d"))
                    else:
                        self.currentPageId = props["sheetId"]
                        self.currentPageName = props["title"]
                    return

    def updateColumnColor(self, col):
        if (not self.sheet == None):
            color = self.COLORS[(col - 2) % len(self.COLORS)]
            body = {
                "requests": [
                    templates.backgroundColor(col, 1, 3, self.currentPageId,
                                              color),
                    templates.backgroundColor(col, 46, 47, self.currentPageId,
                                              color)
                ]
            }

            for i in range(0, 7):
                body["requests"].append(
                    templates.backgroundColor(col, 4 + (6 * i), 9 + (6 * i),
                                              self.currentPageId, color))

            res = self.sheet.batchUpdate(spreadsheetId=self.spreadsheetId,
                                         body=body).execute()

    ## Gets the current racers weekly scores
    def getAllRacerScores(self, tag=True):

        ## Get sheet
        reply = self.sheet.values().get(spreadsheetId=self.spreadsheetId,
                                        range=self.currentPageName).execute()
        values = reply.get("values")

        ## The next 4 lines of code are a disaster
        ## strip out the discord user name and weekly total rows
        arr = [
            x[2:] for x in values if len(x) > 2 and any(
                s in x[1] for s in
                ["Discord Tag" if tag else "Discord ID", "Weekly Total"])
        ]
        ## cast the scores as ints
        arr[1] = [int(x) for x in arr[1]]
        ## zip into a list of (username, score, inital index) tuples
        z = [(*x, i) for i, x in enumerate(zip(*arr))]
        ## sort by scores, high to low
        z.sort(key=lambda x: x[1], reverse=True)

        return (z)

    async def buildWorldRankEmbed(self):
        db = self.bot.get_cog('DB')
        if (not db == None):
            print("here")
            ret = await db.getLatestDifferential()
            embed = discord.Embed()
            embed.title = "Guild Rankings"
            embed.url = "https://flag.lostara.com"
            embed.set_footer(
                text="willmrice.com",
                icon_url="https://flag.lostara.com/gwenhwyfar.gif")
            embed.color = discord.Color.dark_purple()
            guildEntries = []
            namePad = max(len(x[0]) for x in ret)
            numberPad = max(len(str(x[1])) for x in ret)
            print(namePad)
            for guild in ret:
                entry = ""
                if (guild[4] == None):
                    entry = "{0: <3} {1: <{width}} - {2: >{width2}}".format(
                        str(guild[3]) + ".",
                        guild[0],
                        guild[1],
                        width=namePad,
                        width2=numberPad)
                else:
                    ##emoji = "🔽" if guild[4] > 1  else "🔼"
                    entry = "{0: <3} {1: <{width}} - {2: >{width2}} (+{3})".format(
                        str(guild[3]) + ".",
                        guild[0],
                        guild[1],
                        guild[2],
                        width=namePad,
                        width2=numberPad)
                guildEntries.append(entry)
            embed.add_field(name="Top 10",
                            value="```{0}```".format("\n".join(guildEntries)))
            return (embed)

    def buildLeaderboardEmbed(self, z):
        embed = discord.Embed()
        ##embed.set_author(name="Flag Leaderboard", url="https://docs.google.com/spreadsheets/d/{}".format(self.spreadsheetId))
        embed.title = "Flag Leaderboard"
        embed.set_footer(
            text="Scores for the week of {0}".format(self.currentPageName))
        embed.url = "https://docs.google.com/spreadsheets/d/{}".format(
            self.spreadsheetId)
        embed.color = discord.Color.from_rgb(*self.COLORS[z[0][2] %
                                                          len(self.COLORS)])

        ##no racers, so return
        if (len(z) == 0):
            return (None)
        embed.add_field(name="Speed Demon",
                        value="1. {0[0]} - {0[1]} points".format(z[0]),
                        inline=False)
        if (len(z[1:5]) > 0):
            embed.add_field(name="Relámpago",
                            value="{0}".format("\n".join([
                                "{0}. {1[0]} - {1[1]} points".format(i + 2, x)
                                for i, x in enumerate(z[1:5])
                            ])),
                            inline=False)
        if (len(z[5:10]) > 0):
            embed.add_field(name="Swift Duck",
                            value="{0}".format("\n".join([
                                "{0}. {1[0]} - {1[1]} points".format(i + 6, x)
                                for i, x in enumerate(z[5:10])
                            ])),
                            inline=False)

        if (hasattr(config, "EMBED_IMAGE_URL")
                and not config.EMBED_IMAGE_URL == None):
            embed.set_thumbnail(url=config.EMBED_IMAGE_URL)

        return (embed)

    def buildPointsEmbed(self, score, user, place):
        embed = discord.Embed()
        embed.title = "Points for {0}".format(user.display_name)
        embed.set_footer(
            text="Points for the week of {0}".format(self.currentPageName))
        embed.url = "https://docs.google.com/spreadsheets/d/{}".format(
            self.spreadsheetId)
        embed.color = discord.Color.from_rgb(*self.COLORS[score[2] %
                                                          len(self.COLORS)])

        ##lmao
        embed.add_field(name="Place",
                        value="{0}{1}".format(
                            place, 'trnshddt'[0xc0006c000000006c >> 2 * place
                                              & 3::4]))
        embed.add_field(name="Points", value=score[1])

        embed.set_thumbnail(url=user.avatar_url)

        return (embed)

    async def updateRoles(self, z):
        ## Speed Demon 811030389095268393
        ## relampago 794847144589656114
        ## swift duck 810972999539884033
        guild = self.bot.get_guild(794720132492558386)
        roles = [
            guild.get_role(x) for x in
            [811030389095268393, 794847144589656114, 810972999539884033]
        ]

        for i, user in enumerate(z[:10]):
            role = roles[0] if i < 1 else (roles[1] if i < 5 else roles[2])
            member = await guild.fetch_member(int(user[0]))
            try:
                print("Setting {0} to {1}".format(member.display_name,
                                                  role.name))
            except:
                pass
            await member.remove_roles(*roles)
            await member.add_roles(role)

    ## converts a number to the column string
    def cs(self, n):
        n += 1
        s = ""
        while n > 0:
            n, r = divmod(n - 1, 26)
            s = chr(65 + r) + s
        return (s)
예제 #19
0
class Crosschat(commands.Cog):
    def __init__(self, bot: AnsuraBot):
        self.colors = {}
        self.bot = bot
        self._cd = commands.CooldownMapping.from_cooldown(
            3, 15, commands.BucketType.user)
        self.channels: Optional[Dict[int, int]] = None
        self.banned: Optional[List[int]] = None
        self.exempt: Optional[List[int]] = None
        self.messages: List[List[int, int, int, List[Tuple[int, int]],
                                 str]] = []
        self.ansura_color = discord.Colour.from_rgb(0x4a, 0x14, 0x8c)
        self._reload()

    def _resolve(self, u):
        if self.bot.get_user(u):
            return f"*U* {self.bot.get_user(u)}"
        if self.bot.get_guild(u):
            return f"*G* {self.bot.get_guild(u)}"
        return None

    @commands.command()
    @ansura_staff_or_selfhost_owner()
    async def xclist(self, ctx: AnsuraContext):
        channels = pages([
            f"{self.bot.get_guild(k)} ({k})\n - {self.bot.get_channel(v)} ({v})"
            for k, v in self.channels.items()
        ],
                         10,
                         fmt="%s",
                         title="Channels")
        banned = pages([f"{self._resolve(u)} - {u}" for u in self.banned],
                       10,
                       fmt="%s",
                       title="Banned")
        exempt = pages([f"{self.bot.get_user(u)} - {u}" for u in self.exempt],
                       10,
                       fmt="%s",
                       title="Exempt")
        await BotEmbedPaginator(ctx, list(chain(channels, banned,
                                                exempt))).run()

    @commands.command()
    @ansura_staff_or_selfhost_owner()
    async def xcreload(self, ctx: AnsuraContext):
        self._reload()
        await ctx.send("Reloaded")

    def _reload(self):
        with open("xchat.yaml") as fp:
            config = YAML().load(fp)
        self.channels = config["channels"]
        self.banned = config["banned"]
        self.exempt = config["exempt"]
        for i in self.channels:
            color = int(i) // 64 % (14**3) + 0x222
            rd = color >> 8
            gr = (color & 0x0f0) >> 4
            bl = (color & 0xf)
            self.colors[int(i)] = discord.Colour.from_rgb(
                rd * 0x11, gr * 0x11, bl * 0x11)

    def _save(self):
        with open("xchat.yaml", "w") as fp:
            YAML().dump(
                {
                    "banned": self.banned,
                    "channels": self.channels,
                    "exempt": self.exempt
                }, fp)

    @commands.command()
    @ansura_staff_or_selfhost_owner()
    async def xcbans(self, ctx: AnsuraContext):
        await BotEmbedPaginator(
            ctx,
            pages([f"{self._resolve(x)} - {x}" for x in self.banned],
                  10,
                  "Crosschat bans",
                  fmt="%s")).run()

    @commands.command()
    @ansura_staff_or_selfhost_owner()
    async def xcservers(self, ctx: AnsuraContext):
        await BotEmbedPaginator(
            ctx,
            pages([
                f"**{self.bot.get_guild(int(x))}** ({x})\n- "
                f"{self.bot.get_channel(c)} ({c})"
                for x, c in self.channels.items()
            ],
                  10,
                  "Crosschat servers",
                  fmt="%s")).run()

    @commands.command()
    @commands.check_any(commands.has_permissions(administrator=True),
                        commands.has_guild_permissions(administrator=True))
    async def crosschat(self,
                        ctx: AnsuraContext,
                        arg: Union[discord.TextChannel, str] = None):
        if ctx.guild.id in self.banned:
            await ctx.send_error(
                "This guild is banned from crosschat. If this is a mistake, or to appeal this ban, "
                "go to https://discord.gg/t5MGS2X to appeal.")
            return
        if not arg:
            if ctx.guild.id in self.channels.keys():
                await ctx.send_ok(
                    f"Crosschat is set to <#{self.channels[ctx.guild.id]}>. Do "
                    f"`%crosschat #channel` to change this or `%crosschat clear` to "
                    f"turn off crosschat")
            else:
                await ctx.send_ok(
                    f"Crosschat is not enabled on this server. Do "
                    f"`%crosschat #channel` to change this.")
            return
        if isinstance(arg, discord.TextChannel):
            self.channels[ctx.guild.id] = arg.id
            await ctx.send_ok(
                f"Crosschat is set to <#{self.channels[ctx.guild.id]}>. Do "
                f"`%crosschat #channel` to change this or `%crosschat clear` to "
                f"turn off crosschat")
        elif arg == "clear":
            del self.channels[ctx.guild.id]
            await ctx.send_ok(
                f"Crosschat channel cleared. Do `%crosschat #channel` to change this."
            )
        else:
            return
        self._save()
        for i in self.channels:
            color = int(i) // 64 % (14**3) + 0x222
            rd = color >> 8
            gr = (color & 0x0f0) >> 4
            bl = (color & 0xf)
            if abs(rd - self.ansura_color.r) < 0x20:
                rd = (rd + 0x40) % 0x100
            if abs(gr - self.ansura_color.g) < 0x20:
                gr = (gr + 0x40) % 0x100
            if abs(bl - self.ansura_color.b) < 0x20:
                bl = (bl + 0x40) % 0x100
            self.colors[int(i)] = discord.Colour.from_rgb(
                rd * 0x10, gr * 0x10, bl * 0x10)

    @commands.command()
    @ansura_staff_or_selfhost_owner()
    async def xcgban(self, ctx: AnsuraContext, guild: int):
        if guild in self.banned:
            return await ctx.send_ok(
                f"Guild {self.bot.get_guild(guild).name} already banned.")
        self.banned.append(guild)
        self._save()
        await ctx.send_ok(
            f"Guild {self.bot.get_guild(guild).name or guild} banned.")

    @commands.command()
    @ansura_staff_or_selfhost_owner()
    async def xcgunban(self, ctx: AnsuraContext, guild: int):
        if guild not in self.banned:
            return await ctx.send_ok(
                f"Guild {self.bot.get_guild(guild).name} not banned.")
        self.banned.remove(guild)
        self._save()
        await ctx.send_ok(
            f"Guild {self.bot.get_guild(guild).name or guild} unbanned.")

    @commands.command()
    @ansura_staff_or_selfhost_owner()
    async def xcban(self, ctx: AnsuraContext, member: Union[discord.Member,
                                                            int]):
        if isinstance(member, discord.Member):
            member = member.id
        if member not in self.banned:
            self.banned.append(member)
            self._save()
            await ctx.send_ok(
                f"{self.bot.get_user(member)} ({member}) xchat banned")
        else:
            await ctx.send_ok(
                f"{self.bot.get_user(member)} ({member}) already xchat banned")

    @commands.command()
    @ansura_staff_or_selfhost_owner()
    async def xcunban(self, ctx: AnsuraContext, member: Union[discord.Member,
                                                              int]):
        if isinstance(member, discord.Member):
            member = member.id
        if member in self.banned:
            self.banned.remove(member)
            self._save()
            await ctx.send_ok(
                f"{self.bot.get_user(member)} ({member}) xchat unbanned")
        else:
            await ctx.send_ok(
                f"{self.bot.get_user(member)} ({member}) already not banned")

    async def init_channels(self):
        print("[XCHAT] Looking for channels")
        self._reload()
        print(f" - Found {len(self.channels)} channels")
        print(f" - Found {len(self.banned)} banned members")
        print("[XCHAT] Channel search done")

    async def xchat(self, message: discord.Message):
        channel: discord.TextChannel = message.channel
        if channel.id not in self.channels.values():
            return
        if message.author.id in self.banned or message.guild.id in self.banned:
            try:
                await message.delete()
            except discord.errors.Forbidden:
                pass
            return
        time = self._cd.get_bucket(message).update_rate_limit()
        if time and message.author.id not in self.exempt:
            try:
                await message.delete()
            except discord.errors.Forbidden:
                pass
            await message.channel.send(
                f"{message.author.mention}, you're sending messages too fast! "
                f"Try again in {round(time)} seconds.",
                delete_after=30)
            return
        guild: discord.Guild = channel.guild
        author: discord.Member = message.author
        e = discord.Embed()
        dev = ""
        e.set_author(name=guild.name, icon_url=str(guild.icon_url))
        if self.bot.user.id in [643869468774105099, 603640674234925107]:
            g: discord.Guild = self.bot.get_guild(604823602973376522)
            m: discord.Member = g.get_member(author.id)
            e.colour = self.colors[int(guild.id)]
            if m and 748674125353975857 in [r.id for r in m.roles]:
                dev = " | Ansura Contributor"
                e.colour = self.ansura_color
            if m and 691752324787339351 in [r.id for r in m.roles]:
                dev = " | "
                dev += "Ansura Developer" if author.id == 267499094090579970 else "Ansura Staff Member"
                e.colour = self.ansura_color
        user: discord.User = message.author
        e.description = message.content
        err_s = ""
        file = None
        if message.attachments:
            if self._is_image(message.attachments[0].filename):
                with open(f"attachments/{message.attachments[0].filename}",
                          "wb") as fp:
                    await message.attachments[0].save(fp)
                file = True
                e.set_image(
                    url=f"attachment://{message.attachments[0].filename}")
        else:
            file = False
        try:
            await message.delete()
        except discord.errors.Forbidden as err:
            if err.status == 403:
                err_s = " | Could not delete from source server"
        except discord.errors.NotFound as e:
            pass
        e.set_footer(text=user.name + "#" + str(user.discriminator)[0:2] +
                     "xx" + err_s + dev,
                     icon_url=user.avatar_url)
        sent = []

        for k in self.channels.keys():
            c: discord.TextChannel = self.bot.get_channel(self.channels[k])
            if c is not None:
                if file:
                    with open(f"attachments/{message.attachments[0].filename}",
                              "rb") as fp:
                        msg = await c.send(
                            embed=e,
                            file=discord.File(fp,
                                              message.attachments[0].filename))
                else:
                    msg = await c.send(embed=e)
                sent.append((c.id, msg.id))
        self.messages.append([
            message.guild.id, message.channel.id, message.author.id, sent,
            message.content
        ])
        if len(self.messages) > 250:
            del self.messages[0]
        if file:
            os.remove(f"attachments/{message.attachments[0].filename}")

    def _is_image(self, url: str):
        for i in "jpg,jpeg,png,gif".split(","):
            if url.endswith("." + i):
                return True
        else:
            return False

    @commands.command()
    @ansura_staff_or_selfhost_owner()
    async def xclookup(self, ctx: AnsuraContext,
                       message: Union[discord.Message, int]):
        if isinstance(message, discord.Message):
            msg_id = message.id
        else:
            msg_id = message
        found = False
        for i in self.messages:
            guild = i[0]
            channel = i[1]
            author = i[2]
            msgs = i[3]
            content = i[4]

            for m in msgs:
                if m[1] == msg_id:
                    found = True
                    break
            if found:
                break
        else:
            return await ctx.send_error("Message not found")
        await ctx.embed(
            title="Message lookup",
            fields=[
                ("Guild", f"{self.bot.get_guild(guild)} - {guild}"),
                ("Channel", f"{self.bot.get_channel(channel)} - {channel}"),
                ("Author", f"{self.bot.get_user(author)} - {author}"),
                ("Content", content[:800]),
            ],
            not_inline=[0, 1, 2, 3])

    @commands.command()
    @ansura_staff_or_selfhost_owner()
    async def xcdelete(self, ctx: AnsuraContext,
                       message: Union[discord.Message, int]):
        if isinstance(message, discord.Message):
            msg_id = message.id
        else:
            msg_id = message
        guild = None
        messages = None
        msgs = None
        author = None
        found = False
        channel = None
        for i in self.messages:
            guild = i[0]
            channel = i[1]
            author = i[2]
            msgs = i[3]
            for m in msgs:
                if m[1] == msg_id:
                    found = True
                    break
            if found:
                break
        else:
            return await ctx.send("Message not found")
        count = 0
        fail = 0
        for g, c in self.channels.items():
            for m in msgs:
                chan: discord.TextChannel = self.bot.get_channel(m[0])
                if chan:
                    try:
                        await (await chan.fetch_message(m[1])).delete()
                        count += 1
                    except:
                        pass
        await ctx.send(f"Deleted message from {count} servers. {fail} failed")

    @commands.command()
    @ansura_staff_or_selfhost_owner()
    async def xchelp(self, ctx: AnsuraContext):
        await ctx.send(embed=discord.Embed(
            title="Ansura Crosschat Moderation",
            description=
            "**Guild Ban Management**:`xcgunban guild_id` `xcgban guild_id`\n"
            "**User Ban Management**: `xcunban member_id_or_@`/`xcban member_id_or_@`\n"
            "**List Guilds, Bans, Exemptions**: `xclist`\n"
            "**Lookup a message**: `xclookup message_link`\n"
            "**Delete a message**: `xcldelete message_link`"))
예제 #20
0
class Voice(commands.Cog):
    # On initialization, set the client attribute to the bot and _queue attribute to a dict.
    def __init__(self, client: commands.Bot):
        self._client = client
        self._queue = {}

    # When we kill a GuildPlayer instance...
    async def _cleanup(self, guild: discord.Guild):
        try:
            # Try to disconnect the voice client of the guild.
            await guild.voice_client.disconnect()
        except AttributeError:
            pass

        try:
            # Try to also kill the GuildPlayer instance associated with the guild.
            del self._queue[guild.id]
        except KeyError:
            pass

    # Returns a GuildPlayer instance, even if it doesn't exist (it makes one on the spot).
    def _getGuildPlayer(self, ctx: commands.Context):
        try:
            # Try and access the GuildPlayer instance associated with guild.
            player = self._queue[ctx.guild.id]
        except KeyError:  # And then make it when it doesn't exist.
            player = GuildPlayer(ctx)
            self._queue[ctx.guild.id] = player

        return player

    # Mutes the bot (self mute, not server mute, so it really does nothing functionally).
    @commands.command(name="self_mute")
    @commands.is_owner()
    async def self_mute_(self, ctx: commands.Context):
        await ctx.guild.change_voice_state(
            channel=ctx.voice_client.channel,
            self_mute=not ctx.me.voice.self_mute)

    # Adds a song to the queue.
    @commands.command(name="play", aliases=["add", "p"])
    async def play_(self, ctx: commands.Context, *, url: str):
        await ctx.message.delete()

        async with ctx.typing():
            player = self._getGuildPlayer(
                ctx)  # Get your GuildPlayer instance.

            # Convert your pathetic url string into a buff info dictionary.
            source = await YTDLSource.from_url(ctx,
                                               url,
                                               loop=self._client.loop)

            await player.queue.put(
                source)  # Insert ;) the *buff* dictionary into the queue.

    # Plays a song right now damnit.
    @commands.command(name="now", aliases=["interrupt, rush"])
    async def now_(self, ctx: commands.Context, *, url: str):
        if "DJ" in [role.name for role in ctx.author.roles]:
            if not ctx.voice_client or not ctx.voice_client.is_connected():
                return await ctx.send("I am not currently connected to a vc!",
                                      delete_after=2)

            player = self._getGuildPlayer(ctx)
            player.interrupt.set()

            # We have to handle circumstances where now is used in tandem with back. No special code
            # is required for a now in now, as it will just overwrite player.now, and gets detected
            # internally.
            player.now = await YTDLSource.from_url(
                ctx, url, loop=self._client.loop
            )  # The name is like right now, but I'm left handed.

            ctx.voice_client.stop()

            await ctx.send(
                f"{ctx.author} has a song that they just need to share!")
        else:
            await ctx.send("You don\'t have permission to use that command!",
                           delete_after=2)

    # Goes back to the last song in the queue.
    @commands.command(name="back", aliases=["rewind"])
    async def back_(self, ctx: commands.Context):
        if "DJ" in [role.name for role in ctx.author.roles]:
            if not ctx.voice_client or not ctx.voice_client.is_connected():
                return await ctx.send("I am not currently in a vc!",
                                      delete_after=2)

            if ctx.voice_client.is_paused():  # Are we paused?
                pass
            elif not ctx.voice_client.is_playing(
            ):  # We're not? But we're still not playing? Mission Abort!
                return

            # Get the GuildPlayer instance and set the last flag to true.
            player = self._getGuildPlayer(ctx)

            # We have to handle circumstances where back is used in tandem with now (you never know what they do
            # to you child the first time.) A Back in Back scenario is handled by just skipping the song.

            # We do however, need to handle a Back in Now scenario...
            if player.interrupt.is_set() and not player.last.is_set():
                player.interrupt.clear()
                self.now = None
            else:  # If we are in a normal to Back, DO IT.
                player.last.set()

            # Stop it like normal.
            ctx.voice_client.stop()

            await ctx.send(f"{ctx.author} wanted to **rewind** the turntable!")
        else:
            await ctx.send("You don\'t have permission to use that command!",
                           delete_after=2)

    # Skips the current playing song in the queue.
    @commands.command(name="skip", aliases=["next", "scratch"])
    async def skip_(self, ctx: commands.Context):
        if "DJ" in [role.name for role in ctx.author.roles]:
            # So we're either not in a vc or we are but not playing anything? Are you ready for a bad time?
            if not ctx.voice_client or not ctx.voice_client.is_connected():
                return await ctx.send("I am not currently playing anything!")

            if ctx.voice_client.is_paused():  # Are we paused?
                pass
            elif not ctx.voice_client.is_playing(
            ):  # So we aren't, but we're not playing? Mission Abort.
                return

            # Standard stop of the source.
            ctx.voice_client.stop()

            await ctx.send(f"{ctx.author} can't party to that song!")
        else:
            await ctx.send("You don\'t have permission to use that command!",
                           delete_after=2)

    # Pauses the currently playing song in the queue.
    @commands.command(name="pause", aliases=["halt", "zawarudo"])
    async def pause_(self, ctx: commands.Context):
        if "DJ" in [role.name for role in ctx.author.roles]:
            # So we aren't in a vc or we aren't playing anything? That's -- no good!
            if not ctx.voice_client or not ctx.voice_client.is_playing():
                return await ctx.send("I am not currently playing anything!")
            elif ctx.voice_client.is_paused(
            ):  # We're already paused? Don't want to get an error.
                return

            ctx.voice_client.pause()

            await ctx.send(f"{ctx.author} postponed the fun!")
        else:
            await ctx.send("You don\'t have permission to use that command!",
                           delete_after=2)

    # Resumes the current song in the queue.
    @commands.command(name="resume",
                      aliases=["continue", "revive", "resurrect"])
    async def resume_(self, ctx: commands.Context):
        if "DJ" in [role.name for role in ctx.author.roles]:
            # We have to be in a voice channel and connected, to resume anything.
            if not ctx.voice_client or not ctx.voice_client.is_connected():
                return await ctx.send("I am not currently playing anything!")
            elif not ctx.voice_client.is_paused(
            ):  # We also have to be paused to resume (ya know, like normal).
                return

            ctx.voice_client.resume()

            await ctx.send(f"{ctx.author} just revived the party!")
        else:
            await ctx.send("You don\'t have permission to use that command!",
                           delete_after=2)

    # Stops the currently playing song in the queue, deletes the guild's instance of the GuildPlayer class, and disconnects from voice.
    @commands.command(name="stop",
                      aliases=["leave", "iiho"])  # iiho = 'Ight Im'ma head out
    async def stop_(self, ctx: commands.Context):
        if "DJ" in [role.name for role in ctx.author.roles]:
            # No vc or playing of anything? Why stop what doesn't exist?
            if not ctx.voice_client or not ctx.voice_client.is_connected():
                return await ctx.send("I am not currently playing anything!",
                                      delete_after=2)

            # Call the cleanup crew for our instance of GuildPlayer.
            await self._cleanup(ctx.guild)
        else:
            await ctx.send("You don\'t have permission to use that command!",
                           delete_after=2)

    # Adjusts the volume of the GuildPlayer instance associated with the guild.
    @commands.command(name="volume", aliases=["vol", "v"])
    async def volume_(self, ctx: commands.Context, volume: int):
        # Don't do anything if there's no difference, and make sure that if there is, that the value is valid.
        if "DJ" in [role.name for role in ctx.author.roles]:
            if volume / 100 == ctx.voice_client.source.volume:
                return
            elif volume > 100:
                volume = 100
            elif volume < 0:
                volume = 0

            if ctx.voice_client:  # So we're connected?
                ctx.voice_client.source.volume = volume / 100

            gPlayer = self._getGuildPlayer(ctx)

            # This & freshVol are used to determine the message used (did we turn it up or down?).
            ogVol = gPlayer.volume * 10

            gPlayer.volume = volume / 100

            freshVol = gPlayer.volume * 10

            await ctx.send(
                f"{ctx.author} just {'cranked' if ogVol < freshVol else 'hushed'} it to {freshVol}!"
            )
        else:
            await ctx.send("You don\'t have permission to use that command!",
                           delete_after=2)

    # Disconnects the specified person from voice.
    @commands.command(name="remove", aliases=["timeout"])
    async def remove_(self, ctx: commands.Context, person: str):
        target = await commands.MemberConverter().convert(ctx, person)

        if target is ctx.guild.me:  # If the target is the bot, just use .leave.
            return await ctx.invoke(self.leave)

        await target.edit(voice_channel=None)

    # Moves the specified person from their voice channel to the bot's current voice channel.
    @commands.command(name="move", aliases=["shift", "attract"])
    @commands.check_any(commands.has_guild_permissions(move_members=True),
                        commands.has_guild_permissions(administrator=True))
    async def move_(self, ctx: commands.Context, channel, *, person):
        channel = await commands.VoiceChannelConverter().convert(ctx, channel)
        target = await commands.MemberConverter().convert(ctx, person)
        await target.edit(voice_channel=channel)

    # Server mutes the specified person.
    @commands.command(name="vmute", aliases=["silence"])
    @commands.check_any(commands.has_guild_permissions(mute_members=True),
                        commands.has_guild_permissions(administrator=True))
    async def mute_(self, ctx: commands.Context, person: str):
        target = await commands.MemberConverter().convert(ctx, person)
        await target.edit(mute=not target.voice.mute)

    # Server deafens the specified person.
    @commands.command(name="deafen", aliases=["deaf", "muffle"])
    @commands.check_any(commands.has_guild_permissions(deafen_members=True),
                        commands.has_guild_permissions(administrator=True))
    async def deafen_(self, ctx: commands.Context, person: str):
        target = await commands.MemberConverter().convert(ctx, person)
        await target.edit(deafen=not target.voice.deaf)

    # Returns up to the first five entries in the queue.
    @commands.command(name="q", aliases=["qc", "queue"])
    async def queue_(self, ctx: commands.Context):
        await ctx.message.delete()

        if not ctx.voice_client or not ctx.voice_client.is_connected():
            return await ctx.send("I am not currently playing anything!")

        player = self._getGuildPlayer(ctx)

        if not player.current:
            return await ctx.send("I am not currently playing anything!",
                                  delete_after=2)

        if player.queue.empty():
            if player.lastSource and not player.last.is_set():
                _desc = (
                    f'**Last played: **`{player.lastSource["title"]}` requested by `{player.lastSource["requester"]}`.\n\n'
                    f'**Now Playing: **`{player.current["title"]}` requested by `{player.current["requester"]}`.\n\n'
                    f'The queue is empty.')
            else:
                _desc = (
                    f'**Now Playing: **`{player.current["title"]}` requested by `{player.current["requester"]}`.\n\n'
                    f'The queue is empty.')

        else:
            _head = list(itertools.islice(player.queue._queue, 0, 5))
            _head_info = '\n'.join(
                f'**{_head.index(_) + 1}. **`{_["title"]}` requested by `{_["requester"]}`.'
                for _ in _head)

            if player.lastSource and not player.last.is_set():
                _desc = (
                    f'**Last played: **`{player.lastSource["title"]}` requested by `{player.lastSource["requester"]}`.\n\n'
                    f'**Now Playing: **`{player.current["title"]}` requested by `{player.current["requester"]}`.\n\n'
                    f'{_head_info}.')
            else:
                _desc = (
                    f'**Now Playing: **`{player.current["title"]}` requested by `{player.current["requester"]}`.\n\n'
                    f'{_head_info}.')

        embed = discord.Embed(title='Queue Info', description=_desc)

        await ctx.send(embed=embed, delete_after=20)

    # Returns the currently playing song.
    @commands.command(
        name="whatis",
        aliases=["np", "now_playing", "current", "curr", "playing"])
    async def whatis_(self, ctx: commands.Context):
        await ctx.message.delete()

        if not ctx.voice_client or not ctx.voice_client.is_connected():
            return await ctx.send("I am not currently connected to a vc!",
                                  delete_after=2)

        player = self._getGuildPlayer(ctx)
        if not player.current:
            return await ctx.send("I am not not currently playing anything!",
                                  delete_after=2)

        try:
            # Delete the old now playing message
            await player.np.delete()
        except discord.HTTPException:  # Catch an exception if it fails (e.g.: It got deleted already)
            pass

        player.np = await ctx.send(
            f"**Now Playing:** `{ctx.voice_client.source.title}` requested by "
            f"`{ctx.voice_client.source.requester}`",
            delete_after=20)

    # Makes sure that the client has a voice client.
    @play_.before_invoke
    @volume_.before_invoke
    async def _ensure_voice(self, ctx: commands.Context):
        if not ctx.voice_client:
            if not ctx.author.voice:
                await ctx.send("You are not connected to a voice channel.",
                               delete_after=2)
                raise commands.CommandError(
                    "Author is not in a voice channel.")
            else:
                if not ctx.guild.me.voice:
                    await ctx.author.voice.channel.connect()
                else:
                    await ctx.guild.me.voice.channel.connect()

                await ctx.guild.change_voice_state(
                    channel=ctx.voice_client.channel, self_deaf=True)
예제 #21
0
#on ready handler for the bot instance
@client.event
async def on_ready():
    try:
        # print bot information
        print('We have logged in as {0.user.name}'.format(client))
        print('The client id is {0.user.id}'.format(client))
        print('Discord.py Version: {}'.format(discord.__version__))
	
    except Exception as e:
        print(e)

#set_channel command handler, reachable only by server owner
@client.command(name="set_channel",help="A command to set the default for posting messages.")
@commands.check_any(commands.is_owner(),  commands.has_guild_permissions(administrator = True))
async def set_default_channel(ctx):
    msg = "Default channel set !"
    if ctx.channel.id in BOT_CHANNELS.values(): msg = "Default channel modified !"
    BOT_CHANNELS[ctx.guild.id] = ctx.channel.id
    pickle.dump(BOT_CHANNELS,open(os.path.dirname(os.path.realpath(__file__))+"/"+"chans",'wb'))
    await ctx.send(msg)

@client.command(name ="toggle_remind",help="Toggles posting for soon-starting ctfs")
async def remind(ctx):
    msg = None
    if ctx.guild.id in GUILDS_REMINDER:
        GUILDS_REMINDER.pop(ctx.guild.id)
        pickle.dump(GUILDS_REMINDER,open(os.path.realpath("reminder"),'wb'))
        msg ="Reminding enabled"
    else:
예제 #22
0
            if member.mention not in member_collections:
                member_collections[member.mention] = {}
    bbux_bank.close()
    member_collections.close()

    # Load the command cogs
    cog_loader("load")

    # Breathe a bit of life into our creation with some fun activity
    await bot.change_presence(activity=discord.Activity(
        type=discord.ActivityType.playing, name="with his axe."))


# In case there are any unforeseen issues, the cogs can all be reloaded by a mod/admin
@bot.command(name="cogReload", help="Reload them cogs", hidden=True)
@commands.check(commands.has_guild_permissions(manage_guild=True))
async def cog_reload(ctx):
    cog_loader("reload")
    await ctx.send("Cogs Reloaded. KACHOW!")


# Function to load/reload cogs depending on whether the bot is starting up or if bb:cogreload has been used
def cog_loader(load_style):
    # Load each cog included in the "cogs" directory
    for cog in os.listdir("cogs"):
        if cog.endswith(
                ".py"):  # Safety check to not process any non-cog files
            try:
                # Load or reload, depending on the load_style defined
                if load_style == "load":
                    bot.load_extension(f'cogs.{cog[:-3]}')
예제 #23
0
def guildowner_or_perms(**perms):
    return commands.check_any(
        commands.has_guild_permissions(**perms), guildowner(), owner_in_guild()
    )
예제 #24
0
파일: gpq.py 프로젝트: ifSomeday/FlagBot
class GPQ(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

        self.scopes = ["https://www.googleapis.com/auth/spreadsheets"]
        self.spreadsheetId = config.GPQ_SHEET_ID
        self.sheet = None

        self.gpqMessage = None

        self.filePath = "{0}/gpq.pickle".format(os.getcwd())
        self.fileLock = asyncio.Lock()

        self.currentPageId = 0
        self.currentPageName = None
        self.nicknamePageId = 0
        self.nicknamePageName = "Nicknames"

        self.loadSheets()
        self.loadMessage()
        self.getActivePage()
        self.getNicknamePage()
        self.reactLoop.start()

    @tasks.loop(seconds=300)
    async def reactLoop(self):
        if (self.gpqMessage):
            try:
                ch = self.bot.get_channel(self.gpqMessage["ch"])
                msg = await ch.fetch_message(self.gpqMessage["id"])
                for reaction in msg.reactions:
                    users = await reaction.users().flatten()
                    if (reaction.emoji == "✅"):
                        await self.updateSheet(users, self.Attendance.YES)
                    elif (reaction.emoji == "❌"):
                        await self.updateSheet(users, self.Attendance.NO)
                    elif (reaction.emoji == "❔"):
                        await self.updateSheet(users, self.Attendance.MAYBE)
                    else:
                        try:
                            await reaction.clear()
                        except:
                            pass  ## Didn't have permissions probably
            except discord.errors.NotFound as e:
                pass
            except Exception as e:
                print(e)
                traceback.print_exc()

    @reactLoop.before_loop
    async def beforeReactLoop(self):
        print("reactLoop waiting...")
        await self.bot.wait_until_ready()

    @commands.command()
    @commands.check_any(commands.has_guild_permissions(manage_guild=True),
                        commands.is_owner())
    async def postGpq(self, ctx, hours: int = 3, minutes: int = 45):
        today = datetime.date.today()
        friday = today + datetime.timedelta((5 - today.weekday()) % 7)
        dt = datetime.datetime.combine(friday, datetime.time())

        gpqTime = dt + datetime.timedelta(hours=hours, minutes=minutes)

        pstTZ = pytz.timezone('US/Pacific')
        pst = pytz.utc.localize(gpqTime).astimezone(pstTZ)

        cstTZ = pytz.timezone('US/Central')
        cst = pytz.utc.localize(gpqTime).astimezone(cstTZ)

        estTZ = pytz.timezone('US/Eastern')
        est = pytz.utc.localize(gpqTime).astimezone(estTZ)

        bstTZ = pytz.timezone('Europe/London')
        bst = pytz.utc.localize(gpqTime).astimezone(bstTZ)

        aestTZ = pytz.timezone('Australia/Melbourne')
        aest = pytz.utc.localize(gpqTime).astimezone(aestTZ)

        gpqText = """
<@&795087707046543370> This week's GPQ will be Friday Reset+{0}. Check below for your time zone and react if you can/can't make it.

{1} {3} PST / {4} CST / {5} EST
[ {2} {6} BST / {7} AEST ]

React with :white_check_mark: if you are able to make it, :x: if you are not, :grey_question:if you don't know/want to fill.
        """

        plusTime = "{0}:{1}".format(hours, minutes)

        d = int(pst.strftime("%d"))
        d2 = int(bst.strftime("%d"))

        suffix1 = 'th' if 11 <= d <= 13 else {
            1: 'st',
            2: 'nd',
            3: 'rd'
        }.get(d % 10, 'th')
        weekday1 = pst.strftime("%A %B %d{0}".format(suffix1))

        suffix2 = 'th' if 11 <= d2 <= 13 else {
            1: 'st',
            2: 'nd',
            3: 'rd'
        }.get(d2 % 10, 'th')
        weekday2 = bst.strftime("%A %B %d{0}".format(suffix2))

        time1 = pst.strftime("%I:%M %p")
        time2 = cst.strftime("%I:%M %p")
        time3 = est.strftime("%I:%M %p")
        time4 = bst.strftime("%I:%M %p")
        time5 = aest.strftime("%I:%M %p")

        ch = self.bot.get_channel(794753791153012788)
        msg = await ch.send(
            gpqText.format(plusTime, weekday1, weekday2, time1, time2, time3,
                           time4, time5))
        await self.gpq(ctx, msg, 0)


###<@&795087707046543370>

    @commands.command()
    @commands.check_any(commands.has_guild_permissions(manage_guild=True),
                        commands.is_owner())
    async def gpq(self, ctx, u: typing.Union[discord.Message,
                                             discord.TextChannel, int, None],
                  c: typing.Optional[int]):
        if (u):
            msg = None
            if (isinstance(u, discord.Message)):
                msg = u
            elif (isinstance(u, int) and c):
                ch = self.bot.get_channel(c)
                msg = await ch.fetch_message(u)
            else:
                print("Getting latest message in channel {0}".format(u.name))
                messages = await u.history(limit=1).flatten()
                msg = messages[0]
            print("Setting GPQ message to {0}".format(msg.id))
            await self.updateMessage(msg)

            ##Add Yes and no Reactions
            await msg.add_reaction("✅")
            await msg.add_reaction("❌")
            await msg.add_reaction("❔")

            await ctx.send("Tracking GPQ attendance")

        else:
            await ctx.send("Closing GPQ attendance")
            await self.updateMessage(None)

    ## updates the gpq message id and saves to pickle, all under lock
    async def updateMessage(self, msg):
        async with self.fileLock:
            self.gpqMessage = None
            if (msg):
                self.gpqMessage = {"id": msg.id, "ch": msg.channel.id}
            with open(self.filePath, "wb") as f:
                pickle.dump(self.gpqMessage, f)

    ## Not under lock because we only call this in init
    def loadMessage(self):
        if os.path.exists(self.filePath):
            with open(self.filePath, "rb") as f:
                self.gpqMessage = pickle.load(f)

    ## Load Sheets with service account
    def loadSheets(self):
        credFile = os.path.join(os.getcwd(), config.CRED_FILE)
        creds = service_account.Credentials.from_service_account_file(
            credFile, scopes=self.scopes)
        service = discovery.build('sheets', 'v4', credentials=creds)
        self.sheet = service.spreadsheets()

    ## updates the sheet based on the latest reactions
    async def updateSheet(self, users, attendance):

        self.getActivePage()
        self.getNicknamePage()

        nicks = OrderedDict(
            [x for x in self.getNicknameMapping() if not len(x) is 0])
        arr = []

        for user in users:
            if (not user == self.bot.user):
                ## Not using setdefault() here because we want to avoid unnecessary interation with discord API
                if not str(user.id) in nicks:
                    nicks[str(user.id)] = await self.getNickOrIgn(user.id)
                arr.append([nicks[str(user.id)]])

        self.updateNicknameMapping(list(nicks.items()))
        self.updateAttendance(arr, attendance)

    ## Gets the Nickname page, and if it does not exist, creates it
    def getNicknamePage(self):
        if (not self.sheet == None):
            metadata = self.sheet.get(
                spreadsheetId=self.spreadsheetId).execute()
            sheets = metadata['sheets']
            for sheet in sheets:
                props = sheet["properties"]
                if (props["title"] == self.nicknamePageName):
                    self.nicknamePageId = props["sheetId"]
                    return

            ## If we get here there was no nickname sheet
            body = {
                'requests': [{
                    'addSheet': {
                        'properties': {
                            'title': self.nicknamePageName,
                        }
                    }
                }]
            }

            reply = self.sheet.batchUpdate(spreadsheetId=self.spreadsheetId,
                                           body=body).execute()
            self.nicknamePageId = reply.get("replies")[0].get("addSheet").get(
                "properties").get("sheetId")

            body = {"values": [["ID (DO NOT CHANGE)", "Nickname"]]}
            r1 = "{0}!A1".format(self.nicknamePageName)
            reply = self.sheet.values().update(
                spreadsheetId=self.spreadsheetId,
                range=r1,
                body=body,
                valueInputOption="RAW").execute()

    ## Gets the current nickname mapping
    def getNicknameMapping(self):
        if (not self.sheet == None):
            ## First row is headers
            r1 = "{0}!A:B".format(self.nicknamePageName)
            reply = self.sheet.values().get(spreadsheetId=self.spreadsheetId,
                                            range=r1).execute()
            return (reply["values"])

    ## Clears the nickname mapping, then adds back the new mapping
    def updateNicknameMapping(self, v):
        if (not self.sheet == None):
            r1 = "{0}!A:B".format(self.nicknamePageName)
            body = {"values": v}
            reply = self.sheet.values().clear(spreadsheetId=self.spreadsheetId,
                                              range=r1).execute()
            reply = self.sheet.values().update(
                spreadsheetId=self.spreadsheetId,
                range=r1,
                valueInputOption='RAW',
                body=body).execute()

    ## Gets active GPQ party page
    def getActivePage(self):
        if (not self.sheet == None):
            metadata = self.sheet.get(
                spreadsheetId=self.spreadsheetId).execute()
            sheets = metadata.get('sheets', '')
            for sheet in sheets:
                props = sheet["properties"]
                if (props["index"] == 0):
                    self.currentPageId = props["sheetId"]
                    self.currentPageName = props["title"]
                    return

    ## Clears the attending column, then adds back everyone that is still attending
    def updateAttendance(self, v, attendance):
        if (not self.sheet == None):
            r1 = "{0}!{1}3:{1}".format(self.currentPageName, attendance.value)
            body = {"values": v}
            reply = self.sheet.values().clear(spreadsheetId=self.spreadsheetId,
                                              range=r1).execute()
            reply = self.sheet.values().update(
                spreadsheetId=self.spreadsheetId,
                range=r1,
                valueInputOption="RAW",
                body=body).execute()

    ## display_name is not always accurate it appears (perhaps just in reaction lists)
    ## Also will extract text within parenthesis if available, assuming that is an IGN
    async def getNickOrIgn(self, i):
        ch = self.bot.get_channel(self.gpqMessage["ch"])
        g = ch.guild
        user = await g.fetch_member(i)
        res = re.search(r"\((.*)\)", user.display_name)
        if (res):
            return (res.group(1))
        else:
            return (user.display_name)

    ## converts a number to the column string
    def cs(self, n):
        n += 1
        s = ""
        while n > 0:
            n, r = divmod(n - 1, 26)
            s = chr(65 + r) + s
        return (s)

    class Attendance(Enum):
        YES = 'Z'
        NO = 'AC'
        MAYBE = 'AE'
예제 #25
0
class JailService(commands.Cog):
    def __init__(self, bot):
        self.bot = bot
        self.command = bot_setup['command']

    @commands.group()
    @commands.check(is_public)
    @commands.bot_has_guild_permissions(administrator=True,
                                        manage_messages=True,
                                        manage_roles=True)
    @commands.check_any(commands.has_guild_permissions(administrator=True),
                        commands.check(is_overwatch),
                        commands.check(is_community_owner))
    async def jail(self, ctx):
        """
        Entry point for jail actions.

        Args:
            ctx (discord.Context)
        """
        if ctx.invoked_subcommand is None:
            title = '__Available commands under ***Jail*** category!__'
            description = 'With jail system, with administrative rights, user can put another member to jail for ' \
                          'N amount of time. System handles on its own jail release and returning of perks from pre-' \
                          'jail time upon expirations'

            value = [{
                'name': f'Activate jail',
                'value': f'```{self.command}jail on```'
            }, {
                'name': f'Deactivate jail',
                'value': f'```{self.command}jail off```'
            }, {
                'name':
                f'Release members sooner manually',
                'value':
                f'```{self.command}jail release <@discord.User>```'
            }, {
                'name':
                f'Send member to jail',
                'value':
                f'```{self.command}jail punish <@discord.User> <duration in minutes>```'
            }]

            await custom_message.embed_builder(ctx=ctx,
                                               title=title,
                                               description=description,
                                               data=value)

    @jail.command()
    @commands.check(is_community_registered)
    async def on(self, ctx):
        """
        Command turns the jail and profanity system ON

        Args:
            ctx (discord.Context)
        """
        if jail_sys_manager.turn_on_off(community_id=int(ctx.message.guild.id),
                                        direction=1):
            title = '__System Message__'
            message = 'You have turned ON the automatic jail system and profanity monitor successfully. '
            await custom_message.system_message(ctx=ctx,
                                                color_code=0,
                                                message=message,
                                                destination=1,
                                                sys_msg_title=title)
        else:
            message = f'There was a backend error. Please try again later or contact one of the administrators on the community. We apologize for inconvinience'
            await custom_message.system_message(ctx,
                                                message=message,
                                                color_code=1,
                                                destination=1)

    @jail.command()
    @commands.check(is_community_registered)
    async def off(self, ctx):
        """
        Command turns the jail and profanity system OFF

        Args:
            ctx (discord.Context)
        """
        if jail_sys_manager.turn_on_off(community_id=int(ctx.message.guild.id),
                                        direction=0):
            title = '__System Message__'
            message = 'You have turned OFF automtic jail system and profanity successfully. Your members can get crazy'
            await custom_message.system_message(ctx=ctx,
                                                color_code=0,
                                                message=message,
                                                destination=1,
                                                sys_msg_title=title)
        else:
            message = f'There was a backend error. Please try again later or contact one of the administrators on the community. We apologize for inconvinience'
            await custom_message.system_message(ctx,
                                                message=message,
                                                color_code=1,
                                                destination=1)

    @jail.command()
    @commands.check(is_public)
    @commands.check_any(commands.check(is_overwatch),
                        commands.check(is_community_owner))
    async def release(self, ctx, user: Member):
        """
        Allows user with either overwatch or community owner rights to release discord member from the jail.

        Args:
            ctx (dscrod.Context): 
            user (discord.Member): 
        """
        # Check if member in jail
        if jail_manager.check_if_jailed(user_id=user.id,
                                        community_id=ctx.guild.id):
            user_details = jail_manager.get_jailed_user(discord_id=user.id)
            if user_details:
                if jail_manager.remove_from_jailed(discord_id=user.id):
                    release = datetime.utcnow()
                    all_role_ids = user_details["roleIds"]
                    free = discord.Embed(title='__Jail message__',
                                         color=discord.Color.green())
                    free.set_thumbnail(url=self.bot.user.avatar_url)
                    free.add_field(name='Time of release',
                                   value=f'{release}',
                                   inline=False)
                    free.add_field(
                        name='Message',
                        value=
                        f'You have been manually released from jail by the {ctx.message.author} '
                        f'on {ctx.message.guild}')
                    await user.send(embed=free)

                    free = discord.Embed(title='__Jail message__',
                                         color=discord.Color.green())
                    free.set_thumbnail(url=self.bot.user.avatar_url)
                    free.add_field(name='Time of release',
                                   value=f'{release}',
                                   inline=False)
                    free.add_field(
                        name='Message',
                        value=
                        f'You have successfully released from jail {user} on {ctx.message.guild}'
                    )
                    await user.send(embed=free)

                    # Check if member still exists
                    if all_role_ids:
                        for taken_role in all_role_ids:
                            to_give = ctx.message.guild.get_role(
                                role_id=int(taken_role))
                            if to_give:
                                await user.add_roles(
                                    to_give, reason='Returning back roles')

                    role_rmw = discord.utils.get(ctx.guild.roles,
                                                 name="Jailed")

                    if role_rmw:
                        if role_rmw in user.roles:
                            await user.remove_roles(role_rmw,
                                                    reason='Jail time served')

                    print(
                        Fore.LIGHTGREEN_EX +
                        f"{user} Successfully released from jail on {ctx.message.guild} "
                        f"and state restored ")

                    message = f'You have successfully release {user} from the jail, and his' \
                              f' pre-jail perks have been returned.'
                    await custom_message.system_message(ctx,
                                                        message=message,
                                                        color_code=0,
                                                        destination=1)
                else:
                    message = f'User {user} could not be release from jail due to system error. Please try again later. '
                    await custom_message.system_message(ctx,
                                                        message=message,
                                                        color_code=1,
                                                        destination=1)
            else:
                message = f'User {user} is not jailed at this moment. '
                await custom_message.system_message(ctx,
                                                    message=message,
                                                    color_code=1,
                                                    destination=1)
        else:
            message = f'User {user} is not jailed at this moment. '
            await custom_message.system_message(ctx,
                                                message=message,
                                                color_code=1,
                                                destination=1)

    @jail.command()
    @commands.check(is_public)
    @commands.check(is_community_registered)
    @commands.check_any(commands.check(is_overwatch),
                        commands.check(is_community_owner))
    async def punish(self,
                     ctx,
                     jailee: Member,
                     duration: int,
                     *,
                     subject: str = None):
        # Current time
        start = datetime.utcnow()
        # Set the jail expiration to after N minutes
        td = timedelta(minutes=int(duration))

        # calculate future date
        end = start + td
        expiry = (int(time.mktime(end.timetuple())))
        end_date_time_stamp = datetime.utcfromtimestamp(expiry)

        # guild = self.bot.get_guild(id=int(message.guild.id))  # Get guild
        if ctx.author.top_role.position >= jailee.top_role.position:
            active_roles = [role.id
                            for role in jailee.roles][1:]  # Get active roles
            if jailee.id != ctx.message.guild.owner_id:
                if not jailee.bot:
                    if ctx.author.id != jailee.id:
                        # jail user in database
                        if not jail_manager.check_if_jailed(
                                user_id=int(
                                    jailee.id), community_id=ctx.guild.id):
                            if jail_manager.throw_to_jail(
                                    user_id=jailee.id,
                                    community_id=ctx.guild.id,
                                    expiration=expiry,
                                    role_ids=active_roles):

                                # Remove user from active counter database
                                if jail_manager.remove_from_counter(
                                        discord_id=int(jailee.id)):

                                    # Send message
                                    jailed_info = discord.Embed(
                                        title='__You have been jailed!__',
                                        description=
                                        f' You have been manually jailed by '
                                        f'{ctx.message.author} on {ctx.guild} for '
                                        f'{duration} minutes. Status will be restored '
                                        f'once Jail Time Expires.',
                                        color=discord.Color.red())
                                    jailed_info.set_thumbnail(
                                        url=self.bot.user.avatar_url)
                                    jailed_info.add_field(name=f'Reason',
                                                          value=f'{subject}',
                                                          inline=False)
                                    jailed_info.add_field(
                                        name=f'Jail time duration:',
                                        value=f'{duration} minutes',
                                        inline=False)
                                    jailed_info.add_field(
                                        name=f'Sentence started @:',
                                        value=f'{start} UTC',
                                        inline=False)
                                    jailed_info.add_field(
                                        name=f'Jail-time ends on:',
                                        value=f'{end_date_time_stamp} UTC',
                                        inline=False)
                                    jailed_info.set_thumbnail(
                                        url=self.bot.user.avatar_url)
                                    await jailee.send(embed=jailed_info)

                                    # Send notf to user who executed
                                    executor = discord.Embed(
                                        title='__User Jailed__',
                                        description=
                                        f' You have successfully jailed {jailee} on '
                                        f'{ctx.guild} for {duration} minutes. Status '
                                        f'will be restored once Jail Time Expires.',
                                        color=discord.Color.red())
                                    executor.set_thumbnail(
                                        url=self.bot.user.avatar_url)
                                    executor.add_field(name=f'Reason',
                                                       value=f'{subject}',
                                                       inline=False)
                                    executor.add_field(
                                        name=f'Jailed User:'******'{jailee} \n id: {jailee.id}',
                                        inline=False)
                                    executor.add_field(
                                        name=f'Jail time duration:',
                                        value=f'{duration} minutes',
                                        inline=False)
                                    executor.add_field(
                                        name=f'Sentence started @:',
                                        value=f'{start} UTC',
                                        inline=False)
                                    executor.add_field(
                                        name=f'Sentece ends on:',
                                        value=f'{end_date_time_stamp} UTC',
                                        inline=False)
                                    executor.set_thumbnail(
                                        url=self.bot.user.avatar_url)

                                    # Send notf to channel
                                    await ctx.author.send(embed=executor)

                                    # ADD Jailed role to user
                                    role = discord.utils.get(ctx.guild.roles,
                                                             name="Jailed")
                                    await jailee.add_roles(
                                        role, reason='Jailed......')
                                    print(
                                        Fore.RED +
                                        f'User {jailee} has been jailed by {ctx.message.author} '
                                        f'on {ctx.message.guild.id}!!!!')
                                    print(Fore.GREEN +
                                          'Removing active roles from user')
                                    for role in active_roles:
                                        role = ctx.guild.get_role(
                                            role_id=int(role))  # Get the role
                                        await jailee.remove_roles(
                                            role, reason='Jail time')
                            else:
                                title = '__Manual Jail Function Error__'
                                message = f'Member {jailee} could not be jailed at this moment' \
                                          f' due to the backend system error!'
                                await custom_message.system_message(
                                    ctx,
                                    message=message,
                                    color_code=1,
                                    destination=1,
                                    sys_msg_title=title)
                        else:
                            title = '__User already Jailed!__'
                            message = f'Member {jailee} is already jailed!'
                            await custom_message.system_message(
                                ctx,
                                message=message,
                                color_code=1,
                                destination=1,
                                sys_msg_title=title)
                    else:
                        title = '__Jail error!__'
                        message = f'Why would someone want to jail himself?'
                        await custom_message.system_message(
                            ctx,
                            message=message,
                            color_code=1,
                            destination=1,
                            sys_msg_title=title)
                else:
                    title = '__Jail error!__'
                    message = f'AI Sticks together... You cant jail {jailee} as it is a bot!'
                    await custom_message.system_message(ctx,
                                                        message=message,
                                                        color_code=1,
                                                        destination=1,
                                                        sys_msg_title=title)
            else:
                title = '__Jail error!__'
                message = f'Owner of the guild can not be jailed!'
                await custom_message.system_message(ctx,
                                                    message=message,
                                                    color_code=1,
                                                    destination=1,
                                                    sys_msg_title=title)
        else:
            title = '__Jail error!__'
            message = f'You cant jail member who has higher ranked role than you.'
            await custom_message.system_message(ctx,
                                                message=message,
                                                color_code=1,
                                                destination=1,
                                                sys_msg_title=title)

    @jail.error
    async def jail_error(self, ctx, error):
        if isinstance(error, commands.CheckFailure):
            message = f'Command is allowed to be executed only on the public channels of the {ctx.message.guild}' \
                      f' and community needs to be registered into the ***JAIL*** system.'
            await custom_message.system_message(ctx,
                                                message=message,
                                                color_code=1,
                                                destination=1)
        if isinstance(error, commands.CheckAnyFailure):
            message = f'In order to use jail on {ctx.guild} you either need to be on ***Overwatch roster, owern ' \
                      f'of the community or have administrator*** rights!.'
            await custom_message.system_message(ctx,
                                                message=message,
                                                color_code=1,
                                                destination=1)
        elif isinstance(error, commands.BotMissingPermissions):
            message = 'Bot has insufficient permissions which are required to register for services. It requires at ' \
                      'least administrator priileges with message and role management permissions!'
            await custom_message.system_message(ctx,
                                                message=message,
                                                color_code=1,
                                                destination=1)

    @punish.error
    async def punish_error(self, ctx, error):
        if isinstance(error, commands.CheckFailure):
            message = f'***ERROR{error}*** \nThis error occured from possible reasons:\n--> Command not executed ' \
                      f'on public channels of the {ctx.message.guild}\n --> jail service not ' \
                      f'registered ({self.bot.user.mention} service)'
            await custom_message.system_message(ctx,
                                                message=message,
                                                color_code=1,
                                                destination=1)
        elif isinstance(error, commands.CheckAnyFailure):
            message = f'In order to use jail on {ctx.guild} you either need to be on ***Overwatch*** roster, owner' \
                      f'of the community or have administrator*** rights!.'
            await custom_message.system_message(ctx,
                                                message=message,
                                                color_code=1,
                                                destination=1)
        elif isinstance(error, commands.BadArgument):
            message = f'Wrong argument provided:\n __{error}__. \nCommand structure is:\n***{self.bot.user.mention} ' \
                      f'jail punish <@discord.User> <duration in minutes>***'
            await custom_message.system_message(ctx,
                                                message=message,
                                                color_code=1,
                                                destination=1)
        elif isinstance(error, commands.MissingRequiredArgument):
            message = f'You forgot to provide all required arguments. \n***{self.bot.user.mention} jail punish ' \
                      f'<@discord.User> <duration in minutes> <message=Optional>***'
            await custom_message.system_message(ctx,
                                                message=message,
                                                color_code=1,
                                                destination=1)
        else:
            title = '__:bug: Found__'
            message = f'Bug has been found while executing command and {self.bot.user} service team has been ' \
                      f'automatically notified. We apologize for inconvenience!'
            await custom_message.system_message(ctx,
                                                message=message,
                                                color_code=1,
                                                destination=1,
                                                sys_msg_title=title)
            dest = await self.bot.fetch_user(user_id=int(360367188432912385))
            await custom_message.bug_messages(ctx=ctx,
                                              error=error,
                                              destination=dest)

    @release.error
    async def release_error(self, ctx, error):
        if isinstance(error, commands.CheckFailure):
            message = f'Command is allowed to be executed only on the public channels of the {ctx.message.guild}.'
            await custom_message.system_message(ctx,
                                                message=message,
                                                color_code=1,
                                                destination=1)
        elif isinstance(error, commands.CheckAnyFailure):
            message = f'You do not have rights to access this area of {self.bot.user.mention} on {ctx.message.guild}.'
            await custom_message.system_message(ctx,
                                                message=message,
                                                color_code=1,
                                                destination=1)
        elif isinstance(error, commands.BadArgument):
            message = f'Wrong argument provided:\n {error}.\n Command structure is:\n ***{self.bot.user.mention} ' \
                      f'jail release <@discord.User>***'
            await custom_message.system_message(ctx,
                                                message=message,
                                                color_code=1,
                                                destination=1)
        else:
            title = '__:bug: Found__'
            message = f'Bug has been found while executing command and {self.bot.user} service team has been' \
                      f' automatically notified. We apologize for inconvenience!'
            await custom_message.system_message(ctx,
                                                message=message,
                                                color_code=1,
                                                destination=1,
                                                sys_msg_title=title)
            dest = await self.bot.fetch_user(user_id=int(360367188432912385))
            await custom_message.bug_messages(ctx=ctx,
                                              error=error,
                                              destination=dest)

    @on.error
    async def on_error(self, ctx, error):
        if isinstance(error, commands.CheckFailure):
            message = f'{ctx.guild} has not been registered yet for ***JAIL*** service. Please start' \
                      f' with ***{self.bot.mention} service register jail***'
            await custom_message.system_message(ctx,
                                                message=message,
                                                color_code=1,
                                                destination=1)

    @off.error
    async def off_error(self, ctx, error):
        if isinstance(error, commands.CheckFailure):
            message = f'{ctx.guild} has not been registered yet for ***JAIL*** service. ' \
                      f'Please start with ***{bot_setup["command"]} service register jail***'
            await custom_message.system_message(ctx,
                                                message=message,
                                                color_code=1,
                                                destination=1)
예제 #26
0
class Utils(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @commands.command(name='ping')
    async def ping(self, ctx):
        """
        Gets the ping of the bot.
        """
        now = datetime.now()
        message = await ctx.send('Ping!')
        await message.edit(
            content=f'Pong!\nBot: {int(ctx.bot.latency*1000)} ms\n'
            f'Discord: {int((datetime.now() - now).total_seconds()*1000)} ms')

    @commands.command(name='uptime', aliases=['up', 'alive'])
    async def uptime(self, ctx):
        """
        Displays the current uptime of the bot.
        """
        embed = create_default_embed(ctx)
        embed.title = 'Poddo Uptime'
        bot_up = time_to_readable(self.bot.uptime)
        embed.add_field(name='Bot Uptime', value=f'{bot_up}')
        if ctx.bot.is_ready():
            embed.add_field(
                name='Ready Uptime',
                value=
                f'{time_to_readable(datetime.utcnow() - self.bot.ready_time)}')
        return await ctx.send(embed=embed)

    @commands.command(name='prefix')
    @commands.check_any(commands.has_guild_permissions(manage_guild=True),
                        commands.is_owner())
    @commands.guild_only()
    async def change_prefix(self, ctx, to_change: str = None):
        """
        Changes the prefix for the current guild.
        Can only be ran in a guild. If no prefix is specified, will show the current prefix.
        Requires Manage Server permissions.
        """
        guild_id = str(ctx.guild.id)
        if to_change is None:
            if guild_id in self.bot.prefixes:
                prefix = self.bot.prefixes.get(guild_id, BOT_PREFIX)
            else:
                dbsearch = await self.bot.mdb['prefixes'].find_one(
                    {'guild_id': guild_id})
                if dbsearch is not None:
                    prefix = dbsearch.get('prefix', BOT_PREFIX)
                else:
                    prefix = BOT_PREFIX
                self.bot.prefixes[guild_id] = prefix
            return await ctx.send(
                f'No prefix specified to change. Current Prefix: `{prefix}`')
        else:
            await ctx.bot.mdb['prefixes'].update_one(
                {'guild_id': guild_id}, {'$set': {
                    'prefix': to_change
                }},
                upsert=True)
            ctx.bot.prefixes[guild_id] = to_change
            return await ctx.send(f'Guild prefix updated to `{to_change}`')
예제 #27
0
class Fun(commands.Cog):
    'Commands made for fun'

    def __init__(self, bot):
        self.bot = bot

    @commands.group(invoke_without_command=True,
                    description='Get a random quote from a user')
    @commands.guild_only()
    async def quote(self, ctx, *, member: discord.Member = None):
        guild_data = await ctx.fetchrow(
            f'SELECT * FROM quotes WHERE guild_id = {ctx.guild.id}')

        if not guild_data or not guild_data['quoted_members']:
            return await ctx.send(
                f'I dont have any quotes stored. Use {ctx.prefix}help quote add to see how to add a quote'
            )

        if not member:
            member = random.choice(guild_data['quoted_members'])
            quote = random.choice(member[2:])
            return await ctx.send(
                f'Here is a quote from {member[0]}:\n> {quote}')

        quote = discord.utils.find(lambda m: m[1] == str(member.id),
                                   guild_data['quoted_members'])

        if not quote:
            return await ctx.send('I couldnt find a quote for that member')

        await ctx.send(
            f'Heres a quote from {quote[0]}:\n> {random.choice(quote[2:])}')

    @quote.command(description='Add a quote to someone')
    @commands.check_any(commands.is_owner(),
                        commands.has_guild_permissions(manage_guild=True))
    async def add(self, ctx, member: discord.Member, *, quote):
        guild_data = await ctx.fetchrow(
            f'SELECT * FROM quotes WHERE guild_id = {ctx.guild.id}')

        if not guild_data or not guild_data['quoted_members']:
            await ctx.execute(
                f"INSERT INTO quotes(guild_name, guild_id, quoted_members) VALUES('{ctx.guild.name}', {ctx.guild.id}, ARRAY[['{member}', '{member.id}', '{quote}']])"
            )

        elif guild_data['quoted_members']:
            guild_data['quoted_members'].append(
                [str(member), str(member.id), quote])
            await ctx.execute(
                f"UPDATE quotes SET quoted_members = ARRAY{guild_data['quoted_members']} WHERE guild_id = {ctx.guild.id}"
            )

        await ctx.send(f'Added the quote "{quote}" to {member}')

    @quote.command(description='Remove a quote from someone')
    @commands.check_any(commands.is_owner(),
                        commands.has_guild_permissions(manage_guild=True))
    async def remove(self, ctx, member: discord.Member, *, quote):
        guild_data = await ctx.fetchrow(
            f'SELECT * FROM quotes WHERE guild_id = {ctx.guild.id}')

        if not guild_data or not guild_data['quoted_members']:
            return await ctx.send('Your server doesnt have any quotes set')

        if not discord.utils.find(lambda m: m[1] == str(member.id),
                                  guild_data['quoted_members']):
            return await ctx.send(
                'That person doesnt have any quotes to remove')

        if not discord.utils.find(
                lambda m: m[1] == str(member.id) and quote in m,
                guild_data['quoted_members']):
            return await ctx.send('That person doesnt have that quote')

        for elem in guild_data['quoted_members']:
            if elem[1] == str(member.id) and quote in elem:
                guild_data['quoted_members'].remove(elem)
                await ctx.execute(
                    f"UPDATE quotes SET quoted_members = ARRAY{guild_data['quoted_members']}::text[] WHERE guild_id = {ctx.guild.id}"
                )
                return await ctx.send(f'Removed "{quote}" from {member}')

    @quote.command(description='Splice two random quotes together')
    @commands.guild_only()
    async def splice(self, ctx, members: commands.Greedy[discord.Member]):
        guild_data = await ctx.fetchrow(
            f'SELECT * FROM quotes WHERE guild_id = {ctx.guild.id}')

        if not guild_data or not guild_data['quoted_members']:
            return await ctx.send('Your server doesnt have any quotes set')

        if len(guild_data['quoted_members']) <= 1:
            return await ctx.send('I dont have enough quotes to splice')

        if members:
            if len(members) == 1:
                return await ctx.send(
                    'I need at least two people to splice quotes from\nAlternitively, you can run the command without specifying two people'
                )

            if not discord.utils.find(lambda m: m[1] == str(members[0].id),
                                      guild_data['quoted_members']):
                return await ctx.send(
                    f'{members[0]} doesnt have a quote stored')

            if not discord.utils.find(lambda m: m[1] == str(members[1].id),
                                      guild_data['quoted_members']):
                return await ctx.send(
                    f'{members[1]}  doesnt have a quote stored')

            member1 = members[0]
            member2 = members[1]

            quote1 = random.choice([
                m for m in guild_data['quoted_members']
                if m[1] == str(member1.id)
            ])
            quote2 = random.choice([
                m for m in guild_data['quoted_members']
                if m[1] == str(member2.id)
            ])

        else:
            quote1 = random.choice(guild_data['quoted_members'])
            member1 = quote1[0]

            quote2 = random.choice(guild_data['quoted_members'])

            while quote1[1] == quote2[1]:
                quote2 = random.choice(guild_data['quoted_members'])

            member2 = quote2[0]

        quote1 = quote1[2].split(' ')
        decoy1 = ' '.join(quote1[math.ceil(len(quote1) / 2):])
        decoy2 = ' '.join(quote1[:math.ceil(len(quote1) / 2)])
        quote1 = random.choice([decoy1, decoy2])

        quote2 = quote2[2].split(' ')
        decoy1 = ' '.join(quote2[math.ceil(len(quote2) / 2):])
        decoy2 = ' '.join(quote2[:math.ceil(len(quote2) / 2)])
        quote2 = random.choice([decoy1, decoy2])

        await ctx.send(
            f"Combined quotes from {member1} and {member2}\n> {quote1 + ' ' + quote2}"
        )
예제 #28
0
class Crosschat(commands.Cog):
    def __init__(self, bot: AnsuraBot):
        self.colors = {}
        self.bot = bot
        self._cd = commands.CooldownMapping.from_cooldown(
            3, 15, commands.BucketType.user)
        self.channels: Optional[Dict[int, int]] = None
        self.banned: Optional[List[int]] = None
        self.exempt: Optional[List[int]] = None

        self.messages: List[List[int, int, int, List[Tuple[int, int]],
                                 str]] = []
        """
        Originating Guild ID
        Originating Channel ID
        Message Author ID
        Channel ID + Message ID list
        Message content
        """

        if os.path.exists("crosschat_persistence.pickle"):
            with open("crosschat_persistence.pickle", "rb") as fp:
                self.messages = pickle.load(fp)
            os.remove("crosschat_persistence.pickle")

        self.ansura_color = discord.Colour.from_rgb(0x4a, 0x14, 0x8c)
        self._reload()
        for i in self.channels:
            color = int(i) // 64 % (14**3) + 0x222
            rd = color >> 8
            gr = (color & 0x0f0) >> 4
            bl = (color & 0xf)
            if abs(rd - self.ansura_color.r) < 0x20:
                rd = (rd + 0x40) % 0x100
            if abs(gr - self.ansura_color.g) < 0x20:
                gr = (gr + 0x40) % 0x100
            if abs(bl - self.ansura_color.b) < 0x20:
                bl = (bl + 0x40) % 0x100
            self.colors[int(i)] = discord.Colour.from_rgb(
                rd * 0x10, gr * 0x10, bl * 0x10)
            print(self.colors[int(i)])

    def _resolve(self, u):
        if self.bot.get_user(u):
            return f"*U* {self.bot.get_user(u)}"
        if self.bot.get_guild(u):
            return f"*G* {self.bot.get_guild(u)}"
        return None

    @commands.command()
    @ansura_staff_or_selfhost_owner()
    async def xclist(self, ctx: AnsuraContext):
        channels = pages([
            f"{self.bot.get_guild(k)} ({k})\n - {self.bot.get_channel(v)} ({v})"
            for k, v in self.channels.items()
        ],
                         10,
                         fmt="%s",
                         title="Channels")
        banned = pages([f"{self._resolve(u)} - {u}" for u in self.banned],
                       10,
                       fmt="%s",
                       title="Banned")
        exempt = pages([f"{self.bot.get_user(u)} - {u}" for u in self.exempt],
                       10,
                       fmt="%s",
                       title="Exempt")
        await BotEmbedPaginator(ctx, list(chain(channels, banned,
                                                exempt))).run()

    @commands.command()
    @ansura_staff_or_selfhost_owner()
    async def xcreload(self, ctx: AnsuraContext):
        self._reload()
        await ctx.send("Reloaded")

    def _reload(self):
        with open("xchat.yaml") as fp:
            config = YAML().load(fp)
        self.channels = config["channels"]
        self.banned = config["banned"]
        self.exempt = config["exempt"]
        for i in self.channels:
            color = int(i) // 64 % (14**3) + 0x222
            rd = color >> 8
            gr = (color & 0x0f0) >> 4
            bl = (color & 0xf)
            self.colors[int(i)] = discord.Colour.from_rgb(
                rd * 0x11, gr * 0x11, bl * 0x11)

    def _save(self):
        with open("xchat.yaml", "w") as fp:
            YAML().dump(
                {
                    "banned": self.banned,
                    "channels": self.channels,
                    "exempt": self.exempt
                }, fp)

    @commands.command()
    @ansura_staff_or_selfhost_owner()
    async def xcbans(self, ctx: AnsuraContext):
        await BotEmbedPaginator(
            ctx,
            pages([f"{self._resolve(x)} - {x}" for x in self.banned],
                  10,
                  "Crosschat bans",
                  fmt="%s")).run()

    @commands.command()
    @ansura_staff_or_selfhost_owner()
    async def xcservers(self, ctx: AnsuraContext):
        await BotEmbedPaginator(
            ctx,
            pages([
                f"**{self.bot.get_guild(int(x))}** ({x})\n- "
                f"{self.bot.get_channel(c)} ({c})"
                for x, c in self.channels.items()
            ],
                  10,
                  "Crosschat servers",
                  fmt="%s")).run()

    @commands.command()
    @commands.check_any(commands.has_permissions(administrator=True),
                        commands.has_guild_permissions(administrator=True))
    async def crosschat(self,
                        ctx: AnsuraContext,
                        arg: Union[discord.TextChannel, str] = None):
        if ctx.guild.id in self.banned:
            await ctx.send_error(
                "This guild is banned from crosschat. If this is a mistake, or to appeal this ban, "
                "go to https://discord.gg/t5MGS2X to appeal.")
            return
        if not arg:
            if ctx.guild.id in self.channels.keys():
                await ctx.send_ok(
                    f"Crosschat is set to <#{self.channels[ctx.guild.id]}>. Do "
                    f"`%crosschat #channel` to change this or `%crosschat clear` to "
                    f"turn off crosschat")
            else:
                await ctx.send_ok(
                    f"Crosschat is not enabled on this server. Do "
                    f"`%crosschat #channel` to change this.")
            return
        if isinstance(arg, discord.TextChannel):
            self.channels[ctx.guild.id] = arg.id
            await ctx.send_ok(
                f"Crosschat is set to <#{self.channels[ctx.guild.id]}>. Do "
                f"`%crosschat #channel` to change this or `%crosschat clear` to "
                f"turn off crosschat")
        elif arg == "clear":
            del self.channels[ctx.guild.id]
            await ctx.send_ok(
                f"Crosschat channel cleared. Do `%crosschat #channel` to change this."
            )
        else:
            return
        self._save()
        for i in self.channels:
            color = int(i) // 64 % (14**3) + 0x222
            rd = color >> 8
            gr = (color & 0x0f0) >> 4
            bl = (color & 0xf)
            if abs(rd - self.ansura_color.r) < 0x20:
                rd = (rd + 0x40) % 0x100
            if abs(gr - self.ansura_color.g) < 0x20:
                gr = (gr + 0x40) % 0x100
            if abs(bl - self.ansura_color.b) < 0x20:
                bl = (bl + 0x40) % 0x100
            self.colors[int(i)] = discord.Colour.from_rgb(
                rd * 0x10, gr * 0x10, bl * 0x10)

    @commands.command()
    @ansura_staff_or_selfhost_owner()
    async def xcgban(self, ctx: AnsuraContext, guild: int):
        if guild in self.banned:
            return await ctx.send_ok(
                f"Guild {self.bot.get_guild(guild).name} already banned.")
        self.banned.append(guild)
        self._save()
        await ctx.send_ok(
            f"Guild {self.bot.get_guild(guild).name or guild} banned.")

    @commands.command()
    @ansura_staff_or_selfhost_owner()
    async def xcgunban(self, ctx: AnsuraContext, guild: int):
        if guild not in self.banned:
            return await ctx.send_ok(
                f"Guild {self.bot.get_guild(guild).name} not banned.")
        self.banned.remove(guild)
        self._save()
        await ctx.send_ok(
            f"Guild {self.bot.get_guild(guild).name or guild} unbanned.")

    @commands.command()
    @ansura_staff_or_selfhost_owner()
    async def xcban(self, ctx: AnsuraContext, member: Union[discord.Member,
                                                            int]):
        if isinstance(member, discord.Member):
            member = member.id
        if member not in self.banned:
            self.banned.append(member)
            self._save()
            await ctx.send_ok(
                f"{self.bot.get_user(member)} ({member}) xchat banned")
        else:
            await ctx.send_ok(
                f"{self.bot.get_user(member)} ({member}) already xchat banned")

    @commands.command()
    @ansura_staff_or_selfhost_owner()
    async def xcunban(self, ctx: AnsuraContext, member: Union[discord.Member,
                                                              int]):
        if isinstance(member, discord.Member):
            member = member.id
        if member in self.banned:
            self.banned.remove(member)
            self._save()
            await ctx.send_ok(
                f"{self.bot.get_user(member)} ({member}) xchat unbanned")
        else:
            await ctx.send_ok(
                f"{self.bot.get_user(member)} ({member}) already not banned")

    async def init_channels(self):
        print("[XCHAT] Looking for channels")
        self._reload()
        print(f" - Found {len(self.channels)} channels")
        print(f" - Found {len(self.banned)} banned members")
        print("[XCHAT] Channel search done")

    async def xchat(self, message: discord.Message):  # noqa c901
        channel: discord.TextChannel = message.channel
        if channel.id not in self.channels.values():
            return
        if message.type in (discord.MessageType.pins_add,
                            discord.MessageType.new_member):
            return
        if message.author.id in self.banned or message.guild.id in self.banned:
            try:
                await message.delete()
            except discord.errors.Forbidden:
                pass
            return
        time = self._cd.get_bucket(message).update_rate_limit()
        if time and message.author.id not in self.exempt:
            try:
                await message.delete()
            except discord.errors.Forbidden:
                pass
            await message.channel.send(
                f"{message.author.mention}, you're sending messages too fast! "
                f"Try again in {round(time)} seconds.",
                delete_after=30)
            return
        guild: discord.Guild = channel.guild
        author: discord.Member = message.author
        e = discord.Embed()
        dev = ""
        e.colour = self.colors[int(guild.id)]
        e.set_author(name=guild.name, icon_url=str(guild.icon_url))
        if self.bot.user.id in [791266463305957377, 804791983884992608]:
            g: discord.Guild = self.bot.get_guild(788661880343363625)
            m: discord.Member = g.get_member(author.id)
            if m and 788661880431312906 in [r.id for r in m.roles]:
                dev = " | "
                dev += "Developer" if author.id == 569362627935862784 else "Crosschat Moderator"
                e.colour = self.ansura_color
            elif m and 788661880343363632 in [r.id for r in m.roles]:
                dev = " | Aoi Contributor"
                e.colour = self.ansura_color
            elif m and 803034721595424798 in [r.id for r in m.roles]:
                dev = " | Ansura Contributor"
                e.colour = self.ansura_color
        user: discord.User = message.author
        e.description = AnsuraContext.escape(message.content, message)
        err_s = ""
        file = None
        reference: Optional[discord.MessageReference] = message.reference
        messages = None
        author_id = None
        content = None
        cache = {}
        found = False
        if reference:
            ref_id = reference.message_id
            # find message in xchat cache
            for i in self.messages:
                # guild_id = i[0]
                # channel_id = i[1]
                author_id = i[2]
                content = i[4]
                messages = i[3]

                for m in i[3]:
                    if m[1] == ref_id:
                        found = True
                        break
                if found:
                    break
        if messages:
            for m in messages:
                c: discord.TextChannel = self.bot.get_channel(m[0])
                if c:
                    cache[c.guild.id] = (c.id, m[1])

        if message.attachments:
            if self._is_image(message.attachments[0].filename):
                with open(f"attachments/{message.attachments[0].filename}",
                          "wb") as fp:
                    await message.attachments[0].save(fp)
                file = True
                e.set_image(
                    url=f"attachment://{message.attachments[0].filename}")
        else:
            file = False
        try:
            await message.delete()
        except discord.errors.Forbidden as err:
            if err.status == 403:
                err_s = " | Could not delete original message. Make sure I have manage messages in this channel."
        except discord.errors.NotFound:
            pass
        sent = []
        desc = e.description
        content = "\n".join(
            f"> {line}" for line in content.splitlines()) if content else None
        for k in self.channels.keys():
            c: discord.TextChannel = self.bot.get_channel(self.channels[k])
            if cache and k in cache:
                e.description = f"Reply to [{self.bot.get_user(author_id).name}#" \
                                f"{str(self.bot.get_user(author_id).discriminator)[:2]}xx]" \
                                f"(https://discord.com/channels/{k}/{c.id}/{cache[k][1]})\n{content}\n\n" + desc
            else:
                e.description = desc
            if k == message.guild.id:
                e.set_footer(text=user.name + "#" +
                             str(user.discriminator)[0:2] + "xx" + err_s + dev,
                             icon_url=user.avatar_url)
            else:
                e.set_footer(text=user.name + "#" +
                             str(user.discriminator)[0:2] + "xx" + dev,
                             icon_url=user.avatar_url)
            if c is not None:
                if file:
                    with open(f"attachments/{message.attachments[0].filename}",
                              "rb") as fp:
                        msg = await c.send(
                            embed=e,
                            file=discord.File(fp,
                                              message.attachments[0].filename))
                else:
                    msg = await c.send(embed=e)
                sent.append((c.id, msg.id))
        self.messages.append([
            message.guild.id, message.channel.id, message.author.id, sent,
            message.content
        ])
        if len(self.messages) > 250:
            del self.messages[0]
        if file:
            os.remove(f"attachments/{message.attachments[0].filename}")

    def _is_image(self, url: str):
        for i in "jpg,jpeg,png,gif".split(","):
            if url.endswith("." + i):
                return True
        else:
            return False

    @commands.command()
    @ansura_staff_or_selfhost_owner()
    async def xclookup(self, ctx: AnsuraContext,
                       message: Union[discord.Message, int]):
        if isinstance(message, discord.Message):
            msg_id = message.id
        else:
            msg_id = message
        found = False
        for i in self.messages:
            guild = i[0]
            channel = i[1]
            author = i[2]
            msgs = i[3]
            content = i[4]

            for m in msgs:
                if m[1] == msg_id:
                    found = True
                    break
            if found:
                break
        else:
            return await ctx.send_error("Message not found")
        await ctx.embed(
            title="Message lookup",
            fields=[
                ("Guild", f"{self.bot.get_guild(guild)} - {guild}"),
                ("Channel", f"{self.bot.get_channel(channel)} - {channel}"),
                ("Author", f"{self.bot.get_user(author)} - {author}"),
                ("Content", content[:800]),
            ],
            not_inline=[0, 1, 2, 3])

    @commands.command()
    @ansura_staff_or_selfhost_owner()
    async def xcdelete(self, ctx: AnsuraContext,
                       message: Union[discord.Message, int]):  # noqa c901
        if isinstance(message, discord.Message):
            msg_id = message.id
        else:
            msg_id = message
        msgs = None
        found = False
        for i in self.messages:
            i[0]
            i[1]
            i[2]
            msgs = i[3]
            for m in msgs:
                if m[1] == msg_id:
                    found = True
                    break
            if found:
                break
        else:
            return await ctx.send("Message not found")
        count = 0
        fail = 0
        for g, c in self.channels.items():
            for m in msgs:
                chan: discord.TextChannel = self.bot.get_channel(m[0])
                if chan:
                    try:
                        await (await chan.fetch_message(m[1])).delete()
                        count += 1
                    except (discord.HTTPException, discord.Forbidden):
                        pass
        await ctx.send(f"Deleted message from {count} servers. {fail} failed")

    @commands.command()
    @ansura_staff_or_selfhost_owner()
    async def xchelp(self, ctx: AnsuraContext):
        await ctx.send(embed=discord.Embed(
            title="Ansura Crosschat Moderation",
            description=
            "**Guild Ban Management**:`xcgunban guild_id` `xcgban guild_id`\n"
            "**User Ban Management**: `xcunban member_id_or_@`/`xcban member_id_or_@`\n"
            "**List Guilds, Bans, Exemptions**: `xclist`\n"
            "**Lookup a message**: `xclookup message_link`\n"
            "**Delete a message**: `xcldelete message_link`"))
예제 #29
0
class ModerationTools(commands.Cog):
    def __init__(self, bot: BotBase):
        self.bot = bot

    @commands.command(aliases=['hackban'])
    @commands.check(commands.bot_has_guild_permissions(ban_members=True))
    @commands.check_any(commands.has_guild_permissions(ban_members=True),
                        commands.is_owner())
    async def ban(self,
                  ctx: Context,
                  user: Union[discord.Member, discord.User],
                  *,
                  reason: str = None):
        """Bans a member from the server.

        `user`: the user to ban.
        `reason`: the reason for the ban.
        """
        await ctx.guild.ban(user, reason=reason)
        await ctx.send(f'Banned {user} from the server.', delete_after=5)

    @commands.command()
    @commands.check(commands.bot_has_guild_permissions(ban_members=True))
    @commands.check_any(commands.has_guild_permissions(ban_members=True),
                        commands.is_owner())
    async def unban(self,
                    ctx: Context,
                    user: discord.User,
                    *,
                    reason: str = None):
        """Unbans a member from the server.

        `user`: the user to unban.
        `reason`: the reason for the unban.
        """
        await ctx.guild.unban(user, reason=reason)
        await ctx.send(f'{user} was unbanned from the server.', delete_after=5)

    @commands.command()
    @commands.check(commands.bot_has_guild_permissions(kick_members=True))
    @commands.check_any(commands.has_guild_permissions(kick_members=True),
                        commands.is_owner())
    async def kick(self,
                   ctx: Context,
                   user: discord.Member,
                   *,
                   reason: str = None):
        """Kicks a member from the server.

        `user`: the user to kick.
        `reason`: the reason for the kick.
        """
        await user.kick(reason=reason)
        await ctx.send(f'Kicked {user} from the server.', delete_after=5)

    @commands.command()
    @commands.check_any(commands.has_guild_permissions(manage_messages=True),
                        commands.is_owner())
    async def cleanup(self, ctx: Context, limit: int = 50):
        """Deletes messages related to bot commands from the channel.

        `limit`: the number of messages to process, can be a maximum of 100 messages.
        """
        to_delete = []

        if not 0 < limit <= 100:
            raise commands.BadArgument(
                'You can only delete between 1 and 100 messages.')

        async for message in ctx.channel.history(limit=limit):

            context = await self.bot.get_context(message)
            if message.author == self.bot.user:
                to_delete.append(message)

            if ctx.me.permissions_in(
                    ctx.channel
            ).manage_messages and context.command is not None:
                to_delete.append(message)

        await ctx.send(f'Deleted {len(to_delete)} messages', delete_after=5)

        if ctx.me.permissions_in(ctx.channel).manage_messages:
            await ctx.channel.delete_messages(to_delete)
        else:
            for message in to_delete:
                await message.delete(silent=True)

    @commands.command()
    @commands.check(commands.bot_has_guild_permissions(manage_messages=True))
    @commands.check_any(commands.has_guild_permissions(manage_messages=True),
                        commands.is_owner())
    async def nuke(self,
                   ctx: Context,
                   limit: int = 50,
                   *,
                   user: Union[discord.Member, discord.User] = None):
        """Deletes up to a given number of messages from the channel.

        `limit`: the number of messages to process, can be a maximum of 100 messages.
        `user`: Allows for only messages from a given user to be deleted.
        """
        if not 0 < limit <= 100:
            raise commands.BadArgument(
                'You can only delete between 1 and 100 messages.')

        def check(message: discord.Message):
            return user is None or message.author == user

        deleted = await ctx.channel.purge(limit=limit, check=check)

        await ctx.send(f'Deleted {len(deleted)} messages', delete_after=5)
예제 #30
0
class RedditCog(BaseCog, name="Reddit"):
    def __init__(self, bot):
        super().__init__(bot)

    @commands.Cog.listener()
    async def on_ready(self):
        await asyncio.sleep(60 * 60)
        self.start_task(self.feed_sender, check=self.bot.production)

    @commands.check_any(is_tester(),
                        commands.has_guild_permissions(administrator=True))
    @commands.group()
    async def reddit(self, ctx):
        pass

    @reddit.command(name="list")
    async def reddit_list(self, ctx):
        query = Subreddit.select()
        if isinstance(ctx.channel, discord.DMChannel):
            query = query.where(Subreddit.dm == True)
            query = query.where(Subreddit.user_id == ctx.author.id)
        else:
            query = query.where(Subreddit.channel_id == ctx.channel.id)
        names = []
        for subreddit in query:
            names.append(subreddit.subreddit.display_name)

        embed = discord.Embed(color=ctx.guild_color)
        embed.title = ctx.translate("subreddit_list")

        names = "\n".join(names)
        embed.description = f"```\n{names}```"
        asyncio.gather(ctx.send(embed=embed))

    @reddit.command(name="add", aliases=["+"])
    async def reddit_add(self,
                         ctx,
                         subreddit: SubredditConverter,
                         post_type: EnumConverter(
                             Subreddit.PostType) = Subreddit.PostType.hot):
        """Adds a subreddit to the database, this will be sent periodically to the specified channel."""
        if Subreddit.select().where(Subreddit.channel_id == ctx.channel.id,
                                    Subreddit.subreddit
                                    == subreddit).count() > 0:
            raise SendableException(ctx.translate("subreddit_already_added"))

        guild_id = ctx.guild.id if ctx.guild else None
        subreddit = Subreddit.create(guild_id=guild_id,
                                     channel_id=ctx.channel.id,
                                     user_id=ctx.author.id,
                                     subreddit=subreddit,
                                     post_type=post_type,
                                     dm=ctx.guild is None)

        await subreddit.send()
        asyncio.gather(ctx.success())

    @reddit.command(name="remove", aliases=["-"])
    async def reddit_remove(self,
                            ctx,
                            subreddit: SubredditConverter,
                            post_type: EnumConverter(
                                Subreddit.PostType) = Subreddit.PostType.hot):
        """Removes a subreddit from the database."""
        Subreddit.delete().where(Subreddit.channel_id == ctx.channel.id).where(
            Subreddit.subreddit == subreddit).execute()
        await ctx.success()

    @tasks.loop(hours=1)
    async def feed_sender(self):
        for subreddit in Subreddit.select().where(
                Subreddit.automatic == True).order_by(
                    Subreddit.channel_id.desc()):
            try:
                asyncio.gather(subreddit.send())
            except Exception as e:
                print(subreddit.id, e)
                pass