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]
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)
class Moderation(commands.Cog, name = "Moderation"): def __init__(self, bot): self.bot = bot @commands.command() commands.has_guild_permissions(manage_messages=True)
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)
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)
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)
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)
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)
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))
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.')
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)
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))
"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
def cog_check(self, ctx: commands.Context): return commands.has_guild_permissions(manage_channels=True)
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)
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)
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
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)
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`"))
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)
#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:
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]}')
def guildowner_or_perms(**perms): return commands.check_any( commands.has_guild_permissions(**perms), guildowner(), owner_in_guild() )
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'
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)
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}`')
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}" )
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`"))
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)
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